- Published on
EKS에서 503 Service Unavailable 원인 10분 진단
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 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이 됩니다.
ContainerCreating에서 멈춤: CNI/CSI/권한 이슈가 흔함 → EKS Pod가 ContainerCreating에 멈출 때 10분 진단ImagePullBackOff 403(ECR 토큰/권한): → EKS ImagePullBackOff 403 - ECR 권한·토큰 만료 해결
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: 80→targetPort: 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까지 함께 점검하면 재발률을 크게 낮출 수 있습니다.