Published on

EKS Pod→ElastiCache Redis 10분 타임아웃 진단법

Authors

서버가 멀쩡해 보이는데, 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 미설정

가장 흔한 실전 패턴은 이겁니다.

  1. 앱이 Redis 커넥션을 풀에 넣고 idle로 둠
  2. 중간 장비(NAT/방화벽/프록시)가 600초 idle로 세션 정리
  3. 앱은 커넥션이 살아있다고 믿고 재사용
  4. 첫 요청에서 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분이 반복되면, 우선순위는 다음처럼 가져가면 됩니다.

  1. 중간 장비 idle timeout 또는 keepalive 부재를 1차로 의심
  2. 그다음 SG/NACL/라우팅을 Flow Logs로 증거 기반 확인
  3. 마지막으로 DNS/클러스터 장애조치/리소스 이슈를 정리

이 순서대로 하면 “Redis 튜닝”으로 삽질하지 않고, 보통 1~2시간 안에 원인을 좁힐 수 있습니다.

원하시면 사용 중인 언어/클라이언트(ioredis, node-redis, Jedis/Lettuce, go-redis 등)와 네트워크 구성(NAT 여부, 서비스 메시 여부, VPC/서브넷 구조)을 알려주시면, 10분에 딱 맞는 원인 후보를 2~3개로 더 좁혀서 체크 명령과 설정값까지 같이 제시하겠습니다.