前端 Pod 里 curl 一个内部 Service,返回 Connection refused 或者干脆超时,可后端 Pod 明明 kubectl get pods 显示 Running。这种情况先别去翻 CNI 日志,也别怀疑 CoreDNS——直接看 Endpoints 是不是空的。Service 本身不转发流量,它只是一组转发规则,规则背后没有 Endpoints,请求就无处可去。这一条命令能筛掉八成的「Service 访问不通」。
一条命令定生死
kubectl get endpoints my-svc -n production
输出如果是这样,问题已经找到了:
NAME ENDPOINTS AGE
my-svc <none> 6d
ENDPOINTS 列空着,说明没有任何 Pod 被挂到这个 Service 上。反过来,如果看到 10.244.2.17:8080,10.244.3.9:8080 这样的地址列表,说明后端关联是好的,问题在更下游(kube-proxy、NetworkPolicy 或应用本身),排查方向完全不同。
新版集群也可以看 EndpointSlice,信息更细:
kubectl get endpointslice -n production -l kubernetes.io/service-name=my-svc -o wide
| Endpoints 状态 | 说明 | 往哪儿查 |
|---|---|---|
| <none> | 没有 Pod 被关联 | 标签选择器 / 就绪探针 |
| 有 IP 但端口不对 | targetPort 写错 | 对比 containerPort |
| IP 齐全但仍不通 | 转发层或策略拦截 | NetworkPolicy / kube-proxy |
| IP 是旧的 | Pod 已重建但缓存未更新 | DNS 缓存 / controller 健康 |
Endpoints 为空的两个根因
第一个是标签对不上。Service 的 spec.selector 和 Pod 的 labels 必须逐字相同,大小写、连字符差一个都不行。
kubectl describe svc my-svc -n production | grep -A2 Selector
kubectl get pods -n production --show-labels
常见的翻车方式是 Deployment 里 app: my-api,Service 里写成 app: myapi,或者一个用 app 一个用 app.kubernetes.io/name。眼睛看容易漏,用选择器反查更可靠:
kubectl get pods -n production -l app=my-api
# 返回 No resources found 就说明选择器根本没匹配到东西
第二个根因更隐蔽:标签明明对得上,Endpoints 还是空。这时候看 Pod 的 READY 列——如果是 0/1,Kubernetes 认为它没准备好,就不会把它挂到 Endpoints 上。
kubectl get pods -n production
NAME READY STATUS RESTARTS AGE
my-api-7d9f8b6c4-x2klp 0/1 Running 0 4m
Running 但 0/1,是就绪探针在失败。kubectl describe pod 的 Events 里会有 Readiness probe failed 字样,后面跟着具体原因:路径 404、端口不通、或者超时。
踩坑实录
有次上线一个 FastAPI 服务,selector 和 labels 我逐字比对过三遍,确认没问题,Endpoints 依然 <none>。Pod 是 0/1 Running,describe 出来的是 Readiness probe failed: connection refused。我以为是 initialDelaySeconds: 10 太短,加到 60 秒,重启后照旧 refused。进容器一跑 netstat -tlnp 才看明白:应用监听的是 127.0.0.1:8000,不是 0.0.0.0:8000。kubelet 从 Pod IP 发起探测,打不到回环地址上的服务。启动命令里 uvicorn main:app --host 0.0.0.0 补上 --host,Pod 三秒变 1/1,Endpoints 自动补齐。探针 refused 优先怀疑监听地址,不是延迟不够。
端口三兄弟别搞混
Endpoints 有 IP 但访问依然不通,多半是端口关系写反了。Service 里三个端口字段各管一段:
| 字段 | 含义 | 必须等于 |
|---|---|---|
| port | Service 对外暴露的端口 | 调用方 curl 的端口 |
| targetPort | 转发到容器的端口 | containerPort / 实际监听端口 |
| nodePort | 节点上开的端口 | 仅 NodePort 类型需要 |
验证办法是绕过 Service 直连 Pod IP,把变量减到一个:
# 拿到 Pod IP
kubectl get pod my-api-7d9f8b6c4-x2klp -n production -o jsonpath='{.status.podIP}'
# 从调试 Pod 直连
kubectl run tmp-debug --rm -it --image=nicolaka/netshoot -n production -- \
curl -sv --max-time 3 http://10.244.2.17:8080/health
直连 Pod IP 通、走 Service 不通,问题就锁定在 Service 定义或转发层。直连也不通,那就是应用自己的事,跟 Kubernetes 没关系。
还是不通就往下游走
Endpoints 齐全、直连 Pod 也通,剩下三个地方按顺序查。
先验 NetworkPolicy。集群用了 Calico 或 Cilium 且有默认拒绝策略的话,合法流量也会被挡:
kubectl get networkpolicy -n production
kubectl describe networkpolicy default-deny -n production
顺带一个判断技巧:Flannel 不执行网络策略。如果你的 CNI 是 Flannel,那问题一定不在 NetworkPolicy 上,可以直接跳过这一环。
再看 kube-proxy。它挂了 iptables/IPVS 规则就不会生成:
kubectl get pods -n kube-system -l k8s-app=kube-proxy
kubectl logs -n kube-system -l k8s-app=kube-proxy --tail=50 | grep -i error
最后查 DNS。nslookup my-svc 在 Pod 里解析不出来,是 CoreDNS 的问题,跟 Endpoints 无关:
kubectl exec -it tmp-debug -n production -- nslookup my-svc.production.svc.cluster.local
常见问题
问:Pod 显示 Running,为什么还会被排除在 Endpoints 之外?
Running 只表示容器进程活着,Endpoints 认的是 Ready 状态。就绪探针不通过,Pod 就是 0/1 Running,Kubernetes 不会给它转发流量——这是设计如此,避免请求打到还没初始化完的实例上。
问:临时想让 Pod 挂上 Endpoints,能不能先把探针删掉?
可以,作为定位手段。删掉 readinessProbe 后 Pod 立刻 Ready、Endpoints 补齐,就证明是探针配置问题而不是标签问题。但别把这个当修复方案留在生产里,探针没了滚动更新时会把流量打到没起来的 Pod 上。
问:Endpoints 里的 IP 是已经删掉的旧 Pod,怎么办?
检查 kube-controller-manager 是否健康,endpoints controller 由它驱动。另外确认调用方有没有自己做 DNS 缓存或长连接池——某些客户端会把解析结果缓存很久,Pod 换了 IP 它还在打旧地址。
排到哪一步卡住了,把 kubectl get endpoints 和 describe pod 的输出贴到 fenij.com 评论区,我们接着往下看。