/인프라/kubectl get endpoints <none>·Service connection refused 5분 진단
인프라kubernetes serviceendpoints none

kubectl get endpoints <none>·Service connection refused 5분 진단

Kubernetes Service에 connection refused·no endpoints가 뜬다면 Endpoints가 비어있는 게 원인입니다. selector 불일치, label 오타, targetPort, Readiness 실패까지 6가지 원인을 kubectl 한 줄로 5분 만에 진단하는 법을 정리했습니다.

kubectl get endpoints <none>·Service connection refused 5분 진단

kubectl get endpoints가 비어있다(<none>)? Service connection refused·no endpoints 5분 진단 가이드

K8s_Troubleshooting_Guide 14편

Service를 만들고 curl service-name:80 했더니 connection refused 또는 무한 timeout. Pod는 분명히 Running인데 트래픽이 안 갑니다. 이 글은 그 막막한 순간을 위한 글입니다.

결론부터 말하면, 이 상황의 90%는 Service가 트래픽을 보낼 Pod 목록(Endpoints)이 비어있기 때문입니다. Service는 그저 가상 IP일 뿐이고, 실제로 패킷을 받아줄 Pod 주소 목록은 Endpoints(또는 EndpointSlice)에 들어갑니다. 이 목록이 비어있으면 트래픽은 갈 곳이 없어 그대로 거부됩니다.

이 글은 이름은 풀리는데(DNS는 정상) endpoint가 비어있는 케이스에 집중합니다. CoreDNS 이름 해석 실패는 별도 편에서 다룹니다.

일단 이 세 줄부터 치세요: 1차 진단 3종 세트

증상을 막연하게 추측하지 말고, 아래 세 명령어로 5분 안에 범위를 좁힙니다.

Bash
# ① 가장 먼저 — Endpoints가 비어있는가?
kubectl get endpoints my-svc

# ② Service의 selector와 포트 설정 확인
kubectl describe svc my-svc

# ③ Pod의 실제 label과 READY 상태 확인
kubectl get pods --show-labels

get endpoints 출력이 두 갈래 분기점입니다.

TEXT
# 비정상 — 여기가 문제의 시작
NAME     ENDPOINTS   AGE
my-svc   <none>      3m       ← ★ Endpoints가 비어있음 → 3장으로

# 정상 — IP가 채워져 있음
NAME     ENDPOINTS                         AGE
my-svc   10.244.1.7:80,10.244.2.4:80       3m   ← endpoint는 있는데 안 됨 → 4장으로
  • <none> 이면 → selector ↔ label 미스매치 또는 Readiness 실패 (아래 3장)
  • IP가 채워져 있는데 응답이 없으면 → targetPort 오타 / Service 타입 혼동 / kube-proxy (4장)

describe svc에서 읽을 것:

TEXT
Selector:          app=backend,tier=api    ← 이 라벨을 가진 Pod만 endpoint에 등록됨
Port:              80/TCP                   ← 클라이언트 접속 포트
TargetPort:        8080/TCP                 ← Pod로 전달할 포트 (★ 오타 단골)
Endpoints:         <none>                   ← 다시 한번 확인 사살

get pods --show-labels에서 읽을 것: READY 컬럼과 LABELS를 Selector와 눈으로 대조합니다.

Endpoints가 비어있다(<none>): selector·label·Readiness

원인 1 — selector ↔ Pod label 미스매치

가장 흔한 범인입니다. Service의 selector와 정확히 일치하는 label을 가진 Pod가 0개면 endpoint는 영원히 비어있습니다. 추측하지 말고 명령어로 확정합니다.

Bash
# Service가 찾고 있는 selector를 그대로 추출
kubectl get svc my-svc -o jsonpath='{.spec.selector}'
# → {"app":"backend","tier":"api"}

# 그 selector로 실제 매칭되는 Pod 수를 직접 세어본다
kubectl get pods -l app=backend,tier=api
# → No resources found.   ★ 0개면 selector 불일치 확정!

No resources found가 나오면 범인은 확정입니다. Pod에 붙은 라벨이 tier=backend인데 Service는 tier=api를 찾고 있거나, 오타가 났거나입니다.

