- Published on
EKS에서 ALB Ingress 408 Request Timeout 해결 가이드
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 멀쩡해 보이는데도 EKS 앞단의 ALB Ingress에서 408 Request Timeout이 떨어지면, 문제는 대개 **"어디에서 타임아웃이 났는지"**를 잘못 짚어서 오래 헤매는 데서 시작합니다. 408은 애플리케이션이 직접 반환하기도 하지만, EKS+ALB 조합에서는 ALB 계층(리스너/타겟/헬스체크/idle timeout) 또는 **Pod/Service 계층(프로브/리소스/연결 종료/스케일링)**에서 촉발되는 경우가 많습니다.
이 글은 408을 관측 → 분류 → 원인 좁히기 → 설정 변경 순서로 해결하는 실전 체크리스트입니다.
1) 먼저: 408이 ALB에서 났는지 앱에서 났는지 구분
가장 먼저 할 일은 “누가 408을 반환했는가”입니다. 같은 408이라도 해결 방향이 완전히 달라집니다.
1-1. 클라이언트에서 응답 헤더로 판별
- ALB가 반환하는 경우: 응답에
server: awselb/2.0같은 헤더가 보이거나, 바디가 간단한 HTML 에러 페이지인 경우가 많습니다. - 애플리케이션/프록시가 반환하는 경우: 앱/프레임워크 고유 헤더, JSON 에러 바디 등이 보입니다.
curl -v https://api.example.com/slow-endpoint 2>&1 | sed -n '1,40p'
1-2. ALB Access Log / CloudWatch 지표로 판별
ALB 액세스 로그를 켜면 elb_status_code / target_status_code 조합으로 계층을 구분할 수 있습니다.
elb_status_code=408이고target_status_code=-이면 ALB가 타겟과 대화도 못 했거나 요청을 끝까지 못 받았을 가능성이 큽니다.elb_status_code=408이고target_status_code=408이면 타겟(파드/노드/프록시)이 408을 반환했을 가능성이 큽니다.
2) 흔한 원인 Top 7 (EKS + AWS Load Balancer Controller 기준)
아래는 현장에서 가장 자주 만나는 원인들입니다. 408은 보통 단일 원인이라기보다, “느려진 요청 + 짧은 타임아웃 + 불안정한 타겟”이 겹쳐서 발생합니다.
원인 1) ALB idle timeout(기본 60초)보다 응답이 늦음
ALB는 기본적으로 idle timeout 60초입니다. 서버가 60초 넘게 응답을 못 주거나(특히 TTFB가 늦음), 중간에 데이터가 오가지 않으면 연결이 끊길 수 있습니다.
- 긴 쿼리/외부 API 호출/대용량 처리
- SSE/롱폴링/스트리밍
- 백엔드가 첫 바이트를 늦게 주는 구조
해결: Ingress annotation으로 idle timeout을 늘립니다.
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=180
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 80
> 스트리밍(SSE) 계열에서 타임아웃/끊김 이슈는 408만이 아니라 499/502 등으로도 나타납니다. 프록시/버퍼링/idle timeout 관점은 아래 글의 체크리스트가 도움이 됩니다: LLM SSE 스트리밍 499 502 급증과 응답 끊김을 잡는 프록시 튜닝 체크리스트
원인 2) 타겟 그룹 헬스체크가 실패 → 타겟이 수시로 OutOfService
헬스체크가 불안정하면 ALB는 정상 타겟을 찾느라 재시도/라우팅이 꼬이면서 지연이 커지고, 특정 구간에서 408/5xx가 섞여 나오기도 합니다.
체크 포인트:
- 헬스체크 경로가 앱에서 가끔 느리거나 DB를 탐
- readinessProbe는 통과하지만, ALB 헬스체크는 실패(포트/경로/성공코드 불일치)
- 파드 롤링 중
deregistration delay와 조합 문제
해결: 헬스체크는 가볍게(메모리/프로세스 수준) 만들고, Ingress에서 헬스체크 설정을 명시합니다.
metadata:
annotations:
alb.ingress.kubernetes.io/healthcheck-path: /healthz
alb.ingress.kubernetes.io/healthcheck-interval-seconds: "10"
alb.ingress.kubernetes.io/healthcheck-timeout-seconds: "5"
alb.ingress.kubernetes.io/success-codes: "200"
앱 측에서는 /healthz가 DB/외부 의존성을 타지 않도록 분리하는 것이 안전합니다.
원인 3) target-type(instance vs ip) 불일치로 라우팅/보안그룹이 꼬임
AWS Load Balancer Controller에서 타겟 타입이 환경과 맞지 않으면 연결이 성립하지 않아, 결과적으로 타임아웃류가 발생합니다.
instance: ALB → 노드포트로 트래픽 전달ip: ALB → 파드 IP로 직접 전달(보통 EKS에서 선호)
해결: 명시적으로 설정하고, 보안 그룹/서브넷/네트워크 정책을 함께 점검합니다.
metadata:
annotations:
alb.ingress.kubernetes.io/target-type: ip
추가로 Controller IAM/권한 이슈가 있으면 타겟 그룹/리스너 갱신이 실패하면서 이상 증상이 동반될 수 있습니다. 권한 문제를 의심한다면: EKS에서 AWS Load Balancer Controller 403 해결법
원인 4) readiness/liveness probe 설정이 공격적이라 요청 중 파드가 교체됨
프로브가 너무 빡세면(짧은 timeout, 낮은 failureThreshold) 순간적인 GC/CPU 스파이크에도 파드가 재시작되거나 Ready에서 빠집니다. 이때 ALB는 연결 중인 타겟을 잃고, 클라이언트는 타임아웃/리셋을 경험합니다.
해결:
- readinessProbe는 “트래픽 받을 준비”에 초점
- livenessProbe는 “프로세스가 죽었는지”에 초점
- startupProbe로 초기 부하/웜업 구간 보호
livenessProbe:
httpGet:
path: /livez
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 6
readinessProbe:
httpGet:
path: /readyz
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
startupProbe:
httpGet:
path: /readyz
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 30
파드가 자주 재시작/Ready flapping을 한다면 408은 결과일 뿐이고, 근본 원인은 CrashLoopBackOff/리소스 부족일 수 있습니다. 관련 디버깅은 다음 글이 체계적입니다: Kubernetes CrashLoopBackOff 원인별 로그·Probe·리소스 디버깅
원인 5) 리소스(CPU/메모리) 부족으로 처리 지연 → 타임아웃
CPU throttling이 심하거나 메모리 압박으로 GC가 길어지면 TTFB가 늦어지고, 결국 ALB idle timeout이나 클라이언트 타임아웃에 걸립니다.
진단 포인트:
kubectl top pod에서 CPU가 한계에 붙어 있음- HPA가 늦게 반응하거나 스케일이 안 됨
- OOMKilled가 간헐적으로 발생
해결:
- requests/limits 재조정
- HPA 목표치/스케일 정책 조정
- 병목 쿼리/외부 호출 캐싱
OOM이 의심되면 아래 글의 “메모리 누수 추적” 섹션이 특히 유용합니다: Kubernetes OOMKilled 진단과 메모리 누수 추적 실전
원인 6) deregistration delay/드레이닝 미설정으로 롤링 배포 중 요청 유실
롤링 업데이트 중 기존 파드가 종료되는데, 연결 드레이닝이 충분치 않으면 진행 중 요청이 끊기며 타임아웃으로 보일 수 있습니다.
해결:
terminationGracePeriodSeconds확보- preStop 훅으로 커넥션 정리
- 타겟 그룹 deregistration delay 조정
spec:
terminationGracePeriodSeconds: 60
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
Ingress에서 타겟 그룹 속성으로 조정할 수도 있습니다.
metadata:
annotations:
alb.ingress.kubernetes.io/target-group-attributes: deregistration_delay.timeout_seconds=30
원인 7) 네트워크/DNS 간헐 장애로 백엔드 호출이 지연
앱이 내부적으로 RDS/외부 API/다른 서비스로 호출하는 구조라면, **앱 로그에는 단순히 "응답이 늦다"**로만 남고, 결과적으로 408이 튀는 경우가 많습니다.
- CoreDNS 지연/드롭
- 노드의 conntrack 포화
- 특정 AZ/서브넷에서만 발생
EKS에서 DNS 간헐 실패가 의심되면 NodeLocal DNSCache가 효과적일 수 있습니다: EKS NodeLocal DNSCache로 DNS 간헐 실패 잡기
3) 실전 진단 순서(30분 내 원인 좁히기)
3-1. ALB Target Group 상태부터 확인
- AWS 콘솔에서 해당 ALB → Target Group → Targets
healthy/unhealthy변동이 있는지Health check실패 사유(Timeout/5xx 등)
- Ingress 이벤트 확인
kubectl describe ingress api
kubectl -n kube-system logs deploy/aws-load-balancer-controller --tail=200
Controller 로그에 리스너/규칙/타겟 그룹 갱신 오류가 찍히면 408 이전에 인프라 레벨 문제가 있을 수 있습니다.
3-2. 파드 레벨에서 응답 지연/재시작 여부 확인
kubectl get pod -o wide
kubectl describe pod <pod>
kubectl logs <pod> --previous
- 재시작 횟수 증가
- readiness가 자주 false
- OOMKilled / probe failed
3-3. 애플리케이션 타임아웃 체인 점검
타임아웃은 “가장 짧은 놈”이 지배합니다.
- 클라이언트(브라우저/모바일/SDK) 타임아웃
- ALB idle timeout
- Ingress/Service mesh(있다면) 타임아웃
- 앱 서버(gunicorn/uvicorn/node) 타임아웃
- DB 드라이버 타임아웃
예: Python gunicorn을 쓴다면 worker timeout이 너무 짧아 요청이 중간에 잘릴 수 있습니다.
4) 자주 쓰는 Ingress 설정 템플릿(Timeout/헬스체크/타겟)
아래는 “일단 기본 방어선”을 갖춘 ALB Ingress 예시입니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
# ALB idle timeout
alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=180
# Health check
alb.ingress.kubernetes.io/healthcheck-path: /healthz
alb.ingress.kubernetes.io/healthcheck-interval-seconds: "10"
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"
# Draining
alb.ingress.kubernetes.io/target-group-attributes: deregistration_delay.timeout_seconds=30
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 80
> 주의: idle timeout을 무작정 크게 올리면 “느린 요청이 계속 쌓여서” 워커/커넥션 풀이 고갈될 수 있습니다. 반드시 왜 느린지(DB, 외부 API, CPU)도 병행해서 해결해야 합니다.
5) 408을 재현/검증하는 테스트 방법
5-1. 부하/지연 테스트로 타임아웃 경계 확인
# 120초까지 기다리도록 클라이언트 타임아웃을 늘리고 관찰
curl -m 120 -v https://api.example.com/slow
- 60초 근처에서 끊기면 ALB idle timeout 가능성이 큼
- 매번 다른 시간에 끊기면 파드 교체/헬스체크/리소스 스파이크 가능성
5-2. 서버 내부에서 직접 서비스 호출(우회 테스트)
ALB를 거치지 않고 클러스터 내부에서 서비스로 직접 호출해보면, 408이 ALB 문제인지 앱 문제인지 빨리 분리됩니다.
kubectl run -it --rm curl --image=curlimages/curl --restart=Never -- \
curl -v http://api-svc.default.svc.cluster.local/healthz
- 내부 호출도 느리면 앱/의존성/리소스 문제
- 내부는 빠른데 ALB만 느리면 타겟/헬스체크/네트워크/보안그룹/idle timeout 문제
6) 마무리: 가장 효과 좋은 해결 조합
현장에서 408을 가장 빠르게 안정화시키는 조합은 보통 아래 순서로 먹힙니다.
- ALB idle timeout을 실제 SLA에 맞게 조정(예: 120~180초)
- 헬스체크 경로를 가볍게 만들고 Ingress에서 명시적으로 튜닝
- readiness/liveness/startup probe를 현실적으로 조정(초기 웜업 보호)
- 롤링 배포 시 드레이닝/종료 유예를 확보
- CPU throttling/OOM/DNS 등 "느려지는 근본 원인"을 제거
408은 “ALB가 나쁘다”기보다, 타임아웃 체인과 시스템 지연이 만나는 지점에서 생깁니다. 위 순서대로 계층을 분리해 관측하면, 대부분의 408은 재현 가능하고(=원인 규명 가능) 설정 또는 병목 제거로 안정화됩니다.