Published on

EKS ALB Ingress 504인데 Pod는 정상일 때

Authors

서버는 살아있고(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=504
    • target_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: 80targetPort: 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 스트리밍이나 서버 푸시를 붙였다면 특히 자주 터집니다.

8) 실전 “10분 컷” 체크리스트(우선순위)

8-1. 증상 분류

  1. 내부(Service 직접) 호출은 정상인가?
  2. ALB access log에서 elb_status_code=504가 찍히는가?
  3. target_processing_time이 60초 근처로 길게 찍히는가?

8-2. 설정/리소스 확인

  1. Ingress annotation: target-type, backend-protocol, healthcheck-path, idle_timeout
  2. Service: port/targetPort, selector, endpoints 존재
  3. SG: ALB SG → (노드 NodePort 또는 Pod ENI) 인바운드 허용
  4. 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가 정상이라는 사실은 오히려 힌트입니다. 문제는 대개 다음 셋 중 하나로 귀결됩니다.

  1. 느린 응답(백엔드 처리/의존성 병목)으로 ALB idle timeout 초과
  2. 네트워크 경로 차단(SG/NACL/타겟 타입에 따른 포트 허용 누락)
  3. 포트/프로토콜 불일치(HTTP/HTTPS, Service port/targetPort, 헬스체크와 실트래픽의 불일치)

ALB access log와 내부 curl 테스트로 “ALB→타겟” 구간을 증거 기반으로 쪼개면, 504는 생각보다 빠르게 잡힙니다.