Skip to main content

Command Palette

Search for a command to run...

แชร์ประสบการณ์ Config ตัว Amazon ElastiCache ให้ใช้งานกับ Spring Boot

Published
5 min readView as Markdown
แชร์ประสบการณ์ Config ตัว Amazon ElastiCache ให้ใช้งานกับ Spring Boot
Y

My name is Yothin Muangsommuk. I've been in many roles in Software Development team, Software Engineer, Tech Lead, and Technical Coach. My focus has always been improving a sustainability and maintainability through Technical Excellence and Software Craftsmanship. I also A Python enthusiasm, An occasional conference speaker (PyCon APAC, Code Mania, Barcamp Bangkok), and a community organizer (PyCon Thailand, PySomTum, GDG Cloud Bangkok).

Interests: Programming Language Design, Software Engineering, Distributed System

Story

2 - 3 วันที่ผ่านมาพยายามต่อ Redis บน ElastiCache แล้ว Error ตลอดเวลาเรียกใช้ จะมี Log ขึ้นประมาณข้างล่างนี้ แล้วแก้จนมันใช้ได้แล้วเอาขึ้น Server ไปละ นี่คือเรื่องของมัน

org.springframework.data.redis.RedisConnectionFailureException: Unable to connect to Redis; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to ███████-cache.███████.com:6379

Caused by: io.lettuce.core.RedisConnectionException: Unable to connect to ███████-cache.███████.com:6379
e
Caused by: javax.net.ssl.SSLHandshakeException: General SSLEngine problem

Caused by: javax.net.ssl.SSLHandshakeException: General SSLEngine problem

Caused by: java.security.cert.CertificateException: No subject alternative DNS name matching ███████-cache.███████.com found.

สันนิษฐานแรกคือน่าจะเกี่ยวกับ In-Transit Encryption feature ของ ElastiCache's Redis ซึ่งถูกเลือกไว้แล้ว แล้วหลังจาก Research ไปซักระยะเลยเจอว่า ElastiCache's Redis allow ให้ Connect ผ่าน VPC เท่านั้น

Amazon ElastiCache Nodes, deployed within a VPC, can never be accessed from the Internet or from EC2 Instances outside the VPC. Source

ระหว่างนั้นเลยไปทดลองสร้างเครื่อง EC2 ในวง VPC เดียวกันมา Connect แต่ก็เจอปัญหา Error: Connection reset by peer เวลาต่อจาก redis-cli ที่วิ่งอยู่บน Stunnel แล้ว ซึ่งแก้เท่าไรก็ยังไม่ได้ และไม่ได้เกี่ยวกับ IAM Role ด้วยเพราะลอง FullAccessElastiCache ก็ยังไม่ได้

สุดท้ายพี่กานต์ลองใช้ Python Redis client ยิงดูปรากฏว่าติด หน้าตาโค้ดประมาณนี้ เลยรู้สึกว่าไม่ใช่ละ

import redis

r = redis.Redis(host='███████-cache.███████.com', port=6379, db=0, ssl=True)
r.set('foo', 'bar')
print(r.get('foo'))

ซึ่งมีจุดที่ remind อย่างนึงคือ ssl=True เลยพุ่งเป้ากลับมาที่เรื่อง SSL Certificate น่าจะมีปัญหาอะไรบางอย่าง หลังจากนั้นพยายามลองเซต spring.redis.ssl=true ใน application.properties ก็ยังไม่ได้ขึ้น Exception เหมือนตอนแรก ก็เลยมานั่งมอง Exception ละเอียดๆ อีกทีจนไปสะดุดตาอันสุดท้าย

Caused by: java.security.cert.CertificateException: No subject alternative DNS name matching ███████-cache.███████.com found.

