Endpoints 有 IP 还是不通?Service 排查的 3 个隐蔽陷阱
场景:新部署 Nginx,Pod Running、Endpoints 有 IP,curl ClusterIP 超时——Service 所有 K8s 资源看起来都正常,但流量就是过不去 路径:Service 层 → Endpoints 检查 → kube-proxy 规则 → iptables 链 → 内核 conntrack K8s 版本:v1.28,kube-proxy iptables 模式(IPVS 模式在文中单独标注差异)
上篇讲 PDB 排查时用了一个关键思路:现象在 A 层,根因在 B 层。这次也一样——现象是 curl 超时,根因在 kube-proxy 的网络规则层,不在 Service 配置本身。
新部署上线,Service 建好了——kubectl get svc 显示 ClusterIP 和端口,kubectl get endpoints 也有后端 Pod IP。但 curl --connect-timeout 5 10.96.123.45:80 直接超时退出。Endpoints 有 IP,Pod 在 Running——那问题到底在哪?

【坐标】现象:三层"看起来正常"
三层假活 — 每个层面都在骗你
结论:Service 的 K8s 资源状态全部正常,但网络层不通——这是 kube-proxy 相关故障最迷惑人的特征。
Service 不通时,第一反应是查资源状态。看第一层:
$ kubectl get svc nginx
NAME TYPE CLUSTER-IP PORT(S) AGE
nginx ClusterIP 10.96.123.45 80/TCP 12m
ClusterIP 有、端口有——"Service 是好的"。看第二层:
$ kubectl get endpoints nginx
NAME ENDPOINTS AGE
nginx 10.244.1.5:80 12m
Endpoints 也有后端 IP——"后端也绑上了"。看第三层:
$ kubectl get pod -l app=nginx
NAME READY STATUS RESTARTS AGE
nginx-7c79c4b8f7-2xq9f 1/1 Running 0 12m
Pod Running——"应用也在跑"。三层都好,但测一下:
$ curl --connect-timeout 5 10.96.123.45:80
curl: (28) Connection timed out after 5000 milliseconds
这就是 Service 排查最典型的陷阱:资源层的 3 个信号都在告诉你"没问题",但网络层就是不转。 如果这时候你开始改 Service YAML 的 selector、调 port、或者干脆删了重建——方向就完全错了。
这 3 层假活对应的是同一个问题的 3 个表现面(Service 对象、Endpoints 对象、Pod 状态),而真正的排查层是另外 4 层(见下文分层排查表)。资源层的正常不等于网络层的正常,问题在 kube-proxy。

方向判断 — 三个排除法
遇到这种现象,先做三个快速排除:
# 排除 1:用 port-forward 直通 Pod(比改 Service type 安全)
$ kubectl port-forward pod/nginx-7c79c4b8f7-2xq9f 8080:80 &
$ curl localhost:8080
# 如果 port-forward 能通 → 应用本身正常,问题在 Service 转发链路
# 排除 2:Pod 是否真的在监听
$ kubectl exec nginx-7c79c4b8f7-2xq9f -- ss -tlnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 0.0.0.0:80 0.0.0.0:*
# 排除 3:直接访问 Pod IP
$ curl --connect-timeout 3 10.244.1.5:80
# 如果 Pod IP 能通 → 问题在这条链路上的中间环节
Pod IP 直接访问正常、但 ClusterIP 不通——说明 Pod 没问题、Service 对象没问题,中间链路(kube-proxy → 内核规则)有问题。