before / after YAML

YAML
# ❌ before — selector 오타 (tier 값이 Pod와 다름)
apiVersion: v1
kind: Service
metadata:
  name: my-svc
spec:
  selector:
    app: backend
    tier: api        # ← Pod의 실제 라벨은 tier: backend
  ports:
    - port: 80
      targetPort: 80
YAML
# ✅ after — Pod 라벨과 정확히 일치
spec:
  selector:
    app: backend
    tier: backend    # ← 수정
Bash
kubectl apply -f my-svc.yaml
kubectl get endpoints my-svc
# NAME     ENDPOINTS                     AGE
# my-svc   10.244.1.7:80,10.244.2.4:80   ← 채워졌다!

원인 2 — Readiness Probe 실패로 endpoint에서 빠짐

selector는 맞는데도 <none>이라면, Pod가 Running이지만 READY 0/1 인 경우를 의심하세요. 쿠버네티스는 Readiness Probe를 통과하지 못한 Pod를 endpoint 목록에서 자동으로 제외합니다. 트래픽을 안전하게 차단하는 정상 동작이지만, probe 설정이 틀렸을 때 "Pod는 떠 있는데 Service는 빈손"인 상황을 만듭니다.

Bash
kubectl get pods
# NAME            READY   STATUS    RESTARTS   AGE
# backend-xxxxx   0/1     Running   0          2m   ★ Running인데 READY 0/1

kubectl describe pod backend-xxxxx
# Events:
#   Warning  Unhealthy  ...  Readiness probe failed: HTTP probe failed
#                            with statuscode: 404   ← probe path/port 오타

흔한 실수는 probe의 pathport가 실제 앱과 다른 것입니다. 예를 들어 앱은 /healthz를 8080에서 서비스하는데 probe는 /health를 8000으로 찌르고 있다면 영원히 404/refused가 나고 endpoint에 등록되지 않습니다. probe path와 port를 앱 실제 값과 맞추면 잠시 후 READY 1/1이 되고 endpoint가 채워집니다.

실무 경험 한 줄: 신규 배포 직후 5초쯤 endpoint가 비어 보이는 건 정상입니다(probe 첫 통과 대기). 하지만 1~2분이 지나도 <none>이면 거의 확실히 설정 문제입니다. watch kubectl get endpoints my-svc로 채워지는지 지켜보는 습관이 디버깅 시간을 절반으로 줄여줍니다.

Endpoints는 채워졌는데 응답이 없을 때

endpoint에 IP가 보이는데도 curl이 안 된다면 트래픽 경로의 포트Service 타입을 의심합니다.

원인 3 — targetPort 오타 (port 3형제 구분)

가장 헷갈리는 지점입니다. 포트가 세 군데 등장하는데 각각 의미가 다릅니다.

필드위치의미
portService.spec.portsService가 노출하는 포트(클라이언트가 접속)
targetPortService.spec.ports트래픽을 전달할 Pod의 포트
containerPortPod.spec.containers.ports컨테이너가 실제 리슨하는 포트

핵심 규칙: targetPort는 반드시 containerPort(=앱이 실제 리슨하는 포트)와 일치해야 합니다. nginx는 80을 리슨하는데 targetPort: 8080으로 적으면, endpoint는 10.244.x.x:8080을 가리키고 아무도 그 포트에서 듣지 않아 connection refused가 납니다.

YAML
# ❌ before — targetPort가 컨테이너 리슨 포트와 불일치
spec:
  ports:
    - port: 80
      targetPort: 8080   # ← nginx는 80을 리슨하는데 8080을 가리킴
YAML
# ✅ after
spec:
  ports:
    - port: 80
      targetPort: 80     # ← containerPort와 일치
Bash
kubectl apply -f my-svc.yaml
kubectl get endpoints my-svc
# my-svc   10.244.1.7:80,10.244.2.4:80   ← 포트가 :80으로 바뀜

원인 4 — Service 타입 혼동 + 클러스터 내부에서 격리 테스트