Search ไปๆ มาๆ เลยมาเจอบล็อกนี้ [Solved] java.security.cert.CertificateException: No subject alternative names present ใจความหลักๆ เหมือนกันคือต่อ LDAP ไม่ได้ เพราะ Java 1.8.0_181 เปิด "Endpoint identification" เป็น default เพื่อให้ใช้กับ LDAPS connection ได้ ซึ่งก็มีวิธีปิดอยู่ง่ายๆ คือไปเพิ่ม property ข้างล่างก็ได้ละ

-Dcom.sun.jndi.ldap.object.disableEndpointIdentification=true

ซึ่งถึงแม้มันจะไม่ได้แก้ปัญหาของ ElastiCache ต่อไม่ได้ตรงๆ แต่ได้ keyword สำคัญมากคือ disableEndpointIdentification ลิ้งค์ต่อๆ มาเลยยังพุ่งเป้าไปที่เรื่องนี้อยู่ แล้วมาเจอกับ issues นี้ใน github CertificateException while connecting to Azure redis cluster. #1296 ซึ่งเจอกับ Redis เต็มๆ แต่เป็น Azure แต่ที่สำคัญคือ อันนี้เป็นปัญหาบน Library: Redisson ซึ่งเป็น Redis Java Client เหมือนกัน และก็ยิ่งย้ำความเชื่อว่ามันเป็นเรื่อง EndpointIdentification นี่แหละ

ณ จุดนี้คือกลับมานั่งทำความเข้าใจอีกทีว่า spring-data-redis ใช้อะไรอยู่ข้างล่าง จาก Official Document เลยค้นพบว่าข้างล่างมันใช้ Lettuce เป็น Client อยู่ เลยมาเจอ issue Lettuce can't connect to Redis Cluster + SSL but can connect to same Redis server + SSL if treated as Standalone node. #1454 ซึ่งคำตอบแรกของ issue นี้ คือคำตอบเลย

There are a couple of things you can do about:

  1. If the Redis IP addresses can be mapped onto a hostname, then please configure a MappingSocketAddressResolver through ClientResources to map IP addresses into hostnames that are listed in the SSL certificate

  2. Reach out to the Azure support team so that they fix the SSL certificates and include all cluster IP addresses

  3. (least favorable option) Disable SSL certificate validation. That can be done with RedisURI.setVerifyPeer(false) when using Lettuce directly. For Spring, you should be able to register a LettuceClientConfigurationBuilder customizer to invoke LettuceSslClientConfigurationBuilder.disablePeerVerification().

ซึ่งทำให้เราได้คีย์เวิร์คสำคัญมาคือ disablePeerVerification (มันต้องเหมือนข้างบนแน่ๆ) คำถามถัดมาคือ เราจะ disable มันยังไงใน Spring เลยต้องไปหาวิธีใช้ LettuceSslClientConfigurationBuilder อยู่ซักพักใหญ่ๆ เลย ซึ่งอ่าน Doc เฉยๆ ก็เขียนยังไม่เป็นด้วยประสบการณ์ Java แค่นี้

สุดท้ายเลยไปเริ่มจากตัวอย่างของ repository นี้ spring-data-redis-lettuce ซึ่งยกตัวอย่าง custom LettuceConnectionFactory ไว้ได้โอเคเลย แล้วทำให้เริ่มจากตรงนี้จนมาเป็น ลองผิดลองถูก อ่าน Document ของ LettuceClientConfigurationBuilder จนได้โค้ดของ configuration ข้างล่างนี้

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.StringRedisTemplate;

@Configuration
public class RedisClientConfig {
    @Value("${spring.redis.host}")
    private String redisHost;

    @Value("${spring.redis.port}")
    private int redisPort;

    @Bean
    public StringRedisTemplate redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        final StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(lettuceConnectionFactory);
        return template;
    }

    @Bean
    LettuceConnectionFactory lettuceConnectionFactory() {
        LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder()
                .useSsl()
                .disablePeerVerification()
                .build();
        RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration(redisHost, redisPort);

        return new LettuceConnectionFactory(serverConfig, clientConfig);
    }
}

แล้วใน application.properties จะเหลือแค่

