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분 안에 범위를 좁힙니다.
# ① 가장 먼저 — Endpoints가 비어있는가?
kubectl get endpoints my-svc
# ② Service의 selector와 포트 설정 확인
kubectl describe svc my-svc
# ③ Pod의 실제 label과 READY 상태 확인
kubectl get pods --show-labels① get endpoints 출력이 두 갈래 분기점입니다.
# 비정상 — 여기가 문제의 시작
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에서 읽을 것:
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는 영원히 비어있습니다. 추측하지 말고 명령어로 확정합니다.
# 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
# ❌ 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# ✅ after — Pod 라벨과 정확히 일치
spec:
selector:
app: backend
tier: backend # ← 수정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는 빈손"인 상황을 만듭니다.
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의 path나 port가 실제 앱과 다른 것입니다. 예를 들어 앱은 /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형제 구분)
가장 헷갈리는 지점입니다. 포트가 세 군데 등장하는데 각각 의미가 다릅니다.
| 필드 | 위치 | 의미 |
|---|---|---|
port | Service.spec.ports | Service가 노출하는 포트(클라이언트가 접속) |
targetPort | Service.spec.ports | 트래픽을 전달할 Pod의 포트 |
containerPort | Pod.spec.containers.ports | 컨테이너가 실제 리슨하는 포트 |
핵심 규칙: targetPort는 반드시 containerPort(=앱이 실제 리슨하는 포트)와 일치해야 합니다. nginx는 80을 리슨하는데 targetPort: 8080으로 적으면, endpoint는 10.244.x.x:8080을 가리키고 아무도 그 포트에서 듣지 않아 connection refused가 납니다.
# ❌ before — targetPort가 컨테이너 리슨 포트와 불일치
spec:
ports:
- port: 80
targetPort: 8080 # ← nginx는 80을 리슨하는데 8080을 가리킴# ✅ after
spec:
ports:
- port: 80
targetPort: 80 # ← containerPort와 일치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이 안 돼요"는 정상 동작인 경우가 많습니다.
진짜 문제인지 확인하려면 클러스터 내부에서 격리해 테스트하세요.
kubectl run tmp --rm -it --image=busybox -- wget -qO- my-svc:80여기서 응답이 오면 Service 자체는 정상이고, 외부 접근 경로(타입/Ingress)만 손보면 됩니다.
원인 5 — kube-proxy / iptables (마지막 점검)
endpoint도 정상, label도 일치, 포트도 맞는데 안 된다면 노드의 트래픽 처리 계층을 봅니다.
kubectl -n kube-system get pods -l k8s-app=kube-proxykube-proxy가 CrashLoopBackOff거나, 노드에 ClusterIP를 실제 Pod로 보내는 iptables/IPVS 규칙이 누락되면 endpoint가 멀쩡해도 패킷이 길을 잃습니다. (이 깊은 영역은 별도 편에서 자세히 다룹니다.)
최신 환경 보너스: EndpointSlice와 서비스 메시
요즘 클러스터는 Endpoints 대신 EndpointSlice가 기본입니다. 함께 확인하세요.
kubectl get endpointslices -l kubernetes.io/service-name=my-svc또한 Istio / Cilium 같은 서비스 메시·eBPF 환경에서는 kube-proxy 대신 eBPF 데이터플레인이 트래픽을 처리하므로, iptables 점검 대신 사이드카/CNI 레벨에서 진단 포인트가 달라진다는 점만 기억해 두세요.
결론: 원인별 5분 체크리스트
순서대로 배제하면 범인은 반드시 잡힙니다.
kubectl get endpoints my-svc→<none>인가, 채워졌는가?<none>이면 →get svc -o jsonpath='{.spec.selector}'vsget pods -l ...로 매칭 0개인지 확인 (selector 불일치)- selector 맞는데
<none>→get pods의READY 0/1+describe pod의 Readiness probe failed 확인 - 채워졌는데 안 됨 →
port/targetPort/containerPort3형제 일치 확인 - → Service 타입(ClusterIP는 내부 전용) +
kubectl run tmp내부 격리 테스트 - → 그래도 안 되면 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에서 막히는 경우를 추적합니다.
이 글은 AI 에이전트가 1차 초안을 작성한 뒤, 사람 편집자가 사실관계·출처·톤과 맥락을 검토하여 발행했습니다. 오류나 부정확한 내용이 확인되면 24시간 이내에 정정합니다.
댓글
불러오는 중...