- Published on
EKS ALB Ingress 502 target timeout 원인·해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버리스나 단순 NLB 환경과 달리, EKS에서 ALB Ingress(= AWS Load Balancer Controller) 를 쓰면 502가 떠도 원인이 한 군데에 있지 않습니다. 특히 ALB에서 흔히 보이는 메시지가 target timeout(또는 target 응답 지연/미응답) 계열인데, 이 경우는 대개 ALB → Target Group(노드/Pod) → 애플리케이션 경로 어딘가에서 “정상 연결은 됐지만 제때 응답을 못 받았다”는 뜻에 가깝습니다.
이 글은 다음을 목표로 합니다.
- ALB Ingress 502(target timeout)에서 가장 빨리 범위를 좁히는 진단 순서
- ALB/TG 타임아웃 vs 애플리케이션 타임아웃 구분법
- EKS에서 자주 터지는 원인(헬스체크, readiness, keep-alive, slow request, gRPC/HTTP2, nodePort/iptables, 보안그룹, NACL)
- 바로 적용 가능한 Ingress annotation / 서비스 설정 / 앱 설정 예시
관련 케이스로 404가 “고정”되는 TG/Rule 문제도 함께 참고하면 진단 속도가 빨라집니다: EKS ALB Ingress 404 고정 - 10분 규칙·TG 진단
1) 먼저 용어 정리: 502(target timeout)이 의미하는 것
ALB에서 502는 여러 상황에서 나오지만, target timeout 류는 보통 다음 중 하나입니다.
- ALB → 타겟(노드/Pod IP) 연결은 됐지만 타겟이 응답을 늦게 하거나 못함
- 타겟이 응답을 하긴 했지만 중간에서 커넥션이 끊김(RST/FIN)
- 타겟이 살아있지 않거나 라우팅이 꼬여서 실제로는 응답할 프로세스가 없음(readiness/endpoint/iptables)
중요 포인트는 “클라이언트가 느린 게 아니라, ALB가 백엔드에서 응답을 못 받는다”는 방향으로 접근해야 한다는 점입니다.
2) 10분 안에 범위 좁히는 진단 순서(추천)
2.1 ALB 액세스 로그/에러 패턴부터 본다
가능하면 ALB access log를 S3로 켜고, 해당 요청의 target_processing_time, elb_status_code, target_status_code를 확인합니다.
elb_status_code=502+target_status_code=-인 경우가 많다면: 타겟에서 HTTP 응답 자체를 못 받음(연결/타임아웃/리셋)target_processing_time이 ALB idle timeout 근처로 치솟으면: 백엔드가 너무 느림 또는 스트리밍/롱폴링
> Cloudflare 앞단이 있다면 520/521도 비슷한 방식으로 ALB/Nginx 로그를 같이 보면 좋습니다: Cloudflare 520·521, Nginx·ALB 로그로 30분 진단
2.2 Target Group 상태(Healthy/Unhealthy)와 Unhealthy reason 확인
AWS 콘솔에서 Target Group → Targets 탭을 봅니다.
unhealthy가 많으면 502는 “결과”일 뿐이고, 핵심은 헬스체크 실패 원인입니다.healthy인데도 502면, 헬스체크는 통과하지만 실제 트래픽에서만 느리거나 끊기는 문제일 가능성이 큽니다.
2.3 Kubernetes 관점: Service/Endpoints/Readiness부터 확인
아래 3개는 거의 습관처럼 확인해야 합니다.
kubectl -n <ns> get ingress <ing>
kubectl -n <ns> describe ingress <ing>
kubectl -n <ns> get svc <svc> -o wide
kubectl -n <ns> get endpoints <svc>
kubectl -n <ns> get pod -l app=<label> -o wide
kubectl -n <ns> describe pod <pod>
endpoints가 비어 있으면: readiness probe 실패, selector 불일치, 또는 Pod가 준비 상태가 아님- endpoints가 있는데도 502면: 네트워크/포트/프로세스/타임아웃 영역으로 이동
3) 가장 흔한 원인 TOP 8 + 해결법
3.1 ALB idle timeout(기본 60s)보다 응답이 늦다
ALB는 기본 idle timeout이 60초입니다. 백엔드가 70초 걸리면, ALB는 기다리다 끊고 502를 낼 수 있습니다.
해결
- 애플리케이션 응답을 60초 미만으로 줄이거나(비동기 처리/큐)
- ALB idle timeout을 늘립니다.
AWS Load Balancer Controller에서는 Ingress annotation으로 설정합니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=120
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 80
> 단, 무작정 늘리면 “느린 요청이 계속 붙잡고 있는” 상태가 되어 워커/커넥션 고갈을 유발할 수 있습니다. 앱 레벨 타임아웃(예: Gunicorn/uvicorn, DB timeout)도 같이 맞추는 게 안전합니다. 이 주제는 앱 워커 타임아웃 관점에서 Gunicorn Uvicorn Worker timeout 재현과 해결도 참고할 만합니다.
3.2 Target Group health check 경로/포트/코드가 맞지 않다
헬스체크는 통과해야 트래픽이 갑니다. 특히 다음 실수가 잦습니다.
/healthz는 200을 주는데, 실제는/만 열려 있음(혹은 반대)- 헬스체크는 200인데, 실제 트래픽은 인증/리다이렉트/헤더 요구로 느려짐
- 서비스 포트와 컨테이너 포트가 꼬여 헬스체크가 다른 포트로 감
해결
Ingress annotation으로 health check를 명시합니다.
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"
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"
그리고 Kubernetes readiness/liveness도 함께 정합성을 맞춥니다.
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
핵심은 “ALB 헬스체크가 통과하는 엔드포인트”와 “Pod readiness가 Ready로 만드는 엔드포인트”를 가능하면 동일하게 두는 것입니다.
3.3 target-type(Instance/IP)와 Service 타입/포트가 불일치
AWS Load Balancer Controller는 크게 두 가지 타겟 방식이 있습니다.
- instance: 노드(EC2)의 NodePort로 트래픽 전달
- ip: Pod IP로 직접 전달(보통 더 직관적)
여기서 자주 터지는 조합은 다음입니다.
target-type: instance인데 Service가 NodePort가 아니거나, NodePort가 막혀 있음target-type: ip인데 Pod 보안그룹/네트워크 정책/서브넷 라우팅이 맞지 않음
해결
보통 EKS에서는 ip가 문제를 단순하게 만듭니다(단, CNI/보안그룹 설정이 전제).
metadata:
annotations:
alb.ingress.kubernetes.io/target-type: ip
그리고 Service는 일반적으로 ClusterIP로 둬도 됩니다.
apiVersion: v1
kind: Service
metadata:
name: api-svc
spec:
type: ClusterIP
selector:
app: api
ports:
- name: http
port: 80
targetPort: 8080
3.4 애플리케이션 keep-alive/프록시 설정 불일치로 커넥션이 끊긴다
ALB는 클라이언트와 keep-alive를 유지하면서 백엔드에도 재사용 가능한 커넥션을 씁니다. 그런데 백엔드(Nginx, Envoy, 앱 서버)가 keep-alive를 너무 짧게 잡거나, 특정 조건에서 RST를 내면 간헐적으로 502가 튈 수 있습니다.
해결 체크
- Nginx면
keepalive_timeout,proxy_read_timeout,proxy_send_timeout확인 - 앱 서버면 worker timeout, graceful shutdown, max requests 등 확인
- 롤링 업데이트 중이라면
terminationGracePeriodSeconds와 preStop hook으로 드레인 보장
예: 종료 시 드레인 시간을 주는 패턴
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
terminationGracePeriodSeconds: 30
3.5 readiness는 Ready인데 실제로는 의존성(DB/외부 API) 때문에 응답이 늦다
readiness는 “프로세스가 떠있다”만 확인하고, 실제로는 DB 커넥션 풀 고갈/외부 API 지연으로 응답이 밀리면 ALB 관점에서는 target timeout이 됩니다.
해결
- readiness를 “핵심 의존성까지 포함한 얕은 체크”로 강화(단, 과도하게 무겁게 만들면 역효과)
- 앱 내부 타임아웃을 명시하고, 느린 작업은 비동기화
- HPA/리소스 요청/제한 재조정(특히 CPU throttling은 지연을 크게 만듦)
3.6 HTTP/2(gRPC)에서만 터지는 타임아웃/DEADLINE 문제
ALB는 HTTP/2를 지원하고 gRPC도 구성할 수 있지만, gRPC는 클라이언트 deadline, 서버 처리 시간, 프록시 idle timeout이 엮이면서 증상이 502/504, 혹은 앱 로그의 DEADLINE_EXCEEDED로 나타납니다.
- 클라이언트 deadline이 짧으면: ALB가 아니라 클라이언트가 먼저 끊음
- 서버/프록시가 스트리밍 중인데 idle timeout 걸리면: 중간에서 끊김
gRPC 계열 진단은 별도 체크리스트가 도움이 됩니다: EKS에서 gRPC DEADLINE_EXCEEDED 폭증 해결
3.7 보안그룹/네트워크 ACL/네트워크 정책으로 타겟 응답이 막힌다
ALB → 타겟으로 가는 인바운드가 막히면 헬스체크부터 죽습니다. 그런데 “헬스체크는 간신히 통과하지만 실제 경로만 막힘” 같은 미묘한 케이스도 있습니다(포트 다름/경로 다름).
체크리스트
- ALB SG에서 노드/Pod SG로 인바운드 허용(포트 포함)
- 노드 SG egress, Pod SG egress(사용 중인 경우)
- NACL에서 ephemeral port 범위(응답 트래픽) 허용
- Calico/Cilium NetworkPolicy가 특정 namespace/port만 차단하는지
3.8 리소스 부족(CPU throttling, 메모리 OOM)으로 응답이 늦거나 프로세스가 재시작된다
- Pod가 OOMKill로 재시작되면 요청 중 연결이 끊겨 502가 튈 수 있습니다.
- CPU limit이 너무 낮아 throttling이 심하면 p99 지연이 폭발해 idle timeout에 걸립니다.
해결
kubectl top pod및 metrics로 CPU throttling/메모리 사용량 확인- requests/limits 재설정, HPA scale-out
- 애플리케이션 프로파일링(슬로우 쿼리/락)
4) 재현 가능한 최소 진단: Pod까지 직접 때려보기
ALB를 거치지 않고 Pod/Service로 직접 요청을 보내면, “ALB 문제인지 앱 문제인지”가 바로 갈립니다.
4.1 클러스터 내부에서 Service로 호출
kubectl -n <ns> run -it --rm curl --image=curlimages/curl -- \
curl -sv http://api-svc.<ns>.svc.cluster.local/healthz
여기서도 느리거나 타임아웃이면 ALB가 아니라 앱/서비스/엔드포인트 문제입니다.
4.2 Pod IP로 직접 호출(가능한 경우)
POD_IP=$(kubectl -n <ns> get pod -l app=api -o jsonpath='{.items[0].status.podIP}')
kubectl -n <ns> run -it --rm curl --image=curlimages/curl -- \
curl -sv http://$POD_IP:8080/healthz
Pod IP는 되는데 Service는 안 되면 kube-proxy/iptables, selector, endpoints를 의심합니다.
5) 실전 설정 템플릿: “타임아웃/헬스체크/타겟타입” 한 번에 정리
아래는 HTTP API 기준으로, 문제를 줄이는 쪽으로 보수적으로 설정한 예시입니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api
annotations:
kubernetes.io/ingress.class: alb
# 타겟을 Pod IP로
alb.ingress.kubernetes.io/target-type: ip
# 헬스체크 명시
alb.ingress.kubernetes.io/healthcheck-path: /healthz
alb.ingress.kubernetes.io/success-codes: "200-399"
# ALB idle timeout 조정(필요 시)
alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=120
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 80
---
apiVersion: v1
kind: Service
metadata:
name: api-svc
spec:
type: ClusterIP
selector:
app: api
ports:
- name: http
port: 80
targetPort: 8080
그리고 Deployment에는 최소한 readiness와 종료 드레인을 넣습니다.
readinessProbe:
httpGet:
path: /healthz
port: 8080
periodSeconds: 5
timeoutSeconds: 2
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
terminationGracePeriodSeconds: 30
6) 결론: “target timeout”은 ALB가 아니라 백엔드의 신호다
EKS에서 ALB Ingress 502(target timeout)을 만나면, 감으로 Ingress만 만지기보다 다음 순서가 가장 빠릅니다.
- ALB access log로
target_status_code/processing time 확인 - Target Group Healthy/Unhealthy + reason 확인
- Kubernetes에서 endpoints/readiness 확인
- 클러스터 내부에서 Service/Pod 직접 호출로 ALB를 우회해 재현
- 원인에 따라
- idle timeout 조정
- health check 정합성 수정
- target-type/Service 타입 정리
- 앱 워커 타임아웃/keep-alive/종료 드레인 튜닝
- 리소스/HPA/의존성 지연 개선
이렇게 하면 “502니까 네트워크겠지” 같은 막연한 추측을 줄이고, 보통 30~60분 내에 원인을 특정할 수 있습니다.