- Published on
EKS ALB Ingress 504인데 Pod는 정상일 때
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버는 살아있고(Pod Ready), Service도 붙어 있고, 애플리케이션 로그도 에러가 없는데… 외부에서만 504 Gateway Timeout이 난다면 대부분 ALB(또는 AWS Load Balancer Controller)가 타임아웃을 맞았거나, 타겟까지의 경로 중 어딘가에서 연결이 막히거나, 헬스체크/포트/프로토콜이 미묘하게 어긋난 상황입니다.
이 글은 “Pod는 정상인데 ALB Ingress만 504”일 때 흔한 원인을 **재현 가능한 증거(로그/이벤트/설정)**로 좁히는 순서대로 정리합니다. 504는 대체로 ALB가 타겟(노드/Pod/백엔드)로부터 제때 응답을 못 받았다는 뜻이므로, 핵심은 “ALB → 타겟” 구간을 분해해 보는 것입니다.
(유사하게 게이트웨이 계층에서 502/503/504가 섞여 보일 때의 접근법은 OpenAI Responses API 502 Bad Gateway 원인과 해결, OpenAI Responses API 504 Timeout 재현·해결도 참고하면 사고방식에 도움이 됩니다.)
1) 먼저 “어디서” 504가 나는지 확정하기
1-1. 브라우저/클라이언트가 아니라 ALB가 504를 냈는지
ALB가 만든 504라면 응답 헤더/바디에 ALB 특유의 형태가 나오거나, ALB Access Log에 기록됩니다.
- ALB Access Log 활성화(미활성이라면 먼저 켜는 게 좋습니다)
- CloudWatch가 아니라 S3에 떨어지는 로그에서 다음을 봅니다.
elb_status_code=504target_status_code가-이거나 비정상request_processing_time,target_processing_time,response_processing_time
target_processing_time이 길게 찍히면 “ALB→타겟 응답 대기”가 맞고, request_processing_time이 길면 “클라이언트→ALB” 구간 문제(거의 드묾)일 수 있습니다.
1-2. 같은 요청을 클러스터 내부에서 때려보기
외부에서만 504면 Ingress/ALB 레벨 문제일 확률이 큽니다. 내부에서 Service로 직접 호출해 비교합니다.
# 임시 디버그 파드
kubectl run -it --rm debug --image=curlimages/curl --restart=Never -- sh
# Service DNS로 호출
curl -sv http://my-svc.my-ns.svc.cluster.local:8080/healthz
- 내부 호출은 빠르게 200인데 외부만 504 → ALB/Ingress/보안그룹/타겟그룹/프로토콜/타임아웃 쪽으로 이동
- 내부에서도 느리거나 타임아웃 → 애플리케이션/리소스/의존성(DB, 외부 API) 문제 가능성
2) 가장 흔한 원인: TargetGroup 헬스체크는 “정상”, 실제 트래픽은 “막힘”
ALB가 타겟을 Healthy로 보는데도 실트래픽이 504면 다음 케이스가 많습니다.
2-1. 헬스체크 경로/포트는 열려 있는데 실제 엔드포인트가 느림
예: /healthz는 즉시 200, 하지만 /api는 DB 커넥션 풀 고갈로 40~60초 이상 걸림 → ALB idle timeout(기본 60s)과 충돌.
증거 수집
- 애플리케이션에서 요청별 latency 로그
- ALB access log의
target_processing_time이 60초 근처로 찍히는지
해결
- 느린 엔드포인트 최적화(쿼리/캐시/커넥션 풀)
- ALB idle timeout 조정(필요 시)
Ingress annotation 예시:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app
annotations:
alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=120
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-svc
port:
number: 8080
> 주의: 타임아웃을 올리는 건 “증상 완화”일 수 있습니다. 근본적으로는 백엔드가 60초 이상 응답이 늦어지는 이유를 잡아야 합니다.
2-2. readinessProbe는 통과하지만 실제로는 워커/스레드/이벤트루프가 막힘
Kubernetes의 Ready는 “요청을 처리할 수 있다”의 완전한 보장이 아닙니다. 예를 들어:
- Node.js/ Python async 서버에서 이벤트루프 블로킹
- gunicorn worker 수 부족 + 긴 요청
- JVM GC 스톱더월드
체크 포인트
- Pod CPU throttling (
kubectl top pod, HPA, requests/limits) - 애플리케이션 런타임 지표(P99 latency, worker busy)
3) 포트/프로토콜 불일치: HTTP로 때리는데 백엔드는 HTTPS(또는 그 반대)
ALB는 리스너(80/443)와 타겟그룹 프로토콜(HTTP/HTTPS)을 따로 가집니다. 여기서 어긋나면 헬스체크는 우연히 통과해도 실제 요청에서 504/502가 날 수 있습니다.
3-1. Service 포트와 targetPort, 컨테이너 리슨 포트 일치 확인
kubectl get svc my-svc -n my-ns -o yaml
kubectl get deploy my-deploy -n my-ns -o yaml | yq '.spec.template.spec.containers[].ports'
- Service
port: 80→targetPort: 8080 - 컨테이너가 실제로 8080에서 LISTEN하는지
Pod 내부에서 확인:
kubectl exec -n my-ns deploy/my-deploy -c app -- sh -lc 'netstat -lntp || ss -lntp'
3-2. ALB → 타겟 프로토콜 설정 확인
AWS Load Balancer Controller에서는 Ingress annotation으로 타겟 프로토콜을 지정합니다.
metadata:
annotations:
alb.ingress.kubernetes.io/backend-protocol: HTTP
# 또는 HTTPS
- 백엔드가 TLS를 직접 종단(HTTPS)하는데
HTTP로 보내면 연결은 되더라도 응답이 이상해질 수 있습니다. - 반대로 백엔드는 HTTP인데
HTTPS로 보내면 핸드셰이크 실패.
4) Security Group / NACL / Node SG: “ALB에서 노드(또는 Pod ENI)로”가 막힘
ALB Ingress가 504인데 Pod는 정상이라면, 의외로 네트워크 ACL/보안그룹 규칙이 원인인 경우가 많습니다.
4-1. instance target(노드포트) vs ip target(파드 IP)
AWS Load Balancer Controller는 타겟 타입을 instance 또는 ip로 씁니다.
instance: ALB → 노드의 NodePort로 트래픽ip: ALB → Pod IP(VPC CNI, security group for pods 사용 시 SG가 별도)
Ingress에서 확인:
metadata:
annotations:
alb.ingress.kubernetes.io/target-type: ip # 또는 instance
체크
instance면 Node SG에 NodePort 범위(기본 30000-32767) 인바운드가 ALB SG에서 허용돼야 합니다.ip면 Pod ENI/보안그룹(사용 중이라면)에 ALB SG 인바운드 허용이 필요합니다.
4-2. 빠른 진단: 타겟 그룹에서 Unhealthy 이유 확인
AWS 콘솔 Target Group → Targets → “Health status details”를 보면 Timeout/Health checks failed 같은 힌트를 줍니다.
하지만 “Healthy인데도 504”라면, 헬스체크 포트/경로만 열려 있고 실제 경로가 막혀 있을 가능성이 있어 실제 서비스 포트로의 SG 허용을 다시 봐야 합니다.
5) Ingress 리스너/규칙은 맞는데, Service 엔드포인트가 비어있는 경우
Pod가 Ready여도 Service selector가 틀리면 Endpoints가 없을 수 있습니다. 이 경우는 보통 503이 더 흔하지만, 구성에 따라 504처럼 보일 수 있어 같이 점검합니다.
kubectl get endpoints my-svc -n my-ns
kubectl describe svc my-svc -n my-ns
- Endpoints가
<none>면 selector/label 불일치 - Pod label과 Service selector를 비교
6) AWS Load Balancer Controller 이벤트/로그로 “컨트롤 플레인” 문제 배제
ALB/TargetGroup이 원하는 상태로 만들어졌는지, 리스너 규칙이 꼬이지 않았는지 확인합니다.
kubectl -n kube-system logs deploy/aws-load-balancer-controller --tail=200
kubectl describe ingress app -n my-ns
kubectl get ingress app -n my-ns -o yaml
Ingress 이벤트에 다음 류가 보이면 직접 원인입니다.
- target group 생성 실패
- listener rule 충돌
- subnets/SG 태그 문제
7) 타임아웃의 또 다른 축: 장시간 응답/스트리밍(SSE)과 ALB
SSE/롱폴링/대용량 응답처럼 TTFB가 길거나 중간에 데이터가 흐르지 않으면 idle timeout에 걸려 504가 납니다. LLM 스트리밍이나 서버 푸시를 붙였다면 특히 자주 터집니다.
- 해결 방향: 주기적 flush, 프록시 버퍼링/타임아웃 조정, keep-alive 튜닝
- 스트리밍/프록시 관점의 체크리스트는 LLM SSE 스트리밍 499 502 급증과 응답 끊김을 잡는 프록시 튜닝 체크리스트도 같이 보면 좋습니다.
8) 실전 “10분 컷” 체크리스트(우선순위)
8-1. 증상 분류
- 내부(Service 직접) 호출은 정상인가?
- ALB access log에서
elb_status_code=504가 찍히는가? target_processing_time이 60초 근처로 길게 찍히는가?
8-2. 설정/리소스 확인
- Ingress annotation:
target-type,backend-protocol,healthcheck-path,idle_timeout - Service:
port/targetPort, selector, endpoints 존재 - SG: ALB SG → (노드 NodePort 또는 Pod ENI) 인바운드 허용
- Pod 리소스: CPU throttling/worker 부족/DB 풀 고갈
8-3. 바로 써먹는 커맨드 묶음
# 1) 엔드포인트 존재 확인
kubectl get ep -n my-ns my-svc
# 2) Ingress 상세(이벤트 포함)
kubectl describe ingress -n my-ns app
# 3) 컨트롤러 로그
kubectl -n kube-system logs deploy/aws-load-balancer-controller --tail=300
# 4) 내부에서 서비스 호출
kubectl run -it --rm debug --image=curlimages/curl --restart=Never -- sh -lc \
'curl -sv http://my-svc.my-ns.svc.cluster.local:8080/healthz; echo; curl -sv http://my-svc.my-ns.svc.cluster.local:8080/api'
9) 결론: 504는 “Pod가 죽었다”가 아니라 “ALB가 기다리다 포기했다”
EKS에서 ALB Ingress 504를 만났을 때 Pod가 정상이라는 사실은 오히려 힌트입니다. 문제는 대개 다음 셋 중 하나로 귀결됩니다.
- 느린 응답(백엔드 처리/의존성 병목)으로 ALB idle timeout 초과
- 네트워크 경로 차단(SG/NACL/타겟 타입에 따른 포트 허용 누락)
- 포트/프로토콜 불일치(HTTP/HTTPS, Service port/targetPort, 헬스체크와 실트래픽의 불일치)
ALB access log와 내부 curl 테스트로 “ALB→타겟” 구간을 증거 기반으로 쪼개면, 504는 생각보다 빠르게 잡힙니다.