- Published on
EKS에서 Pod는 뜨는데 Service Endpoints가 0일 때
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서버가 살아있고 Pod도 Running인데, Service가 끝내 트래픽을 못 받는 상황은 EKS 운영에서 꽤 자주 만납니다. 특히 kubectl get endpoints(또는 EndpointSlice)가 비어 있으면, 외부에선 ALB/NLB가 5xx를 뿜고 내부에선 ClusterIP로도 연결이 안 됩니다.
핵심은 단순합니다.
- Service가 선택한 Pod가 없다(selector 불일치)
- 선택은 했지만 Ready가 아니다(readiness gate/Probe 실패)
- Ready인데도 포트 매핑이 틀렸다(port/targetPort/이름 불일치)
- 대상이 Pod가 아니라 다른 리소스다(ExternalName, 잘못된 namespace, headless 등)
아래 순서대로 보면 대부분 10~30분 내에 원인을 확정할 수 있습니다.
증상 재현: 무엇이 “Endpoints 0”인가
먼저 Endpoint가 정말 비어 있는지, EndpointSlice로 관리되는지부터 확인합니다.
# Service 확인
kubectl -n <ns> get svc <svc-name> -o wide
# Endpoints(구형 리소스) 확인
kubectl -n <ns> get endpoints <svc-name> -o yaml
# EndpointSlice(현행) 확인
kubectl -n <ns> get endpointslice -l kubernetes.io/service-name=<svc-name>
kubectl -n <ns> get endpointslice -l kubernetes.io/service-name=<svc-name> -o yaml
endpoints가 비어 있고endpointslice도 비어 있으면 컨트롤러가 대상 Pod를 못 찾는 것입니다(대개 selector/namespace).endpoints는 비어 있는데endpointslice에 주소가 있다면, 클러스터 버전/컨트롤 플레인 설정에 따라 조회 방식이 달라 보일 수 있습니다. 이 경우도 결국 **EndpointSlice의endpoints.conditions.ready**를 봐야 합니다.
1) 가장 흔한 원인: Service selector와 Pod label 불일치
Service는 selector로 Pod를 “선택”합니다. selector가 한 글자라도 다르면 Endpoints는 0입니다.
# Service selector 확인
kubectl -n <ns> get svc <svc-name> -o jsonpath='{.spec.selector}'
# Pod label 확인
kubectl -n <ns> get pods --show-labels
# selector로 실제 매칭되는 Pod가 있는지 즉시 검증
kubectl -n <ns> get pods -l app=myapp,tier=api -o wide
자주 하는 실수
- Helm/ArgoCD 템플릿에서
app.kubernetes.io/namevsapp혼용 - Deployment는
app=myapp인데 Service는app=my-app namespace를 다르게 배포(예: Service는default, Pod는prod)
빠른 수정 예시
Service selector를 Pod 라벨에 맞추거나, Pod 템플릿 라벨을 Service에 맞춥니다.
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp
ports:
- name: http
port: 80
targetPort: 8000
> 운영 중 변경 시 주의: selector를 바꾸면 순간적으로 트래픽 대상이 바뀌므로, 롤링/카나리 전략이 있으면 그 흐름에 맞춰 적용하세요.
2) Pod는 Running인데 Ready가 아니라서 제외되는 경우
Endpoints는 기본적으로 Ready Pod만 포함합니다. 그래서 Pod가 Running이어도 READY 0/1이면 Endpoints는 0이 됩니다.
kubectl -n <ns> get pods -o wide
kubectl -n <ns> describe pod <pod-name>
# readiness 상태만 뽑기
kubectl -n <ns> get pod <pod-name> -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}'
readinessProbe가 실패하는 대표 원인
- 앱이 실제로는
0.0.0.0이 아닌127.0.0.1에만 바인딩 - 컨테이너 포트는 8000인데 probe는 8080을 체크
/healthz가 인증/리다이렉트/DB 의존으로 느려져 타임아웃initialDelaySeconds가 너무 짧아 부팅 중 실패 반복
예: probe 포트/경로 정합성 점검
readinessProbe:
httpGet:
path: /healthz
port: 8000
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
publishNotReadyAddresses가 필요한 케이스
Stateful한 워크로드(예: DB, 일부 gRPC peer discovery)에서 Ready 전에도 주소가 필요할 수 있습니다. 이때는 headless Service와 함께 publishNotReadyAddresses: true를 고려합니다.
apiVersion: v1
kind: Service
metadata:
name: myapp-headless
spec:
clusterIP: None
publishNotReadyAddresses: true
selector:
app: myapp
ports:
- name: grpc
port: 50051
targetPort: 50051
단, 일반적인 HTTP API 서비스에서 이 옵션은 장애를 숨길 수 있으니 신중해야 합니다.
3) Service port/targetPort 불일치(이름 기반 매핑 포함)
Pod가 Ready인데도 통신이 안 될 때, Endpoints가 0이 아니라 “있는데도” 장애가 나는 경우가 많지만, 특정 조건에선 EndpointSlice에 포트가 비정상으로 기록되거나 기대와 다르게 매핑됩니다. 특히 targetPort를 문자열(이름) 로 쓸 때 실수가 잦습니다.
# Service 포트 정의 확인
kubectl -n <ns> get svc <svc-name> -o yaml
# Pod 컨테이너 포트 이름/번호 확인
kubectl -n <ns> get pod <pod-name> -o jsonpath='{.spec.containers[0].ports}'
흔한 실수 1: targetPort 이름 불일치
# Service
ports:
- name: http
port: 80
targetPort: web # 이름 기반
# Deployment
ports:
- name: http # 여기 이름이 web가 아니라 http면 매칭 실패
containerPort: 8000
해결: 이름을 맞추거나 숫자 포트로 고정합니다.
ports:
- name: http
port: 80
targetPort: 8000
흔한 실수 2: 프로토콜/포트가 다른데 probe만 통과
예를 들어 앱은 8000에서 뜨는데 Service가 8080으로 보내면, readiness가 8000을 보고 통과해도 실제 트래픽은 실패합니다. 이건 Endpoints 0은 아니지만, “서비스가 안 된다” 증상으로 같이 묶여 디버깅이 꼬입니다.
4) Service가 Pod를 대상으로 하지 않는 타입/설정
ExternalName
type: ExternalName이면 Endpoints가 생기지 않는 것이 정상입니다.
kubectl -n <ns> get svc <svc-name> -o jsonpath='{.spec.type}'
kubectl -n <ns> get svc <svc-name> -o yaml | sed -n '1,120p'
selector가 아예 없는 Service
selector가 없으면 Kubernetes가 자동으로 Endpoints를 만들지 않습니다(수동 Endpoints를 만들거나, 다른 컨트롤러가 붙여야 함).
kubectl -n <ns> get svc <svc-name> -o jsonpath='{.spec.selector}'
- 출력이 비어 있으면: 의도된 설계인지 확인하세요(예: 외부 DB를 ClusterIP로 감싸는 패턴).
5) 네임스페이스/컨텍스트 착각: “Pod는 뜨는데”가 다른 곳일 때
EKS에서 여러 namespace, 여러 context를 쓰면 가장 흔한 실수 중 하나가 “보고 있는 리소스가 서로 다른 곳”인 경우입니다.
kubectl config get-contexts
kubectl config current-context
kubectl get ns
# 같은 namespace에서 svc/pod를 같이 확인
kubectl -n <ns> get svc,pod -o wide
특히 CI/CD가 -n 없이 적용해 default에 Service가 생겼는데, Pod는 prod에 떠 있는 식이면 Endpoints는 0이 됩니다.
6) EndpointSlice 컨트롤러/권한/클러스터 이상 징후
대부분은 애플리케이션 매니페스트 문제지만, 드물게 컨트롤 플레인/애드온 이슈로 EndpointSlice 갱신이 지연되기도 합니다.
# kube-system에서 관련 컴포넌트 상태 확인
kubectl -n kube-system get pods
# 컨트롤러 로그는 EKS에선 직접 보기 어렵지만,
# 최소한 이벤트에서 단서를 찾을 수 있음
kubectl -n <ns> get events --sort-by=.lastTimestamp | tail -n 50
또, 노드/네트워크가 꼬여 kubectl logs/exec 자체가 불안정하면 원인 파악이 늦어집니다. 이 경우에는 아래 글의 체크리스트가 도움이 됩니다.
7) 실전: 30분 트러블슈팅 체크리스트(명령어 순서)
아래 순서로 실행하면 “Endpoints 0”의 원인을 거의 항상 좁힐 수 있습니다.
1) Service가 무엇을 선택하는지
kubectl -n <ns> get svc <svc-name> -o yaml | sed -n '1,160p'
spec.selector존재?spec.ports[].targetPort가 무엇?
2) selector로 Pod가 실제로 잡히는지
kubectl -n <ns> get pods -l <key>=<value> -o wide
0개면 selector/namespace 문제로 확정.
3) Pod Ready 여부
kubectl -n <ns> get pods -l <selector>
kubectl -n <ns> describe pod <pod-name> | sed -n '1,220p'
Readiness probe failed메시지 확인- 컨테이너가 재시작 중인지(
Restart Count)
4) EndpointSlice에 Ready가 찍히는지
kubectl -n <ns> get endpointslice -l kubernetes.io/service-name=<svc-name> -o yaml
endpoints: []면 선택 자체 실패conditions.ready: false면 readiness 문제
5) 직접 통신 테스트(클러스터 내부)
임시 디버그 Pod로 Service DNS와 Pod IP를 각각 찍어봅니다.
kubectl -n <ns> run netdebug --rm -it --image=ghcr.io/nicolaka/netshoot -- bash
# Service로
curl -sv http://<svc-name>.<ns>.svc.cluster.local:80/
# Pod IP로(엔드포인트가 생겼다면)
curl -sv http://<pod-ip>:8000/
Service로는 안 되고 Pod IP로는 되면: Service 포트/targetPort, selector, readiness 중 하나입니다.
8) 자주 나오는 케이스별 “정답” 패턴
케이스 A: Deployment 라벨은 app.kubernetes.io/name, Service는 app
- 해결: Service selector를 표준 라벨로 맞추기
selector:
app.kubernetes.io/name: myapp
케이스 B: readinessProbe가 /를 때리는데 302/401로 실패
- 해결: 인증 없는 헬스 엔드포인트 분리(
/healthz)
케이스 C: 컨테이너는 8000인데 Service targetPort가 80
- 해결:
targetPort: 8000으로 수정
케이스 D: Pod는 Ready인데도 외부(ALB)에서만 장애
- Endpoints 0이라면 Service 단계 문제지만,
- Endpoints가 정상인데도 외부에서만 실패하면 Ingress/ALB/보안그룹/헬스체크 경로 문제 가능성이 큽니다.
Cloudflare/프록시/ALB 로그 기반으로 빠르게 좁히는 방식은 아래 글이 참고가 됩니다.
9) 운영 팁: “Endpoints 0”을 더 빨리 감지하는 방법
- 배포 직후 자동 검증: CI에서
kubectl get endpointslice로 ready endpoint 수가 1 이상인지 확인 - 프로브 설계: readiness는 “외부 의존성(DB, S3 등)”을 과도하게 포함하지 말고, 최소한의 프로세스/핸들러 상태를 확인
- 라벨 표준화:
app.kubernetes.io/*라벨을 기준으로 Service/Deployment를 통일
EKS에서 권한/IRSA 등 다른 축의 문제로 애플리케이션이 정상 기동을 못 하면서 readiness가 계속 실패하는 경우도 있습니다(예: S3 접근 403으로 스타트업 단계에서 죽거나, healthz가 내부적으로 AWS 호출을 함). 그런 경우는 아래 글의 접근 방식처럼 “증상→원인 축소”를 빠르게 하는 게 유리합니다.
마무리
EKS에서 Pod가 Running인데 Service Endpoints가 0이면, 거의 항상 (1) selector 불일치 또는 (2) Ready 아님에서 출발합니다. 그 다음이 (3) port/targetPort(특히 이름 기반) 불일치, 그리고 (4) Service 타입/selector 없음/namespace 착각입니다.
문제 상황에서 중요한 건 “감으로 수정”이 아니라, selector → 매칭 Pod 존재 → Ready 여부 → EndpointSlice 조건 → 내부 curl 순으로 기계적으로 확인하는 것입니다. 이 루틴만 몸에 익히면, Endpoints 0은 재현도 쉽고 해결도 빠른 편의 장애로 바뀝니다.