spring.cache.type=redis
spring.redis.host=███████-cache.███████.com
spring.redis.port=6379
spring.cache.redis.time-to-live=18000

Resources

More from this blog

ยิง request แบบไม่มีสะดุดด้วย emacs REST client

ช่วงปีใหม่ผมมีเวลาไปเรียนคอร์ส The Ultimate Go Series ของพี่ยอดซึ่งจากใจคนที่ดู Go อยู่ห่างๆ ผมบอกได้เลยว่าคอร์สนี้สอนได้ครบเครื่องมากในเวลาไม่กี่ชั่วโมง แต่นั่นไม่ใช่ประเด็นครับ ประเด็นคือตอนคอร์ส 2 ที่กำลังเรียนเรื่อง API พี่ยอดใช้ HTTP Client ที่ร...

Jan 9, 20223 min read102
ยิง request แบบไม่มีสะดุดด้วย emacs REST client

[Book Review] The Phoenix Project เพราะ IT Operation ก็ไม่ต่างกับ Production Plant

ผมรู้จัก The Phoenix Project มานานมากและมันวนเวียนมาเกือบจะได้อ่านหลายรอบ จนกระทั่งเมื่อประมาณกลางเดือนที่แล้ว ผมเพิ่งได้มีโอกาสจะหยิบมันขึ้นมาอ่าน และตั้งแต่ตอนนั้นผมแทบจะวางไม่ลงเลยตลอดระยะเวลาเกือบหนึ่งเดือนที่ผ่านมา และผมแนะนำให้กับทุกคนที่ทำงานใ...

Dec 20, 20214 min read456
[Book Review] The Phoenix Project เพราะ IT Operation ก็ไม่ต่างกับ Production Plant

Emacs diary EP2: สภาพแวดล้อม

ผมไม่รู้ว่าจะมีใครมาใช้ emacs แบบ เป็น text-editor ตัวแรกเลยมั้ย ผมไม่ใช่คนนึงแน่ๆ ซึ่งพอเราเปลี่ยนเครื่องมือ มันจะอารมณ์เดียวกับเราเปลี่ยนภาษา Programming ที่เราเขียนทุกวันเลย เพราะสิ่งแรกๆ ที่เราพยายามทำคือ "เราพยายามจะทำสิ่งเดียวกับที่เราเคยทำได้จ...

Dec 4, 20214 min read98
Emacs diary EP2: สภาพแวดล้อม

เรื่องประหลาดๆ ของการนับปีใน Java

ลืมหัวข้อ blog นี้ไปก่อนนะครับ แล้วลองพยายามเดาดูว่าโค้ด Java ข้างล่างนี้พยายามทำอะไร มันก็ดูตรงไปตรงมาใช่มั้ยครับ แล้วถ้ารันออกมาสมมติวันนี้วันที่ 1 ธันวาคม 2021 สิ่งที่ function นี้คืนกลับมาก็ เป็น 2020-12-01 ใช่มั้ยครับ ถ้าเราเขียน Unit test ไว้ ...

Dec 1, 20212 min read96
เรื่องประหลาดๆ ของการนับปีใน Java

Make Time | โลกมันวุ่นวาย แต่เรายังหาความสงบได้นะ

เคยมีความรู้สึกแบบนี้มั้ยครับ อารมณ์ประมาณว่า อยากจะเรียนคอร์สนั้นที่ซื้อไว้ตั้งนานแล้วให้จบ อยากจะอ่านหนังสือเล่มที่ซื้อเมื่อเดือนก่อน อยากจะทำ side project ที่คิดไว้ให้ออกมาเป็นรูปเป็นร่าง อยากจะมีทำให้ความสัมพันธ์ในชีวิตกับคนที่เราแคร์ดีขึ้น อะไรแ...

Nov 18, 20212 min read144
Make Time | โลกมันวุ่นวาย แต่เรายังหาความสงบได้นะ
P

Python by Cow

22 posts