Published on

EKS에서 Pod는 뜨는데 Service Endpoints가 0일 때

Authors

서버가 살아있고 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/name vs app 혼용
  • 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은 재현도 쉽고 해결도 빠른 편의 장애로 바뀝니다.