【分层】逐层排查:从 Service 到内核
排查顺序表
| 层次 | 排查对象 | 关键命令 | 预期正常 | 异常信号 |
|---|---|---|---|---|
| Layer 1 | Endpoints | kubectl get endpoints |
有正确 Pod IP | 空 Endpoints → selector 不匹配 |
| Layer 2 | kube-proxy 规则 | iptables-save -t nat \| grep <ClusterIP> |
有 KUBE-SVC + KUBE-SEP 链 | 无规则 → kube-proxy 没同步 |
| Layer 3 | kube-proxy 组件 | kubectl logs -n kube-system kube-proxy-xxx |
无 error/warning | "Failed to sync" 等错误 |
| Layer 4 | 内核 conntrack | conntrack -L -d <ClusterIP> |
有正确 DNAT 条目 | 条目满/无条目 |
Layer 1: Service 层 — Endpoints 确认
Service 层的关键不是看 Service YAML 对不对,是确认 Endpoints controller 是否找到了后端 Pod。Endpoints(端点列表,记录了 Service 后端 Pod 的 IP:Port)由 Endpoints Controller 自动维护,它的判断依据是 Service 的 selector 和 Pod 的 label 是否匹配。
kubectl get endpoints nginx 显示 10.244.1.5:80 — 说明匹配上了。如果为空,是 selector 问题,走另一个排查路径。
Endpoints 确认有后端后,下一步就问:kube-proxy 有没有根据这些 Endpoints 生成规则?
Layer 2: kube-proxy 规则层 — iptables 链结构
这是排查 Service 不通时最核心的一步。kube-proxy(Kubernetes 网络代理组件,运行在每个节点上,负责将 Service 的 ClusterIP 流量转发到后端 Pod)的 iptables 模式会在 NAT 表的 PREROUTING 和 OUTPUT 链注入规则,数据包到达节点后经过 DNAT(目的地址转换,将 ClusterIP 替换为后端 Pod IP)完成转发。
用 iptables-save 导出当前规则,过滤指定的 ClusterIP:
$ iptables-save -t nat | grep 10.96.123.45
-A KUBE-SERVICE -d 10.96.123.45/32 -p tcp -m tcp --dport 80 -j KUBE-SVC-XXXXX
-A KUBE-SVC-XXXXX -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-YYYY1
-A KUBE-SVC-XXXXX -j KUBE-SEP-YYYY2
-A KUBE-SEP-YYYY1 -p tcp -m tcp -j DNAT --to-destination 10.244.1.5:80
-A KUBE-SEP-YYYY2 -p tcp -m tcp -j DNAT --to-destination 10.244.1.2:80
正常的 iptables 规则应该有三段链条:
KUBE-SERVICE → 入口匹配(根据目标 IP + Port 跳入 Service 专用链)
↓
KUBE-SVC-XXXXX → 负载均衡(多 Pod 时按概率选择后端)
↓
KUBE-SEP-YYYY1 → DNAT(将 ClusterIP:Port 替换为真实的 Pod IP:Port)
异常判断:
| 输出现象 | 问题 | 根因 |
|---|---|---|
grep 10.96.123.45 无任何输出 |
kube-proxy 没有为这个 Service 生成规则 | kube-proxy 服务异常或配置错误 |
| 有 KUBE-SERVICE 链但没有 KUBE-SVC | 规则生成了一半,同步中断 | kube-proxy 内存不足或 API 连接中断 |
| 有 KUBE-SVC 但没有 KUBE-SEP | Endpoints 更新未反映到规则 | Endpoints 变更后 kube-proxy 未触发同步 |
Layer 3: kube-proxy 组件层 — 日志排查
iptables 规则为空,接下来查 kube-proxy 本身:
# 查看 kube-proxy Pod
$ kubectl get pods -n kube-system -l k8s-app=kube-proxy
NAME READY STATUS RESTARTS AGE
kube-proxy-8n7k2 1/1 Running 0 3h
kube-proxy-lmjkc 1/1 Running 0 3h
# 查看配置模式
$ kubectl describe configmap kube-proxy -n kube-system | grep -A 5 "mode:"
iptables:
masqueradeAll: false
masqueradeBit: 14
minSyncPeriod: 0s
syncPeriod: 30s
mode: "iptables"
kube-proxy 通过 ConfigMap(集中配置 K8s 组件的 Key-Value 配置对象)kube-proxy 来配置运行模式。mode: "iptables" 是默认模式,mode: "ipvs" 是高性能模式。
看 kube-proxy 日志中是否有同步错误:
$ kubectl logs -n kube-system kube-proxy-8n7k2 --tail=50
I0710 10:15:23.456789 1 server.go:650] "Using iptables proxy"
I0710 10:15:23.567890 1 server_linux.go:63] "Detected node IP" ip="192.168.1.10"
W0710 10:15:25.123456 1 iptables.go:178] "Failed to sync iptables rules" err="could not connect to api server"
关键日志信号:
| 日志 | 含义 |
|---|---|
Failed to sync iptables rules |
kube-proxy 无法同步 iptables 规则 |
could not connect to api server |
kube-proxy 无法连接 API Server,可能网络不通或证书过期 |
Detected node IP 与实际不符 |
kube-proxy 用的节点 IP 不对,影响 IPVS 模式的绑定 |
Using iptables proxy |
确认当前运行模式 |
如果 kube-proxy 日志显示 Using iptables proxy 且没有错误,但规则确实为空——可能需要触发一次手动同步。

