Published on

EKS에서 503 Service Unavailable 원인 10분 진단

Authors

서버가 503을 뱉을 때 가장 중요한 질문은 하나입니다. “누가 503을 반환하는가?”

EKS 환경에서는 503이 애플리케이션이 아니라 ALB/NLB(또는 Ingress Controller/Envoy/Nginx) 같은 프록시 계층에서 주로 생성됩니다. 즉, 백엔드로 보낼 대상이 없거나, 대상은 있는데 헬스체크가 실패했거나, 네트워크 경로가 끊겼거나, 포트/프로토콜이 어긋났거나 중 하나일 확률이 높습니다.

이 글은 “10분 진단”을 목표로, 가장 흔한 원인을 증상 → 확인 명령 → 해석 → 조치 순서로 압축해 제공합니다.

> Ingress에서 502/503인데 Pod 로그가 비어 있다면, 거의 항상 LB/헬스체크/타겟그룹 레벨 문제입니다. 비슷한 케이스는 EKS Ingress 502인데 Pod 로그가 비면? ALB/NLB 헬스체크부터도 함께 보세요.

0~1분: 503의 “발생 지점”부터 확정하기

체크 0) 클라이언트에서 응답 헤더로 추정

curl -sv https://your.domain.example/healthz 2>&1 | egrep -i "< HTTP|server:|x-amzn|via|x-envoy"
  • server: awselb/2.0 또는 x-amzn-... 헤더가 보이면 ALB/NLB/ELB 계층에서 생성된 503일 가능성이 큽니다.
  • server: nginx/envoy 등은 Ingress Controller/Service Mesh가 503을 만들었을 수 있습니다.

체크 1) Ingress/Service 경로를 빠르게 도식화

  • (예) Client → Route53 → ALB(Ingress) → Service(ClusterIP/NodePort) → Pod
  • (예) Client → NLB(Service type=LoadBalancer) → NodePort → Pod

이 경로 중 어디에서 “백엔드가 0개”가 되었는지 찾으면 끝입니다.

1~3분: Kubernetes에서 “엔드포인트 0” 여부 확인

503의 최상위 원인: Service가 라우팅할 Pod 엔드포인트를 하나도 갖지 못함

체크 2) Service selector와 Pod 라벨 불일치

kubectl -n <ns> get svc <svc-name> -o wide
kubectl -n <ns> describe svc <svc-name>
kubectl -n <ns> get pods -l <selector-key>=<selector-value> -o wide
  • Endpoints: <none> 또는 0 endpoints면 거의 확정적으로 라벨/셀렉터 불일치 혹은 Ready Pod 0개입니다.

체크 3) EndpointSlice로 실제 백엔드 수 확인

kubectl -n <ns> get endpointslice -l kubernetes.io/service-name=<svc-name>
kubectl -n <ns> describe endpointslice -l kubernetes.io/service-name=<svc-name>
  • 주소가 없거나 conditions.ready=false면 Service가 트래픽을 보낼 수 없습니다.

즉시 조치

  • Service spec.selector를 Pod 라벨과 맞추기
  • Deployment/Pod가 Ready가 되도록(다음 섹션)

3~5분: Pod는 떠 있는데 “Ready가 0”인 경우

엔드포인트가 0인 두 번째 원인: Pod가 Running이어도 Ready가 false

체크 4) Readiness Probe 실패

kubectl -n <ns> get pods -o wide
kubectl -n <ns> describe pod <pod-name>

Events에서 아래가 보이면 Readiness 문제입니다.

  • Readiness probe failed: ...

흔한 원인

  • 프로브 경로(/healthz)가 실제 앱과 다름
  • 컨테이너 포트/프로토콜 불일치(HTTP vs HTTPS)
  • 앱 스타트업이 느려 initialDelaySeconds가 부족

조치 예시(Deployment 일부)

readinessProbe:
  httpGet:
    path: /healthz
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5
  timeoutSeconds: 2
  failureThreshold: 6

체크 5) 애플리케이션은 떠 있는데 포트가 안 열림

kubectl -n <ns> exec -it <pod-name> -- sh -c "netstat -lntp || ss -lntp"
  • 앱이 8080을 listen하지 않는데 Service는 8080으로 보내면 503/연결 실패가 연쇄적으로 발생합니다.

체크 6) Pod가 아예 정상 생성되지 못하는 경우

Pod가 ContainerCreating, ImagePullBackOff, Pending이면 당연히 엔드포인트는 0이 됩니다.

5~7분: Ingress(ALB) / Service(NLB) 헬스체크 불일치

Kubernetes는 Ready인데도 503이면, 로드밸런서가 타겟을 Unhealthy로 보고 트래픽을 버리는 케이스가 많습니다.

케이스 A) AWS Load Balancer Controller + ALB(Ingress)

체크 7) Ingress 이벤트와 어노테이션

kubectl -n <ns> describe ingress <ingress-name>
  • Events에 TargetGroup 생성/수정 실패가 있는지 확인
  • 헬스체크 관련 어노테이션(예)
alb.ingress.kubernetes.io/healthcheck-path: /healthz
alb.ingress.kubernetes.io/success-codes: "200-399"
alb.ingress.kubernetes.io/healthcheck-port: traffic-port

체크 8) Target Group 상태를 AWS CLI로 확인