ClusterIP(기본값)는 클러스터 내부에서만 접근됩니다. 외부에서 직접 접속하려면 NodePort(노드 IP의 고정 포트로 노출)나 LoadBalancer(외부 LB 할당)가 필요합니다. "내 노트북에서 ClusterIP로 curl이 안 돼요"는 정상 동작인 경우가 많습니다.

진짜 문제인지 확인하려면 클러스터 내부에서 격리해 테스트하세요.

Bash
kubectl run tmp --rm -it --image=busybox -- wget -qO- my-svc:80

여기서 응답이 오면 Service 자체는 정상이고, 외부 접근 경로(타입/Ingress)만 손보면 됩니다.

원인 5 — kube-proxy / iptables (마지막 점검)

endpoint도 정상, label도 일치, 포트도 맞는데 안 된다면 노드의 트래픽 처리 계층을 봅니다.

Bash
kubectl -n kube-system get pods -l k8s-app=kube-proxy

kube-proxy가 CrashLoopBackOff거나, 노드에 ClusterIP를 실제 Pod로 보내는 iptables/IPVS 규칙이 누락되면 endpoint가 멀쩡해도 패킷이 길을 잃습니다. (이 깊은 영역은 별도 편에서 자세히 다룹니다.)

최신 환경 보너스: EndpointSlice와 서비스 메시

요즘 클러스터는 Endpoints 대신 EndpointSlice가 기본입니다. 함께 확인하세요.

Bash
kubectl get endpointslices -l kubernetes.io/service-name=my-svc

또한 Istio / Cilium 같은 서비스 메시·eBPF 환경에서는 kube-proxy 대신 eBPF 데이터플레인이 트래픽을 처리하므로, iptables 점검 대신 사이드카/CNI 레벨에서 진단 포인트가 달라진다는 점만 기억해 두세요.

결론: 원인별 5분 체크리스트

순서대로 배제하면 범인은 반드시 잡힙니다.

  1. kubectl get endpoints my-svc<none>인가, 채워졌는가?
  2. <none>이면get svc -o jsonpath='{.spec.selector}' vs get pods -l ... 로 매칭 0개인지 확인 (selector 불일치)
  3. selector 맞는데 <none>get podsREADY 0/1 + describe pod의 Readiness probe failed 확인
  4. 채워졌는데 안 됨port/targetPort/containerPort 3형제 일치 확인
  5. → Service 타입(ClusterIP는 내부 전용) + kubectl run tmp 내부 격리 테스트
  6. → 그래도 안 되면 kube-proxy 상태 + EndpointSlice 확인

자주 묻는 질문 (FAQ)

Q. Pod는 Running인데 endpoints가 비어있어요. 왜죠? A. READY 컬럼을 보세요. 0/1이면 Readiness Probe를 통과하지 못한 것이고, 쿠버네티스는 통과 못 한 Pod를 endpoint에서 자동 제외합니다. probe의 path/port를 앱 실제 값과 맞추면 채워집니다.

Q. selector는 맞는 것 같은데 endpoint가 0이에요. 어떻게 확정하나요? A. kubectl get pods -l <selector>로 직접 세어보세요. No resources found가 나오면 라벨 불일치가 확정입니다. 눈으로 비교하지 말고 selector 값을 그대로 -l에 넣어 명령어로 검증하는 게 정확합니다.

Q. endpoints에 IP는 보이는데 curl이 connection refused예요. A. targetPort가 컨테이너가 실제 리슨하는 포트와 다른지 확인하세요. endpoint 주소 뒤 포트(:8080 등)가 앱 리슨 포트와 다르면 거부됩니다. 또한 ClusterIP는 클러스터 내부에서만 동작하니 kubectl run tmp --rm -it --image=busybox -- wget -qO- my-svc:80으로 내부 테스트부터 하세요.


다음 편 예고: Ingress 404/503 — 요청이 백엔드까지 가는 길 디버깅. Service까지는 멀쩡한데 Ingress에서 막히는 경우를 추적합니다.

✦ ✦ ✦
편집 검토 · Editorial Review

이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.

작성 · Content Reviewer·검토 · 사람 편집자·발행 · 2026년 6월 17일

댓글

불러오는 중...