Layer 4: 内核 conntrack 层
iptables 规则正确,kube-proxy 日志无异常,但依然不通——那问题可能在内核的连接跟踪表(conntrack,Linux 内核的连接跟踪子系统,记录所有经过 NAT 的网络连接状态)。conntrack 表满时新连接无法建立 DNAT。
# 查看 conntrack 统计
$ conntrack -S
cpu=0 found=0 invalid=0 insert=0 insert_failed=0 drop=0 early_drop=0 error=0
cpu=1 found=0 invalid=0 insert=0 insert_failed=0 drop=0 early_drop=0 error=384
cpu=2 found=0 invalid=0 insert=0 insert_failed=0 drop=0 early_drop=0 error=0
cpu=3 found=0 invalid=0 insert=0 insert_failed=0 drop=0 early_drop=0 error=0
# 查看 conntrack 最大条目数和当前使用量
$ sysctl net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_max = 262144
$ sysctl net.netfilter.nf_conntrack_count
net.netfilter.nf_conntrack_count = 262144
insert_failed 非零 + conntrack_count 等于 conntrack_max = conntrack 表满了。此时新连接无法做 NAT,表现就是 curl 超时,但已有连接不受影响。
验证方法:临时加大 conntrack 上限后重试:
$ sysctl -w net.netfilter.nf_conntrack_max=524288
$ curl --connect-timeout 3 10.96.123.45:80
# 如果通 → 确认是 conntrack 表满

【路径】🔍 下次先跑这个命令
Service 不通的 4 步排查链
命令和输出见上方的截图 code-troubleshooting-cmds.png,这里是核心逻辑:
- Step 1 —
kubectl get endpoints,确保有后端 IP - Step 2 —
iptables-save -t nat | grep <ClusterIP>,确认 KUBE-SERVICE → KUBE-SVC → KUBE-SEP 三段链完整 - Step 3 —
ipvsadm -Ln -t <ClusterIP>:<Port>(IPVS 模式),查 VirtualServer + RealServer - Step 4 —
conntrack -S | grep insert_failed,确认 conntrack 未满
异常判断快速表
| 命令 | 正常输出关键词 | 异常信号 | 下一步 |
|---|---|---|---|
kubectl get endpoints |
10.244.x.x:port |
空 | 检查 Service selector |
iptables-save \| grep ClusterIP |
KUBE-SVC-、KUBE-SEP- |
无输出 | kubectl logs kube-proxy-xxx |
ipvsadm -Ln |
-> 10.244.x.x:port |
无 RealServer | 检查 Pod ready 状态 |
conntrack -S |
insert_failed=0 |
insert_failed>0 |
扩容或调整 nf_conntrack_max |
kubectl logs kube-proxy-xxx |
Using iptables proxy |
Failed to sync |
检查 API Server 连通性 |

【定位】最常见的误判
❌ 错误排查方向
直觉反应:Service 不通 → Service YAML 写错了 → 改 selector/port/type → 重建 Service。
这样排查的人通常的逻辑链是:
curl ClusterIP 不通
→ kubectl delete svc nginx
→ kubectl expose ...(换了个端口或者加了个 type)
→ curl 还是不通
→ "K8s 的 Service 是不是有 bug?"
为什么这个路径是错的:kubectl get endpoints 已经告诉你 Service 的 selector 匹配成功了。Endpoints 有后端 IP 说明 Service 对象的配置正确。这时候改 YAML 是在解决一个不存在的问题。
✅ 正确排查方向
正确逻辑链:
curl ClusterIP 不通
→ kubectl get endpoints → 确认有后端(排除 Service 配置问题)
→ iptables-save -t nat | grep <ClusterIP> → 确认规则
→ 规则为空 → kubectl logs kube-proxy-xxx → 找同步错误
→ 规则正常 → conntrack -S | grep insert_failed → 确认 conntrack 是否满
→ 定位根因 → 修复
两者的本质差异:错误方向在 K8s 资源层打转,正确方向在网络规则层深挖。 排查 K8s 问题不是在查 Pod——是在查 Pod 所在的整条链路。Service 的 YAML 只是声明了意图,真正执行转发的是 kube-proxy 驱动的内核规则。