aws elbv2 describe-target-health --target-group-arn <tg-arn>
  • unhealthy 사유가 Health checks failed면:
    • 헬스체크 path/port/protocol 불일치
    • 보안그룹/네트워크로 헬스체크가 못 들어옴

케이스 B) Service type=LoadBalancer + NLB

NLB는 보통 TCP 레벨로 헬스체크를 하며, externalTrafficPolicy와 NodePort 구성이 얽히면 “노드에는 포트가 열렸지만 실제 Pod로 못 감” 문제가 생깁니다.

체크 9) externalTrafficPolicy=Local로 인한 노드 편향

kubectl -n <ns> get svc <svc-name> -o yaml | yq '.spec.externalTrafficPolicy'
  • Local이면 Pod가 없는 노드로는 트래픽이 가면 안 되는데, 설정/타겟 등록 방식에 따라 헬스체크가 실패할 수 있습니다.

7~9분: Service/Port/Protocol 매핑 실수 (의외로 가장 흔함)

체크 10) Service의 targetPort가 실제 컨테이너 포트와 다름

kubectl -n <ns> get svc <svc-name> -o yaml
kubectl -n <ns> get deploy <deploy-name> -o yaml | yq '.spec.template.spec.containers[].ports'
  • Service port: 80targetPort: 8080인데, 컨테이너는 8000을 열고 있으면 백엔드 연결 실패

체크 11) HTTP/HTTPS 종단 위치(TLS termination) 혼선

  • ALB에서 TLS 종료(HTTPS→HTTP)인데 백엔드는 HTTPS만 받도록 구성
  • 반대로 ALB가 HTTP로 헬스체크하는데 백엔드는 HTTPS 리다이렉트(301/302)로만 응답

조치:

  • ALB Listener/TargetGroup 프로토콜과 애플리케이션 수신 프로토콜 정렬
  • 헬스체크는 리다이렉트 없는 “순수 200” 엔드포인트로 분리(/healthz)

9~10분: 네트워크 경로(보안그룹/NACL/Ingress-only) 점검

Kubernetes/ALB 설정이 맞아 보이는데도 503이면, 헬스체크 트래픽이 백엔드에 도달하지 못하는 경우를 의심합니다.

체크 12) 보안그룹 규칙(특히 ALB → Node/Pod)

  • ALB SG에서 노드 SG(또는 Pod ENI SG)로 백엔드 포트 인바운드 허용 필요
  • Node SG에서 ALB SG 소스 허용 필요(구성에 따라)

체크 13) “egress는 되는데 ingress만 실패” 유형

클러스터 내부 통신은 되는데 외부에서만 503이면, 인바운드 경로(보안그룹/NACL/라우팅) 문제일 확률이 큽니다. 이 유형은 EKS에서 Pod egress는 되는데 ingress만 실패할 때 체크리스트가 바로 맞습니다.

실전: 10분 진단용 커맨드 묶음(복붙)

아래는 “503이 뜬다”는 제보를 받았을 때 제가 가장 먼저 실행하는 순서입니다.

# 1) Ingress/Service/Endpoints 한 번에
kubectl -n <ns> get ingress,svc,endpoints,endpointslice -o wide

# 2) 문제 Service 상세
kubectl -n <ns> describe svc <svc-name>

# 3) Ready 아닌 Pod 찾기
kubectl -n <ns> get pods -o wide
kubectl -n <ns> get pods --field-selector=status.phase!=Running -o wide

# 4) 특정 Pod 이벤트/프로브 실패 확인
kubectl -n <ns> describe pod <pod-name>

# 5) Ingress 이벤트/ALB 컨트롤러 힌트
kubectl -n <ns> describe ingress <ingress-name>

# 6) (옵션) 클러스터 내부에서 Service로 직접 호출
kubectl -n <ns> run -it --rm curl --image=curlimages/curl --restart=Never -- \
  curl -sv http://<svc-name>.<ns>.svc.cluster.local:<port>/healthz

원인별 “가장 빠른 결론” 요약

  • Endpoints가 0개: 라벨/셀렉터 불일치, Pod Ready 0, Pod 생성 실패
  • Pod Ready 0: readinessProbe 실패(경로/포트/초기지연), 앱 미리스닝
  • K8s는 정상인데 503: ALB/NLB 타겟그룹 Unhealthy(헬스체크 path/port/protocol), SG/NACL로 헬스체크 차단
  • 가끔만 503: 특정 노드에만 Pod 없음(externalTrafficPolicy=Local), 롤링업데이트 중 readiness/liveness 튜닝 부족, 스케일 인/아웃 시 드레이닝 미흡

마무리: 503은 “백엔드 부재”를 먼저 의심하라

EKS에서 503은 대부분 애플리케이션 버그라기보다 라우팅 가능한 백엔드가 존재하지 않는 상태(Endpoints=0, Ready=0, TargetGroup unhealthy)에서 시작합니다. 따라서 진단의 우선순위를 “Pod 로그 보기”보다 Endpoints/Ready/헬스체크에 두면, 10분 안에 원인 범위를 거의 항상 좁힐 수 있습니다.

다음 단계로는 503이 “간헐적”일 때를 대비해 (1) 롤링업데이트 전략(maxUnavailable, preStop, terminationGracePeriod) (2) HPA/Cluster Autoscaler와의 상호작용 (3) ALB deregistration delay까지 함께 점검하면 재발률을 크게 낮출 수 있습니다.