- Published on
EKS ALB Ingress 504(5xx) 간헐 발생 원인·해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
EKS에서 ALB Ingress(= AWS Load Balancer Controller가 만드는 ALB)를 쓰다 보면 “평소엔 잘 되는데” 특정 시간대나 특정 Pod로 라우팅될 때만 504(또는 502/503) 같은 5xx가 간헐적으로 튀는 문제가 자주 나옵니다. 이 유형은 단일 설정 오타보다 타임아웃/헬스체크/드레인/네트워크/DNS/애플리케이션 리소스가 결합된 경우가 많아서, 원인별로 신호를 분리해 빠르게 좁혀야 합니다.
이 글은 “ALB가 504를 냈다”를 출발점으로, 어떤 로그/메트릭을 먼저 보고, 어떤 설정을 어떻게 바꾸면 재발을 막을 수 있는지 실전 관점으로 정리합니다.
1) 504/5xx의 의미를 먼저 분해하기
ALB Ingress에서의 5xx는 크게 두 갈래입니다.
- ALB가 클라이언트에게 5xx를 응답: 대상(Target)과의 통신/응답이 문제
- 백엔드가 5xx를 응답했고 ALB는 전달만: 애플리케이션 에러
특히 504는 “대상으로부터 제때 응답을 못 받음” 쪽이 대부분입니다. 하지만 “제때”의 기준이 여러 층에 존재합니다.
- (A) ALB Idle timeout (기본 60s)
- (B) 애플리케이션/프레임워크 타임아웃 (예: Uvicorn/Gunicorn, Node/Java 서버, DB 클라이언트)
- (C) Kubernetes readiness/liveness, terminationGracePeriod, preStop
- (D) Target Group health check (경로, 성공 코드, 타임아웃, interval)
- (E) 네트워크 레벨: Security Group/NACL/노드/ENI/CNI 이슈
- (F) DNS/서비스 디스커버리: CoreDNS 간헐 실패로 upstream 호출이 느려짐
“간헐적”이라는 단서는 보통 (C)(D)(F) 또는 특정 Pod/노드에만 발생하는 (E)(리소스)에서 많이 나옵니다.
2) 가장 먼저 확인할 것: ALB Access Log로 504의 주체 판단
가능하면 ALB Access Log를 S3로 켜고, 504가 발생한 요청의 필드를 봅니다.
elb_status_code: ALB가 클라이언트에 준 코드target_status_code: 대상이 준 코드(대상이 응답을 못 하면-)request_processing_time,target_processing_time,response_processing_time
판단 규칙(실무에서 유용한 패턴):
elb_status_code=504+target_status_code=-→ 대상과 연결/응답 자체가 안 됨(드레인, 네트워크, 타임아웃)elb_status_code=502/503+target_status_code=-→ 연결 실패/대상 없음/헬스체크 실패elb_status_code=200인데 사용자 체감이 끊김 → 클라이언트/중간 프록시 타임아웃 가능
ALB 로그를 못 켠 상태라면, 최소한 CloudWatch의 ALB 메트릭을 봅니다.
HTTPCode_Target_5XX_CountHTTPCode_ELB_5XX_CountTargetResponseTimeHealthyHostCount
ELB_5XX가 오르면 로드밸런서 레벨, Target_5XX가 오르면 애플리케이션 레벨 가능성이 큽니다.
3) 원인 1: ALB Idle timeout(60s) vs 앱 처리시간 불일치
가장 흔한 504는 단순합니다.
- 요청이 60초(기본 idle timeout) 를 넘는 순간
- ALB가 연결을 끊고 504를 반환
특히 다음 케이스에서 자주 터집니다.
- LLM/리포트/배치성 API처럼 처리 시간이 들쭉날쭉
- SSE/스트리밍/롱폴링에서 버퍼링/프록시 설정이 맞지 않음
스트리밍/프록시 환경에서 끊김을 다룬 체크리스트는 아래 글도 함께 보면 좋습니다.
해결: ALB idle_timeout 조정(ingress annotation)
AWS Load Balancer Controller에서 ALB 속성은 annotation으로 설정합니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=180
spec:
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: app-svc
port:
number: 80
주의할 점:
- idle timeout을 늘리는 것은 “진짜로 오래 걸리는 요청”이 있을 때만. 무작정 늘리면 느린 요청이 자원을 더 오래 점유합니다.
- 앱 서버의 keep-alive/worker timeout도 함께 정합성을 맞춰야 합니다.
4) 원인 2: Target Group health check 설정 불일치(간헐 503/504 유발)
헬스체크가 실제 앱 특성과 맞지 않으면, 특정 Pod가 헬시에서 언헬시로 흔들리며 간헐 오류가 납니다.
대표 패턴:
/health가 DB/외부 API까지 확인 → 순간적인 의존성 지연이 헬스체크 실패로 전이- 헬스체크 timeout/interval이 너무 공격적
- 성공 코드 범위가 맞지 않음(예: 204 반환인데 200만 허용)
해결: 헬스체크는 “가볍고 결정적”이어야 함
- readiness와 ALB 헬스체크는 가능하면 가벼운 엔드포인트(프로세스/이벤트루프 살아있음 정도)
- 의존성(DB 등)은 별도의
/ready에서 확인하고, 트래픽 유입 여부는 readiness로 제어
Ingress 예시:
metadata:
annotations:
alb.ingress.kubernetes.io/healthcheck-path: /healthz
alb.ingress.kubernetes.io/healthcheck-interval-seconds: "15"
alb.ingress.kubernetes.io/healthcheck-timeout-seconds: "5"
alb.ingress.kubernetes.io/healthy-threshold-count: "2"
alb.ingress.kubernetes.io/unhealthy-threshold-count: "2"
alb.ingress.kubernetes.io/success-codes: "200-399"
추가로, HealthyHostCount가 순간적으로 0 또는 급감하는지 보면 헬스체크 흔들림을 빠르게 확인할 수 있습니다.
5) 원인 3: Pod 종료/롤링업데이트 중 드레인 미흡(간헐 502/504)
“간헐”의 강력한 단서 중 하나는 배포/스케일링/노드 교체 타이밍에만 5xx가 늘어나는 경우입니다.
문제 메커니즘:
- Pod가 SIGTERM을 받고 종료 중인데
- 아직 Target Group에서 완전히 빠지지 않았거나
- 앱이 새 연결을 받지 못하는 상태에서 요청이 들어와 504/502가 발생
해결 1: terminationGracePeriod + preStop + readiness 연동
아래는 전형적인 패턴입니다.
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
terminationGracePeriodSeconds: 60
containers:
- name: app
image: your/app:tag
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
failureThreshold: 1
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 15"]
핵심은:
- SIGTERM 직후 readiness를 실패시키거나(앱에서 구현)
- preStop으로 타겟에서 빠질 시간을 벌어 새 요청 유입을 줄이는 것
해결 2: Target Group deregistration delay 확인
ALB Target Group에는 deregistration delay(드레인 시간)가 있습니다. 앱의 종료/요청 처리 시간과 맞춰야 합니다.
- 긴 요청이 있으면 delay를 늘리고
- 짧은 요청만 있으면 너무 길게 잡지 않습니다.
(콘솔 또는 IaC로 설정. Controller에서도 일부 속성은 annotation으로 제어 가능)
6) 원인 4: 노드/Pod 리소스 압박으로 “특정 Pod만” 느려짐
간헐 504는 종종 느린 Pod로 라우팅될 때만 발생합니다.
체크 포인트:
- HPA가 늦게 반응하거나, scale-out 직후 cold start로 느림
- CPU throttling, 메모리 압박(OOM 직전), GC stop-the-world
- 워커 수 부족(예: Gunicorn worker 1개로 장시간 블로킹)
확인 방법
kubectl top pod -n <ns>로 순간 피크 확인- 애플리케이션 지표(P99 latency, queue length)
- ALB
TargetResponseTimeP95/P99가 튀는지
해결 방향
- requests/limits 재설계(특히 CPU limit로 인한 throttling 주의)
- 워커/스레드/이벤트루프 모델 점검
- HPA 스케일 정책을 “피크 전에” 반응하도록 조정
7) 원인 5: CoreDNS/DNS 간헐 실패 → upstream 호출 지연 → 504
백엔드가 내부적으로 다른 서비스(예: RDS, 외부 API, 내부 마이크로서비스)를 호출하는데, DNS가 간헐적으로 느려지면 애플리케이션은 대기하다가 결국 타임아웃에 걸립니다. 이때 ALB는 “응답이 없다”로 504를 낼 수 있습니다.
이 패턴은 로그에 흔히 이렇게 남습니다.
Temporary failure in name resolutioni/o timeout(특히 첫 연결에서)
EKS에서 CoreDNS가 “정상처럼 보이는데” DNS가 간헐 실패하는 케이스는 아래 글을 같이 참고하면 진단이 빨라집니다.
빠른 재현/검증용 명령
문제 시간대에 Pod 안에서 DNS/연결을 직접 확인합니다.
kubectl run -it --rm dns-test --image=busybox:1.36 --restart=Never -- sh
# Pod 내부
nslookup your-upstream.default.svc.cluster.local
wget -S -O- http://your-upstream.default.svc.cluster.local:8080/healthz
해결은 CoreDNS 리소스/노드 로컬 DNS 캐시/네트워크 경로/업스트림 리졸버 문제 등으로 갈리므로, “ALB 문제”로만 좁히지 않는 게 중요합니다.
8) 원인 6: 보안그룹/네트워크 정책/CNI 이슈로 특정 경로만 드랍
ALB → Node/Pod 경로는 보안그룹, 서브넷 라우팅, CNI(aws-vpc-cni), NetworkPolicy(사용 시) 등 여러 레이어를 통과합니다. 간헐 504는 “패킷 드랍/재전송”에서도 발생합니다.
특히 다음 상황을 의심합니다.
- 특정 노드에만 몰릴 때 실패
- 재시도하면 바로 성공
target_status_code=-가 자주 보임(연결 실패)
네트워크 관점에서 “Pod는 뜨는데 트래픽이 0/간헐”을 빠르게 진단하는 흐름은 아래 글이 도움이 됩니다.
9) 실전 트러블슈팅 순서(체크리스트)
간헐 504/5xx는 “추측”보다 “증거 기반 분기”가 훨씬 빠릅니다. 아래 순서로 보면 평균적으로 시간을 가장 아낍니다.
1) ALB Access Log/메트릭으로 범주화
ELB_5XXvsTarget_5XXtarget_status_code가-인지TargetResponseTime이 튀는지
2) 배포/스케일 이벤트와 상관관계 확인
- 5xx 스파이크 시점에 Deployment rollout, HPA scale, 노드 드레인 있었는지
3) 타임아웃 정합성 점검
- ALB idle timeout
- 앱 서버 타임아웃(keepalive, request timeout)
- upstream 클라이언트 타임아웃(DB/HTTP)
4) 헬스체크/레디니스 설계 재검토
- 헬스체크 엔드포인트가 무겁지 않은지
- 성공 코드, timeout/interval 적절한지
5) 특정 Pod/노드 편향 확인
- 에러가 특정 target에 몰리는지
- CPU throttling/메모리 압박/GC
6) DNS/네트워크 계층 확인
- CoreDNS 지연/실패
- SG/NACL/NetworkPolicy/CNI
10) “해결됐는지”를 검증하는 방법
수정 후에는 단순히 에러가 안 보이는 것보다, 재발 조건에서 지표가 개선되는지 확인해야 합니다.
- 동일 트래픽에서
TargetResponseTimeP95/P99 감소 HealthyHostCount안정화(흔들림 제거)- 롤링 업데이트 중 5xx 스파이크가 사라짐
- 애플리케이션 로그에서 타임아웃/리졸브 에러 감소
부하 테스트(짧게라도)로 “60초 근처” 같은 경계 조건을 다시 밟아보면, idle timeout/워커/드레인 문제는 금방 재현됩니다.
마무리
EKS의 ALB Ingress 504/5xx 간헐 문제는 ALB 자체 결함이라기보다, 타임아웃 정합성 + 헬스체크 설계 + 종료/드레인 + 내부 DNS/네트워크 + 리소스가 맞물려 생기는 경우가 대부분입니다.
가장 중요한 것은 (1) ALB 로그/메트릭으로 “누가 5xx를 만들었는지”를 먼저 나누고, (2) 배포/스케일/종속성 지연과의 상관관계를 통해 원인을 좁힌 뒤, (3) 타임아웃/헬스체크/드레인 설계를 체계적으로 맞추는 것입니다.
이 흐름대로 접근하면 “가끔 난다”는 애매한 문제도 대개 1~2회 관측으로 원인을 특정할 수 있습니다.