【标点】修复方案 + Check-list
修复方案
根据根因不同,修复路径不同:
场景 A:iptables 规则不存在 → 重启 kube-proxy
# 重启 kube-proxy DaemonSet
$ kubectl rollout restart -n kube-system daemonset kube-proxy
daemonset.apps/kube-proxy restarted
# 等待重启完成
$ kubectl rollout status -n kube-system daemonset/kube-proxy
daemonset "kube-proxy" successfully rolled out
# 验证规则已生成
$ iptables-save -t nat | grep 10.96.123.45
场景 B:kube-proxy 配置模式与实际不符
# 查看当前模式配置
$ kubectl describe configmap kube-proxy -n kube-system | grep "mode:"
mode: ""
# mode 为空 → 实际使用 iptables 模式,但 ConfigMap 没显式声明
# 修改为显式 iptables 模式
$ kubectl edit configmap kube-proxy -n kube-system
# 在 iptables 段下添加 mode: "iptables"
# 重启 kube-proxy 使配置生效
$ kubectl rollout restart -n kube-system daemonset kube-proxy
场景 C:conntrack 表满 → 扩容
# 临时扩容
$ sysctl -w net.netfilter.nf_conntrack_max=524288
# 持久化配置
$ cat <<EOF | tee /etc/sysctl.d/99-conntrack.conf
net.netfilter.nf_conntrack_max = 524288
net.netfilter.nf_conntrack_buckets = 131072
EOF
$ sysctl -p /etc/sysctl.d/99-conntrack.conf
场景 D:IPVS 模式降级
一个特别隐蔽的场景:kube-proxy 配置了 IPVS 模式,但内核模块 ip_vs 未加载——kube-proxy 静默降级到 iptables,不会报错、不会 CrashLoopBackOff。你查了 ipvsadm -Ln 没规则,但 iptables-save -t nat 却有。不是 Service 不通,是你查错了地方。
# 确认是否降级
$ kubectl logs -n kube-system kube-proxy-xxx | grep "IPVS"
W0710 10:15:25.123 proxier.go:247] "IPVS is not supported, falling back to iptables"
# 加载模块(还要在节点上持久化 /etc/modules-load.d/k8s-ipvs.conf)
$ modprobe ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack_ipv4

Service 不通排查 Check-list
每条对应一个命令,拷贝即用:
□ kubectl get svc <name>
→ Service 存在且 ClusterIP/IP 不为空
□ kubectl get endpoints <name>
→ Endpoints 有后端 IP:Port(空 → 查 selector 匹配)
□ kubectl get pod -l app=<label> -o wide
→ Pod Running,IP 与 Endpoints 一致
□ kubectl logs -n kube-system -l k8s-app=kube-proxy --tail=20
→ 无 "Failed to sync" 错误
□ iptables-save -t nat | grep <ClusterIP>
→ 有 KUBE-SERVICE → KUBE-SVC → KUBE-SEP 三段链
□ ipvsadm -Ln | grep <ClusterIP> # IPVS 模式
→ 有 VirtualServer + RealServer
□ sysctl net.netfilter.nf_conntrack_count
sysctl net.netfilter.nf_conntrack_max
→ count < max(<80% 为安全水位)
□ curl --connect-timeout 3 <PodIP>:<Port>
→ 直接访问 Pod IP 正常(排除应用本身问题)
附:完整命令清单
# === 1. Service 状态 ===
kubectl get svc nginx
kubectl get endpoints nginx
kubectl describe svc nginx
kubectl get pod -l app=nginx -o wide
# === 2. iptables 规则 ===
iptables-save -t nat | grep 10.96.123.45
iptables -t nat -L KUBE-SERVICE -n | head -20
# === 3. IPVS 规则(IPVS 模式) ===
ipvsadm -Ln -t 10.96.123.45:80
ipvsadm -Ln -c | grep 10.96.123.45
# === 4. kube-proxy 排查 ===
kubectl get pods -n kube-system -l k8s-app=kube-proxy
kubectl logs -n kube-system kube-proxy-xxx --tail=50
kubectl describe configmap kube-proxy -n kube-system
# === 5. conntrack ===
conntrack -S
conntrack -L -d 10.96.123.45
sysctl net.netfilter.nf_conntrack_max
sysctl net.netfilter.nf_conntrack_count
# === 6. 内核实测 ===
curl --connect-timeout 3 10.244.1.5:80 # Pod IP 直连
curl --connect-timeout 3 10.96.123.45:80 # ClusterIP
ss -tlnp # Pod 内监听状态
# === 7. 修复 ===
kubectl rollout restart -n kube-system daemonset kube-proxy
sysctl -w net.netfilter.nf_conntrack_max=524288
modprobe ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh nf_conntrack_ipv4
下篇我们聊 Ingress 配置错误——同样是"建好了但访问不了",Ingress 的排查逻辑完全不同。Service 的流量转发靠 kube-proxy 管,Ingress 的入口路由靠 Ingress Controller 管。排查入口换了,排查工具链也要换。
🔗 个人博客:https://opencao.cn 📺 公众号:Ai拆代码的曹操 🌟 知识星球:Ai拆代码的曹操