- Published on
EKS ALB Ingress에서 504 Idle timeout만 반복될 때
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버는 살아 있고 Pod도 Ready인데, 클라이언트 요청만 504(Idle timeout) 으로 끊기는 상황은 EKS에서 자주 겪는 장애 패턴입니다. 특히 AWS Load Balancer Controller(ALB Ingress)를 쓰는 경우, 504는 “애플리케이션이 느리다”라는 단일 원인보다 연결/응답의 흐름 어디선가 ‘응답이 안 오거나(또는 못 받거나)’, ‘연결이 유지되지 못하는’ 문제를 의미하는 경우가 많습니다.
이 글은 ALB -> Target Group -> Node/Pod -> App 경로를 기준으로, 504(Idle timeout)가 반복될 때 빠르게 원인을 좁히는 체크리스트와 재현/확인 명령을 제공합니다.
> 관련 글도 함께 보면 원인 분리 속도가 빨라집니다: EKS ALB Ingress 504인데 Pod는 정상일 때
1) 먼저 “어떤 504인가”를 로그로 확정하기
ALB Ingress에서 504가 뜬다고 해서 항상 ALB idle timeout(기본 60s)만의 문제는 아닙니다. 우선 ALB 액세스 로그 또는 Ingress Controller 로그로 504의 성격을 확정해야 합니다.
1-1. ALB Access Log에서 확인할 필드
ALB Access Log를 켜고, 아래 필드를 봅니다.
elb_status_code: 504인지target_status_code: 타겟(파드/노드)에서 반환한 코드가 있는지request_processing_time/target_processing_time/response_processing_time
해석 예시:
elb_status_code=504이고target_status_code=-이면: ALB가 타겟과 HTTP 응답을 주고받지 못한 케이스(연결/라우팅/보안그룹/헬스/네트워크)elb_status_code=504이고target_status_code=200같은 값이 찍히면: ALB에서 클라이언트로 보내는 과정에서 문제(대개 드묾) 또는 로그 해석/타임라인 확인 필요target_processing_time이 60초 근처에서 끊기면: ALB idle timeout에 걸릴 가능성
1-2. Ingress 이벤트/컨트롤러 로그
AWS Load Balancer Controller가 타겟그룹/리스너 규칙을 제대로 만들었는지부터 확인합니다.
kubectl -n kube-system logs deploy/aws-load-balancer-controller --tail=200
kubectl describe ingress -n <ns> <ingress-name>
kubectl get events -n <ns> --sort-by=.lastTimestamp | tail -n 30
여기서 흔한 힌트:
- 타겟그룹 생성은 됐는데 Target 등록이 불안정
- 헬스체크 경로/포트 불일치
- Ingress annotation 충돌
2) 504(Idle timeout) “반복”의 3대 원인 지도
현장에서 반복적으로 맞닥뜨리는 원인을 크게 3개로 묶을 수 있습니다.
- 진짜로 응답이 60초(또는 설정값) 이상 걸린다: 앱/DB/외부 API 지연, 스트리밍 미지원, long polling
- 응답은 빠른데 중간에서 끊긴다: keep-alive/HTTP2/프록시 버퍼링, connection draining, readiness/endpoint 변동
- ALB가 타겟에게 도달하지 못한다: SG/NACL/라우팅, NodePort/TargetType 설정, health check 실패
이제부터는 “ALB 설정 → 타겟그룹 → 쿠버네티스 서비스/파드 → 애플리케이션” 순서로 좁혀갑니다.
3) ALB Idle timeout을 ‘증상 완화’로만 먼저 조정하기
ALB의 기본 idle timeout은 60초입니다. 요청이 60초를 넘는다면 504가 납니다. 다만 timeout을 늘리는 건 근본 해결이 아니라, 원인 진단을 위한 임시 조치 또는 정상적인 장기 요청을 지원하기 위한 설계 변경의 일부여야 합니다.
3-1. Ingress annotation으로 idle timeout 변경
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api
namespace: prod
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=180
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 80
- 늘렸는데도 정확히 180초에서 끊기면: 진짜로 응답이 안 오고 있다(앱/다운스트림)
- 늘렸더니 해결되면: 장기 요청이 정상 요구사항인지 재검토 (비동기/큐/웹훅/폴링으로 바꾸는 게 보통 더 안전)
4) Target Group 레벨: 타겟 타입, 포트, 헬스체크부터 확인
ALB Ingress에서 504가 반복될 때, 실제로는 타겟그룹이 Pod로 안정적으로 라우팅하지 못하는 케이스가 매우 많습니다.
4-1. target-type(Instance vs IP) 불일치
target-type: instance: ALB가 노드(NodePort) 로 보냄target-type: ip: ALB가 Pod IP 로 직접 보냄 (보통 EKS에서 선호)
Ingress에 다음을 명시하고, 서비스 타입/포트가 그에 맞는지 확인합니다.
metadata:
annotations:
alb.ingress.kubernetes.io/target-type: ip
점검 포인트:
instance인데 Service가 NodePort가 아니거나, 보안그룹에서 NodePort 대역이 막혀 있으면 504가 반복될 수 있습니다.ip인데 Pod 보안그룹/네트워크 정책/서브넷 라우팅이 꼬이면 역시 504가 납니다.
4-2. 헬스체크 경로/포트/성공코드
헬스체크가 “겉보기엔 통과”해도, 실제 트래픽 경로와 다르면 장애가 납니다. 예를 들어 앱은 /healthz는 즉시 200이지만, 실제 API는 DB 커넥션 풀 고갈로 60초 이상 대기하는 상황이 가능합니다.
Ingress annotation 예시:
metadata:
annotations:
alb.ingress.kubernetes.io/healthcheck-path: /healthz
alb.ingress.kubernetes.io/healthcheck-port: traffic-port
alb.ingress.kubernetes.io/success-codes: "200-399"
AWS 콘솔에서 Target Group의 Healthy/Unhealthy 변동, Health check timeout, Interval, Healthy threshold도 함께 확인하세요.
5) Kubernetes Service/Endpoint: “라우팅 대상이 계속 바뀌는지” 확인
504가 “간헐적”이 아니라 “반복”이라도, 실제로는 특정 Pod로만 가면 504가 발생하고 다른 Pod는 정상인 경우가 많습니다. 이때는 Endpoints를 보면 힌트가 나옵니다.
kubectl -n prod get svc api-svc -o wide
kubectl -n prod get endpoints api-svc -o yaml
kubectl -n prod get pod -l app=api -o wide
점검 포인트:
- endpoints에 Pod IP가 기대한 만큼 들어있는지
- readinessProbe가 자주 실패해서 endpoints에서 빠졌다/붙었다 하는지
- 특정 노드에만 Pod가 몰려 있고 그 노드만 네트워크/CPU 압박이 있는지
5-1. readinessProbe가 “가짜로 건강”할 때
readiness가 단순히 프로세스 살아있음만 체크하면, 실제 요청 처리 능력(예: DB 연결 가능, 큐 지연 없음)을 반영하지 못합니다.
- readiness는 의존성(주요 DB/캐시) 연결 가능 여부를 포함시키는 것이 504 반복을 줄이는 데 효과적입니다.
- 단, 의존성 장애 시 트래픽을 모두 차단할지(Fail Closed) 서비스 특성에 따라 조정해야 합니다.
6) 애플리케이션 레벨: “응답을 안 보내는” 전형적 패턴
ALB idle timeout은 결국 “응답이 늦다”로 귀결될 때가 많습니다. 하지만 원인은 애플리케이션 내부에서 더 구체적입니다.
6-1. 서버/프레임워크의 keep-alive/timeout 불일치
대표적으로 다음 불일치가 문제가 됩니다.
- ALB idle timeout(예: 60s) < 앱 응답 시간
- 앱 서버 keep-alive가 너무 짧아 중간에 커넥션이 끊김
- 프록시(Nginx/Envoy)
proxy_read_timeout이 ALB보다 짧음
Nginx를 사이드카/인그레스 뒤에 두는 경우 예시:
location / {
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_read_timeout 180s;
proxy_send_timeout 180s;
proxy_connect_timeout 5s;
proxy_pass http://127.0.0.1:8080;
}
6-2. DB 지연/락/커넥션 풀 고갈
요청이 특정 쿼리에서 멈추면, ALB는 “서버가 응답 없음”으로 보고 504를 내보냅니다. 특히 다음 상황에서 60초 근처로 딱딱 끊기는 패턴이 나옵니다.
- 커넥션 풀 max 도달 → 대기열에서 블로킹
- 락 경합/슬로우 쿼리 → API 응답 지연
- autovacuum/IOPS 폭증 등으로 DB가 순간적으로 느려짐
DB 이슈가 의심되면 아래 글의 방식으로 원인 분리를 병행하세요.
6-3. 외부 API 호출이 길어지는 경우(다운스트림 타임아웃)
애플리케이션이 외부 API를 호출하는데, 그쪽 타임아웃이 길거나 무한 재시도면 결국 ALB idle timeout으로 끊깁니다.
- 서버 내부 타임아웃(예: 10s) + 재시도(예: 2회)처럼 총 상한을 명확히 두세요.
- 클라이언트/서버/다운스트림 타임아웃을 “짧은 쪽이 이긴다”는 원칙으로 정렬합니다.
7) 재현과 분리: ALB를 우회해서 Pod까지 직접 때려보기
ALB에서만 504가 나오는지, Pod 자체가 느린지 분리해야 합니다.
7-1. Pod로 직접 요청(포트포워드)
kubectl -n prod port-forward deploy/api 18080:8080
# 다른 터미널
curl -v --max-time 120 http://127.0.0.1:18080/api/slow
- 포트포워드도 60초 이상 걸리면: 앱/DB/외부 API 문제
- 포트포워드는 빠른데 ALB에서만 504면: ALB/타겟그룹/네트워크/프록시 계층 문제
7-2. 클러스터 내부에서 Service로 요청
kubectl -n prod run -it --rm net-debug \
--image=curlimages/curl:8.5.0 -- sh
# pod 안에서
curl -v --max-time 120 http://api-svc.prod.svc.cluster.local/api/slow
이 테스트는 kube-proxy/Service 라우팅/네트워크 정책까지 포함합니다.
8) 자주 놓치는 설정: Pod 종료/롤링 업데이트 중 504
504가 배포 시점에만 반복되거나, 특정 시간대에 몰리면 종료 처리(graceful shutdown) 와 드레이닝을 의심하세요.
체크리스트:
terminationGracePeriodSeconds가 충분한가- 애플리케이션이 SIGTERM을 받고도 기존 요청을 끝까지 처리하는가
- readinessProbe가 종료 직전에 빠르게 false로 전환되는가
예시(종료 훅으로 드레이닝 시간을 확보):
spec:
terminationGracePeriodSeconds: 60
containers:
- name: api
image: your-api:1.2.3
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
이때 핵심은 sleep 자체가 아니라, 종료 직전 새로운 요청 유입을 차단(readiness fail) 하고 기존 요청을 마무리할 시간을 주는 것입니다.
9) 결론: “60초”에 집착하지 말고 경로를 쪼개라
EKS ALB Ingress에서 504(Idle timeout)가 반복될 때의 정석은 다음 순서입니다.
- ALB Access Log로 504의 형태를 확정(타겟 응답이 있었는지)
- Target Group의 target-type/포트/헬스체크가 실제 트래픽 경로와 일치하는지 확인
- Service/Endpoints 변동, readinessProbe의 진짜 의미 점검
- Pod 직격(포트포워드/클러스터 내부 curl) 으로 ALB 계층을 분리
- 앱 내부에서는 DB/외부 API/커넥션 풀/타임아웃 정렬을 확인
idle timeout을 늘리는 것은 임시 처방일 수 있지만, 위 순서로 원인을 분해하면 “왜 504가 반복되는지”가 결국 숫자(로그/지표)로 드러납니다.
추가로, 504인데 Pod는 정상(Ready)처럼 보이는 전형적 함정을 더 깊게 다룬 글은 아래를 참고하세요.