- Published on
EKS Pod→ElastiCache Redis 10분 타임아웃 진단법
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 멀쩡해 보이는데, EKS Pod에서 ElastiCache Redis로 붙는 트래픽만 일정 시간(특히 정확히 10분) 지나면 타임아웃이 나는 케이스는 생각보다 자주 나옵니다.
이 글은 “Redis가 느리다”가 아니라 네트워크/연결 상태가 10분 경계에서 끊기는 유형을 대상으로, 재현 → 관측 → 원인 분리 → 영구 조치까지 한 번에 정리합니다.
> 전제: ElastiCache for Redis는 VPC 내부 엔드포인트(프라이빗 IP)이며, EKS는 동일/피어링/트랜짓게이트웨이 등으로 라우팅이 연결되어 있다고 가정합니다.
1) 10분 타임아웃이 의미하는 것
10분(600초)은 AWS/쿠버네티스/네트워크 장비에서 자주 등장하는 idle timeout 기본값과 겹칩니다.
대표적으로 다음 범주가 있습니다.
- 중간 장비의 Idle Timeout
- NLB/ALB, Envoy/프록시, NAT Gateway, 방화벽 어플라이언스, 서비스메시 사이드카 등
- “연결은 살아있다고 생각했는데” 중간에서 세션을 정리해버림
- 클라이언트 커넥션 풀/keepalive 부재
- redis client가 TCP keepalive를 안 켜서 idle 상태의 소켓이 중간에서 정리됨
- 다음 요청에서 재사용하려다 timeout
- DNS/엔드포인트 변경과 캐시
- ElastiCache(특히 클러스터 모드/리더 교체/노드 교체)에서 엔드포인트가 바뀌거나, 클라이언트가 오래된 IP를 붙잡는 경우
핵심은 “Redis가 처리 못 해서 10분 걸리는” 게 아니라, 연결/세션이 10분 경계에서 끊겨 다음 요청이 막히는 패턴인지 먼저 확인하는 것입니다.
2) 증상 분류: ‘요청이 10분 걸림’ vs ‘10분마다 끊김’
먼저 애플리케이션 로그에서 다음을 구분합니다.
- A형:
GET/SET같은 단일 명령이 600초 동안 대기 → 네트워크 블랙홀/패킷 드롭/재전송 누적 가능 - B형: 평소엔 빠르다가 idle 후 첫 요청만 timeout → idle timeout/keepalive 이슈 가능
- C형: 특정 Pod/Node에서만 재현 → 노드 보안그룹, 라우팅, CNI, 노드 로컬 방화벽/iptables 가능
이 분류가 되면 진단 시간이 절반으로 줄어듭니다.
3) 가장 먼저 할 3가지(재현·관측·경로 확인)
3.1 Pod에서 Redis 엔드포인트를 ‘직접’ 때려 보기
애플리케이션을 통하지 말고, 문제 Pod 안에서 TCP 레벨과 Redis 프로토콜 레벨을 분리합니다.
# 1) DNS 확인
kubectl -n <ns> exec -it <pod> -- sh -lc 'getent hosts <redis-endpoint>'
# 2) TCP 연결 확인(포트 오픈)
kubectl -n <ns> exec -it <pod> -- sh -lc 'nc -vz <redis-endpoint> 6379'
# 3) Redis ping(가능하면 redis-cli)
kubectl -n <ns> exec -it <pod> -- sh -lc 'redis-cli -h <redis-endpoint> -p 6379 PING'
nc는 “포트가 열려있나”만 봅니다.redis-cli는 “프로토콜 레벨에서 정상인가”를 봅니다.
여기서 이미 10분 대기/타임아웃이 재현되면 애플리케이션 이슈 가능성이 크게 줄어듭니다.
3.2 Node 단에서 경로 확인(같은 노드/다른 노드 비교)
Pod가 올라간 노드에서 동일 테스트를 해보면, Pod 네트워크(CNI) vs 노드/서브넷 경로를 분리할 수 있습니다.
# 노드에 접속(SSM이나 bastion 등) 후
getent hosts <redis-endpoint>
nc -vz <redis-endpoint> 6379
- 노드에서는 되고 Pod에서만 안 되면: CNI/네트워크 정책/Pod SG(사용 중이라면)/iptables 쪽을 의심합니다.
- 특정 노드에서만 안 되면: 노드 SG/라우팅/서브넷 NACL/노드 자체 문제를 의심합니다.
CNI/노드 네트워크 계열은 아래 글의 점검 흐름과도 맞닿습니다: Terraform apply 후 EKS 노드 NotReady - CNI·IRSA·보안그룹 점검
3.3 “정확히 10분”을 계측으로 확인
애플리케이션에서 타임아웃이 발생한 시각과, Redis 쪽에서 연결이 끊긴 시각(CloudWatch/Redis slowlog/engine log)을 대조합니다.
- 애플리케이션: connect timeout / command timeout / socket timeout 로그
- Redis:
connected_clients,rejected_connections,sync_partial_err,master_link_down_since_seconds등 지표
ElastiCache Redis는 OS 레벨 로그를 직접 못 보므로, CloudWatch Metrics + 애플리케이션 관측이 핵심입니다.
4) 네트워크 계층에서 자주 터지는 원인 6가지
4.1 보안그룹(SG) 인바운드/아웃바운드 불일치
ElastiCache SG 인바운드에 “EKS 노드 SG” 또는 “Pod SG(보안그룹 for Pods)”가 열려 있어야 합니다.
체크리스트:
- ElastiCache SG inbound: TCP 6379 from (EKS node SG or Pod SG)
- EKS node SG outbound: TCP 6379 to ElastiCache SG/VPC CIDR
- NACL이 있다면 ephemeral port(1024-65535)도 고려(양방향)
특히 NACL은 stateless라서 인바운드만 열어도 안 되고, 리턴 트래픽 포트 범위를 막으면 “가끔 되다 끊김/재전송 후 타임아웃” 패턴이 나옵니다.
4.2 라우팅/서브넷 경계(피어링/TGW/다중 VPC)
EKS와 ElastiCache가 다른 VPC에 있거나 TGW/피어링이면,
- 양쪽 서브넷 라우팅 테이블에 상대 CIDR 경로가 있어야 하고
- 보안그룹 참조가 VPC 간에는 제한이 있을 수 있어 CIDR 기반 허용이 필요할 수 있습니다.
이 경우 “일부 노드만” 실패하는 패턴이 자주 나오니, 문제 Pod가 뜬 노드의 서브넷 라우팅 테이블부터 확인하세요.
4.3 중간 프록시/메시(Envoy, NLB, Sidecar) idle timeout
Pod→Redis 경로에 Envoy/프록시가 끼면 idle timeout 기본값이 60s~600s로 설정되어 있는 경우가 있습니다.
- 서비스 메시(mTLS)로 Redis를 감싸거나
- 내부 트래픽을 NLB로 우회시키거나
- egress gateway를 태우는 설계
라면, “10분 후 첫 요청이 죽는” 증상이 강하게 나타납니다.
이 주제는 egress 경로 추적 관점에서 아래 글의 접근이 도움이 됩니다: EKS에서 Pod egress만 502? Envoy/NLB 추적기
4.4 DNS 캐시/엔드포인트 변경(특히 클러스터 모드)
ElastiCache는 노드 교체/장애조치 시 IP가 바뀔 수 있습니다.
- 클라이언트가 엔드포인트를 한 번 resolve한 뒤 IP를 오래 캐시
- keepalive 없는 장기 연결이 끊기고, 재연결 시 오래된 IP를 계속 사용
이면 “주기적으로” 장애가 반복될 수 있습니다.
확인:
# Pod에서 주기적으로 resolve 결과가 바뀌는지
kubectl -n <ns> exec -it <pod> -- sh -lc 'for i in $(seq 1 20); do date; getent hosts <redis-endpoint>; sleep 30; done'
DNS TTL을 무시하는 라이브러리/런타임도 있으니(특히 JVM 계열), 애플리케이션 런타임의 DNS 캐시 정책도 같이 봅니다.
4.5 커넥션 풀 재사용 + TCP keepalive 미설정
가장 흔한 실전 패턴은 이겁니다.
- 앱이 Redis 커넥션을 풀에 넣고 idle로 둠
- 중간 장비(NAT/방화벽/프록시)가 600초 idle로 세션 정리
- 앱은 커넥션이 살아있다고 믿고 재사용
- 첫 요청에서 timeout (재전송/대기 후 실패)
해결은 크게 두 가지입니다.
- 클라이언트에서 TCP keepalive 활성화
- idle time보다 짧게 커넥션을 재활용/검증 (health check, test-on-borrow)
(예) Node.js ioredis keepAlive 설정
import Redis from 'ioredis';
const redis = new Redis({
host: process.env.REDIS_HOST,
port: 6379,
// TCP keepalive(초 단위가 아니라 ms)
keepAlive: 60_000,
// 연결/명령 타임아웃을 너무 길게 두지 말 것
connectTimeout: 5_000,
commandTimeout: 2_000,
// 장애 시 재시도 전략도 명확히
retryStrategy(times) {
return Math.min(times * 50, 1000);
},
});
(예) Java Lettuce TCP keepalive + timeout
RedisURI uri = RedisURI.Builder.redis(host, 6379)
.withTimeout(Duration.ofSeconds(2))
.build();
ClientOptions options = ClientOptions.builder()
.socketOptions(SocketOptions.builder()
.keepAlive(true)
.connectTimeout(Duration.ofSeconds(5))
.build())
.build();
RedisClient client = RedisClient.create(uri);
client.setOptions(options);
포인트는 “timeout을 늘려서 버티기”가 아니라 끊긴 커넥션을 빨리 감지하고 재연결하게 만드는 것입니다.
4.6 Pod/Node 리소스 이슈로 인한 네트워크 지연(간접 원인)
CPU throttling, 메모리 압박, 노드 디스크 압박은 네트워크 타임아웃처럼 보일 수 있습니다.
- GC stop-the-world로 인해 소켓 read가 늦어짐
- 노드가 불안정해 kube-proxy/cni가 흔들림
- 컨테이너가 Evicted/재시작 직전 상태에서 커넥션이 끊김
노드 상태가 의심되면 Eviction/디스크 압박도 같이 확인하세요: EKS에서 nodefs ImageGC로 Pod가 Evicted될 때
5) 패킷 드롭/블랙홀을 빠르게 확인하는 방법
5.1 VPC Flow Logs로 6379 트래픽이 REJECT인지 확인
VPC Flow Logs를 켜고, Redis 서브넷 ENI 또는 노드 ENI 기준으로 필터링합니다.
REJECT가 보이면 SG/NACL/라우팅 문제 가능성이 큼ACCEPT인데 앱은 timeout이면 중간 프록시/keepalive/클라이언트 쪽 가능성이 큼
Athena 쿼리 예시(컬럼은 설정에 따라 다름):
SELECT
from_unixtime(start) AS start_time,
srcaddr, dstaddr, srcport, dstport,
action, bytes, packets
FROM vpc_flow_logs
WHERE (dstport = 6379 OR srcport = 6379)
AND start > unix_timestamp(current_timestamp - interval '1' hour)
ORDER BY start DESC
LIMIT 200;
5.2 Pod에서 장시간 idle 후 첫 요청만 죽는지 실험
“idle timeout” 가설을 검증하는 가장 간단한 실험입니다.
# 1) 연결 후 대기
kubectl -n <ns> exec -it <pod> -- sh -lc 'redis-cli -h <redis-endpoint> -p 6379 PING; sleep 610; redis-cli -h <redis-endpoint> -p 6379 PING'
- 첫 PING은 성공, 610초 후 PING이 실패하면 idle timeout 계열 가능성이 큽니다.
- 단,
redis-cli는 매번 새 연결을 만들 수 있으니, 애플리케이션과 동일한 방식(풀/장기 연결)로 재현하는 테스트 코드가 더 정확합니다.
6) 실전 해결책: “10분”을 없애는 조치 우선순위
6.1 1순위: 클라이언트 keepalive + 짧은 타임아웃 + 재시도
- TCP keepalive 활성화
- connect/command timeout을 합리적으로(수 초 단위) 설정
- 재시도는 지수 백오프 + 상한(서킷브레이커)로 제어
이 조합이 가장 비용 대비 효과가 큽니다.
6.2 2순위: Redis 접근 경로 단순화(프록시/게이트웨이 제거)
Pod가 Redis로 갈 때
- egress gateway
- 내부 NLB
- L7 프록시
를 불필요하게 타고 있다면, VPC 내부 직접 연결로 단순화하세요. Redis는 L7 프록시를 태우는 순간 idle timeout/커넥션 관리 문제가 급증합니다.
6.3 3순위: SG/NACL/라우팅을 ‘양방향’으로 재검증
특히 NACL이 커스텀인 환경에서 “처음엔 되는데 가끔 10분쯤에 죽는다”가 나옵니다.
- 인바운드 6379만 열어놓고
- ephemeral return 포트를 막아
- SYN은 갔는데 응답이 안 돌아와 재전송 후 timeout
같은 현상이 발생합니다.
6.4 4순위: DNS 캐시 정책 조정(JVM 등)
JVM은 기본 DNS 캐시가 길게 잡히는 경우가 있어, 장애조치 후 오래된 IP를 계속 붙잡을 수 있습니다.
networkaddress.cache.ttl- 클라이언트의 endpoint refresh 설정
을 점검하세요.
7) 점검 체크리스트(현장에서 그대로 복붙용)
- 문제 패턴이 “idle 후 첫 요청”인지, “항상 600초 대기”인지 분류
- Pod/Node에서
getent hosts,nc,redis-cli로 직접 재현 - ElastiCache SG inbound에 EKS node SG/Pod SG가 허용되는지
- Node SG outbound 및 NACL(양방향, ephemeral) 확인
- VPC Flow Logs에서 6379 트래픽
REJECT여부 확인 - 경로에 Envoy/NLB/egress gateway 등 중간 장비가 있는지, idle timeout 값 확인
- Redis 클라이언트에 TCP keepalive, connect/command timeout, 재시도 정책 적용
- DNS 캐시/엔드포인트 변경(장애조치) 시나리오 점검
8) 마무리: 10분 타임아웃은 ‘대부분’ Redis 문제가 아니다
EKS Pod→ElastiCache Redis에서 정확히 10분이 반복되면, 우선순위는 다음처럼 가져가면 됩니다.
- 중간 장비 idle timeout 또는 keepalive 부재를 1차로 의심
- 그다음 SG/NACL/라우팅을 Flow Logs로 증거 기반 확인
- 마지막으로 DNS/클러스터 장애조치/리소스 이슈를 정리
이 순서대로 하면 “Redis 튜닝”으로 삽질하지 않고, 보통 1~2시간 안에 원인을 좁힐 수 있습니다.
원하시면 사용 중인 언어/클라이언트(ioredis, node-redis, Jedis/Lettuce, go-redis 등)와 네트워크 구성(NAT 여부, 서비스 메시 여부, VPC/서브넷 구조)을 알려주시면, 10분에 딱 맞는 원인 후보를 2~3개로 더 좁혀서 체크 명령과 설정값까지 같이 제시하겠습니다.