- Published on
EKS ALB Ingress 504(60초) idle_timeout 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 멀쩡한데 요청이 정확히 60초 전후로 끊기며 504 Gateway Timeout이 발생한다면, 애플리케이션이 느린 게 아니라 ALB의 idle timeout(기본 60초) 에 걸렸을 가능성이 큽니다. EKS에서 AWS Load Balancer Controller(구 ALB Ingress Controller)를 쓰는 환경에서는 이 현상이 특히 자주 보입니다.
이 글에서는 “왜 60초냐”를 로그/지표로 확인하는 방법부터, Ingress annotation으로 ALB idle_timeout을 늘리는 방법, 그리고 단순히 타임아웃만 늘리는 게 아니라 장기적으로 안전한 설계(비동기/스트리밍/헬스체크/keep-alive) 로 재발을 줄이는 방법까지 정리합니다.
> 참고로 EKS 운영 중 자주 같이 터지는 이슈로는 IRSA/권한 문제도 있습니다. 관련해서는 EKS ExternalSecret 미동작 - IRSA·KMS·권한 10분 진단, Terraform apply 후 EKS 노드 NotReady - CNI·IRSA·보안그룹 점검 도 함께 보면 트러블슈팅 속도가 빨라집니다.
증상: “정확히 60초”의 의미
ALB(Application Load Balancer)는 커넥션이 idle(유휴) 상태로 일정 시간 지속되면 연결을 끊습니다. 기본값이 60초입니다.
여기서 말하는 idle은 “요청 처리 전체 시간이 60초”가 아니라, 연결 상에서 데이터가 오가지 않는 상태가 60초 지속되는 상황을 의미합니다.
대표 패턴은 아래와 같습니다.
- 서버는 2~3분짜리 작업을 수행하지만, 그동안 응답 바디를 전혀 보내지 않음(버퍼링)
- 업스트림(파드)에서 처리 중이지만 중간 진행 상황을 보내지 않고 마지막에 한 번에 응답
- 애플리케이션/프레임워크가 응답을 flush하지 않아 ALB 기준으로는 60초 동안 아무 데이터가 없음
그 결과 클라이언트는 504를 받고, 서버는 “나는 아직 처리 중이었는데?” 같은 상태가 됩니다.
먼저 확인할 것: 504가 ALB에서 난 건지, 파드에서 난 건지
1) ALB 액세스 로그로 확인
ALB 액세스 로그를 S3로 켜두면 가장 확실합니다. 504가 ALB에서 발생하면 로그에 elb_status_code=504가 찍힙니다.
예시(개념):
... elb_status_code=504 target_status_code=- ...
target_status_code가 비어있거나 -에 가까우면 타겟(파드)까지 정상 응답을 못 받고 ALB가 끊었다는 신호입니다.
2) CloudWatch 지표
ALB 지표에서 다음을 확인합니다.
HTTPCode_ELB_5XX_Count증가 여부 (ALB가 자체적으로 5xx를 냄)TargetResponseTime가 60초 근처에서 끊기는지
3) 파드 로그/애플리케이션 로그
파드가 실제로 60초 이후에도 작업을 계속하고 있는지, 혹은 서버 자체 타임아웃(예: Gunicorn, Node, Spring)이 먼저 끊는지 확인합니다.
> 애플리케이션 타임아웃이 원인인 경우도 많습니다. 하지만 “정확히 60초”는 ALB idle_timeout 시그니처인 경우가 많습니다.
원인: ALB idle_timeout 기본값 60초
ALB는 로드밸런서 속성으로 idle_timeout.timeout_seconds 값을 가집니다.
- 기본: 60초
- 설정 가능 범위(일반적으로): 1~4000초
EKS에서 Ingress로 ALB를 만들었다면, 이 값은 Ingress annotation 또는 IngressClassParams/Service annotation 등을 통해 제어합니다(설치한 AWS Load Balancer Controller 버전에 따라 옵션이 조금씩 다를 수 있음).
해결 1) Ingress annotation으로 idle_timeout 늘리기(가장 흔한 정답)
AWS Load Balancer Controller를 사용한다면, Ingress에 alb.ingress.kubernetes.io/load-balancer-attributes 주석을 추가해 ALB 속성을 바꿀 수 있습니다.
아래는 idle timeout을 180초로 늘리는 예시입니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api
namespace: default
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
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
port:
number: 80
적용 후에는 아래처럼 확인합니다.
kubectl apply -f ingress.yaml
kubectl describe ingress api | sed -n '/Annotations/,$p'
그리고 AWS 콘솔에서 해당 ALB의 Attributes → Idle timeout 값이 변경됐는지 확인합니다.
주의: “Ingress 수정했는데 반영이 안 돼요”
- 같은 ALB를 여러 Ingress가 공유하는 구성(그룹)이라면, 최종 속성은 충돌/우선순위 영향을 받을 수 있습니다.
- Controller가 권한을 못 가져서 수정이 실패하는 경우도 있습니다(이 경우 controller 로그에 힌트가 남습니다).
Controller 로그 확인:
kubectl -n kube-system logs deploy/aws-load-balancer-controller | tail -n 200
해결 2) 타임아웃을 무작정 늘리기 전에: 설계적으로 더 안전한 접근
idle_timeout을 늘리는 건 즉효가 있지만, 다음 부작용을 고려해야 합니다.
- 느린 요청이 장시간 커넥션을 점유 → ALB/타겟 연결 수 증가
- 클라이언트가 끊겼는데 서버는 계속 작업 → 불필요한 비용/부하
- “긴 요청”이 늘어날수록 장애 시 폭발 반경 증가
따라서 다음 중 하나를 함께 검토하는 것이 좋습니다.
1) 비동기 작업 + 폴링/콜백으로 전환
2~3분 걸리는 작업은 HTTP 요청-응답 한 번에 끝내기보다:
POST /jobs→ job_id 반환GET /jobs/{id}→ 상태 조회- 완료 시 웹훅/푸시
로 전환하면 로드밸런서 타임아웃에 덜 민감해지고, 재시도/중복 처리도 다루기 쉬워집니다.
2) 스트리밍(Chunked) 또는 주기적 flush로 idle 상태 방지
ALB idle은 “데이터가 오가지 않음”이 핵심이므로, 서버가 주기적으로 작은 데이터를 보내면 연결이 유지됩니다.
예: Nginx 뒤에서 SSE(Server-Sent Events)로 진행률을 보내는 FastAPI 예시
from fastapi import FastAPI
from sse_starlette.sse import EventSourceResponse
import asyncio
app = FastAPI()
@app.get("/stream")
async def stream():
async def gen():
for i in range(120):
yield {"event": "progress", "data": str(i)}
await asyncio.sleep(1)
return EventSourceResponse(gen())
이런 방식은 “긴 처리”를 사용자 경험 측면에서도 낫게 만들 수 있습니다.
3) 애플리케이션/프록시의 타임아웃 체인 정리
실제 운영에서는 타임아웃이 한 군데만 있는 게 아니라 체인으로 존재합니다.
- Client timeout
- ALB idle_timeout
- Ingress/sidecar(Nginx/Envoy) read_timeout
- 애플리케이션 서버(Gunicorn
timeout, Node server timeout, Spring async timeout) - upstream(DB/외부 API) timeout
원칙은 바깥쪽(클라이언트/ALB)이 안쪽(앱)보다 약간 더 길거나, 혹은 의도적으로 더 짧게 두고 빠르게 실패시키는 등 “정책”을 정하는 것입니다. 아무렇게나 늘리면 장애 시 대기열만 길어져 더 크게 터집니다.
실전 체크리스트: 10분 안에 결론 내기
1) 정말 60초인가?
- 클라이언트에서 측정: 59~61초에 반복 재현?
- ALB 액세스 로그
elb_status_code=504확인
2) 요청이 60초 동안 완전 무응답인가?
- 서버가 중간에 flush/stream을 하는지
- 프레임워크가 버퍼링하고 있지 않은지
3) Ingress annotation이 실제 ALB에 반영됐나?
- Ingress describe에 annotation 존재
- AWS 콘솔/CLI로 ALB attribute 확인
AWS CLI 예시(개념):
aws elbv2 describe-load-balancer-attributes \
--load-balancer-arn <ALB_ARN>
4) 타겟 그룹/헬스체크는 정상인가?
헬스체크가 불안정하면 504와 섞여 보일 수 있습니다.
- Target group health: unhealthy 증가?
- 파드 readinessProbe/endpoint 정상?
권장 설정 예시(보수적)
- 단순히 “가끔 70~90초 걸리는 요청”이라면:
idle_timeout=120~180초 - “몇 분 걸리는 작업”이라면: 타임아웃을 늘리기보다 비동기/스트리밍 설계로 전환
- 외부 API 호출이 길어지는 경우: 내부 호출 타임아웃을 명확히(예: 20~30초) 두고 재시도/서킷브레이커 적용
마무리
EKS에서 ALB Ingress로 서비스할 때 정확히 60초 전후로 504가 난다면, 대부분은 애플리케이션 자체 장애가 아니라 ALB idle_timeout 기본값(60초) 과 “긴 요청의 무응답 구간”이 맞물린 문제입니다.
1차 처방은 Ingress annotation으로 idle_timeout.timeout_seconds를 늘리는 것이고, 2차 처방은 긴 요청을 비동기/스트리밍으로 바꿔 타임아웃 민감도를 낮추는 것입니다. 운영 환경에서는 타임아웃을 단순히 ‘늘리기’보다, 전체 타임아웃 체인을 설계하고 관측(로그/지표)까지 붙여야 재발을 줄일 수 있습니다.
추가로 EKS에서 권한/IRSA 이슈가 섞이면 원인 분리가 어려워집니다. 그런 경우에는 EKS IRSA는 되는데 STS 429 Throttling 해결 같은 체크리스트도 함께 참고하면 좋습니다.