故障时间 :2026-08-06 上午
影响范围 :互联网医院系统测试环境(devcucc7000.xxx.cn),titan-cms、titan-doctor 服务持续 502,影响测试人员测试与交付进度!!
恢复时间 :全量重启 kube-proxy 后恢复
核心根因:kube-proxy 未能为 service-titan-zuul 生成 iptables/IPVS 转发规则,导致 ClusterIP 成为网络黑洞

一、背景:医生pc端业务突然大面积 502
当天上午,互联网医院系统测试环境访问异常【其实这种网络问题一般是运维来处理,但是我们的运维最近太忙了,只能自己先排查,给运维一个方向。第一次遇见这个kube-proxy 网络问题,k8s自己忘得差不多了,知识不用就容易忘啊,还好有AI。花了2天才最终确认问题,多亏是测试环境!!!如果是生产,运维还不在的情况,岂不是炸了!!! 】。查看 Nginx access.log,titan-cms 和 titan-doctor 相关接口持续返回 502 Bad Gateway,典型请求路径包括:
bash
GET /api-ih/titan-cms/imSendMessage/doctorHeartbeat?doctorId=3160
GET /api-ih/titan-doctor/consultingRoom/counselDetailList/
POST /api-ih/titan-member/tenant/reg/list
环境信息:
- 网关:Tengine 2.3.2,跑在 K8s 节点(132.xx.xx.81、132.xx.xx.244)的 Docker 容器内
- Nginx upstream 配置使用的是 K8s Service 域名:
service-titan-zuul.thc-dev-bjhlwyy-devihmb1.svc.unprod.bjhlwyy.xxxx.xxxx:9000 - DNS 解析结果:10.xx.xx.210:9000
- Zuul Pod:
app-titan-zuul-0,实际 Pod IP 为 10.xx.xx.152 
奇怪的是,member 服务看起来是正常的。为什么只有 cms 和 doctor 炸?这个疑问一直贯穿整个排查过程。
二、排查过程:从 Nginx 到 K8s 网络层的逐层剥离
2.1初步排查
cms服务我在kuboard(K8s的可视化管理界面)上看到是正常运行的啊,也能看到启动日志和一些定时任务日志的触发?只不过说不健康,刚开始怀疑是这个问题导致的:
- Readiness Probe 失败 → K8s 认为这个 Pod 没准备好,会把它从 Service 的 Endpoints 中移除
- 结果就是:流量不会被转发到这个 Pod,直到探针恢复正常

- 但是当我触发接口的时候,发现请求根本没到cms服务。会不会是cms服务的代码有问题,导致测试环境cms服务没有真正起来?那本地起一下试试?

-
本地起完发现都没问题,cms服务也注册到eureka上了?等等,那会不会是线上服务没有注册到eureka上?
-
会不会是服务没有注册到eureka上,导致服务不可达出现502?【经过排查,测试环境的cms服务也注册到eureka上了!!!我丢,那这是啥原因啊!!!】
-
那会不会是zuul报的异常:502 Bad Gateway?
-
但是经过排查,发现请求也没有到zuul!!!在zuul网关中没有查到相关日志!!!无解了!!
-
哎,既然请求没有到cms,没有到zuul?那再往前推一层,会不会是nginx的问题啊【之前重启zuul网关的时候,发现每次重启zuul的pod节点ip都会变化,不会是nginx中用了硬编码了吧?写死ip,一直访问旧的zuul的IP?不会吧,不能这么蠢吧!!!!】?看看nginx中有没有异常日志!!!!
2.2 第一步:确认 Nginx 层
先找 Nginx 进程,确认它跑在容器里:
bash
ps -ef | grep nginx
输出:

bash
root 64233 64218 0 2025 ? 00:00:00 nginx: master process /opt/container/instances/nginx/sbin/nginx -g daemon off;
通过 /proc/<pid>/cgroup 确认是 K8s Pod,
bash
cat /proc/19265/cgroup
bash
kubepods-burstable-pod250dcfbd_4d45_4558_b355_c04b5603756c.slice/docker-6f243f01afbfa970...
用 docker exec 进入容器。
bash
docker exec -it 6f243f01afbfa970 bash
查 upstream 配置:
bash
grep -r "titan-zuul" conf/
发现配置没问题,upstream 用的是 Service 域名 ,不是硬编码 IP【还好没这么蠢】:
nginx
upstream usm_thc-dev-bjhlwyy-devihmb1_everxxxxx.cn_titan-zuul {
server service-titan-zuul.thc-dev-bjhlwyy-devihmb1.svc.unprod.bjhlwyy.xxxx.thc:9000;
}
2.3 第二步:Nginx 日志暴露 upstream 不可达
(1)分析access.log:
bash
grep -E "titan-cms|titan-doctor" access.log | tail -n 20
access.log 里所有 502 请求的 upstream 地址都是 10.xx.xx.210:9000,响应时间从 0.078s 到 5.850s 不等:
bash
2026-08-06T09:27:18+08:00||132.xx.xx.94||...||GET||https||devcucc7000.everxxxxx.cn
||/api-ih/titan-cms/imSendMessage/doctorHeartbeat?doctorId=13120052
||502||0.336||10.xx.xx.210:9000||502||0.337
||<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head><title>502 Bad Gateway</title></head>
<body><center><h1>502 Bad Gateway</h1></center>
<hr/>Powered by Tengine/2.3.2</body></html>
2026-08-06T09:27:18+08:00||132.xx.xx.94||...||GET||https||devcucc7000.everxxxxxx.cn
||/api-ih/titan-cms/imSendMessage/doctorHeartbeat?doctorId=3160
||502||3.374||10.xx.xx.210:9000||502||3.375
观察:
- 所有请求状态码均为 502
- upstream 地址统一为 10.xx.xx.210:9000
- 响应时间 0.078s ~ 5.850s 不等(部分快速失败,部分超时)
(2)分析 error.log:
bash
tail -n 100 error.log | grep -i "502\|upstream\|titan-cms\|titan-doctor"
关键错误信息:
bash
[error] connect() failed (113: No route to host) while connecting to upstream,
client: 132.xx.xx.94,
server: ~^(devihmb1.everxxxxx.cn|NULL)$,
request: "GET /api-ih/titan-cms/imSendMessage/doctorHeartbeat?doctorId=3160 HTTP/1.0",
upstream: "http://10.57.163.210:9000/titan-cms/imSendMessage/doctorHeartbeat?doctorId=3160",
host: "devcucc7000.everxxxx.cn"
error.log 里两种错误交替出现:

connect() failed (113: No route to host) while connecting to upstream
connect() failed (110: Connection timed out) while connecting to upstream
这说明 Nginx 连不上 10.xx.xx.210:9000。
2.4 第三步:网络层验证------能 ping 通,但端口完全拒绝
在 Nginx 容器所在节点测试:网络层可达,ping 正常。
bash
ping 10.xx.xx.210
# 64 bytes from 10.xx.xx.210: icmp_seq=1 ttl=64 time=0.052 ms
# 13 packets transmitted, 13 received, 0% packet loss
ping 正常,但 telnet 端口直接暴毙:
bash
# 节点 132.xx.xx.81
telnet 10.x.xx.210 9000
# telnet: connect to address 10.xx.xx.210: No route to host
# 节点 132.xx.xx.244
telnet 10.xx.xx.210 9000
# telnet: connect to address 10.xx.xx.210: Connection timed out
这时候我怀疑是不是防火墙或者网络策略出了问题,但其他服务又没问题,所以暂时没往全局网络故障上想。
2.5 第四步:DNS 解析没问题,但 IP 身份存疑
在 Nginx 容器内解析域名:
bash
getent hosts service-titan-zuul.thc-dev-bjhlwyy-devixxx.svc.unprod.xxx.kuber.thc
输出:
bash
10.xx.xx.210 service-titan-zuul.thc-dev-bjhlwyy-devihxxx.svc.unprod.xxx.kuber.thc
DNS 解析结果和日志里报错的ip一致,说明不是 Nginx DNS 缓存的问题。
2.6 第五步:进 Zuul Pod 验证服务本身
(1)进入 app-titan-zuul-0:
bash
hostname -i
# 10.xx.xx.152
curl -v --connect-timeout 5 http://localhost:9000/
# < HTTP/1.1 404
# {"timestamp":"2026-08-06 09:59:26","status":404,"error":"Not Found",...}

404 是正常响应,说明 Zuul 服务本身活着,端口也开着。
(2)从 Nginx 容器直接 curl Pod IP:
bash
curl -v --connect-timeout 5 http://10.xx.xx.152:9000/
# Connected to 10.xx.xx.152 (10.xx.xx.152) port 9000
# < HTTP/1.1 404

Pod IP152 直接访问完全正常,但 ClusterIP 210就是不通。

三、踩坑记录:被 ClusterIP 和 Pod IP 的概念搞混了
坑一:误把 ClusterIP 当成"旧 Pod IP"
一开始我写的第一版报告,根因分析是这么写的:
"K8s Service 的 Endpoints 指向了一个已不存在的旧 Pod IP
10.xx.x.210,而当前实际运行的 Zuul Pod IP 为10.x.xx.152。"
这个推断是错的【运维人员还是很专业的,虽然运维没空处理这个问题,但是运维一眼便看出来你这个方向是有问题的!!!!没办法,继续排查,找到问题再给运维确认!!!!】。
后来在 Pod 里查看环境变量,直接被打脸:
bash
env | grep -i "TITAN_ZUUL"
输出:
SERVICE_TITAN_ZUUL_SERVICE_HOST=10.xx.xx.210
SERVICE_TITAN_ZUUL_PORT=tcp://10.xx.xx.210:9000
SERVICE_TITAN_ZUUL_PORT_9000_TCP_ADDR=10.xx.xx.210
10.xx.xx.210 是 Service 的 ClusterIP,不是 Pod IP。 ClusterIP 是 K8s 分配的虚拟 IP,不会随 Pod 重启而变化。真正的问题是 ClusterIP 背后的转发规则没有指向当前 Pod,而不是"IP 变旧了"。
坑二:在精简容器里找网络工具,浪费了不少时间服务器我没权限啊,运维只给开了这个权限!!!
Zuul 的容器镜像是精简版,没有 ip、ss、tcpdump、nslookup 这些工具。我一开始还想在 Pod 里抓包、看连接状态,结果:
bash
ss -tlnp | grep 9000
# bash: ss: command not found
tcpdump -i any -n port 9000 -c 20
# bash: tcpdump: command not found
后来才反应过来,这种环境只能靠 /dev/tcp 做端口探测,或者用 curl + timeout 这种最原始的方式验证。
坑三:member 为什么正常?一开始没想清楚
这是整个排查过程中最大的疑问。如果所有流量都走同一个 zuul,member 也应该 502 才对。
我在 Pod 里测试了各个 Service 的连通性:
bash
# member 的 Service
curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 \
http://service-titan-member.thc-dev-bjhlwyy-xxxx.svc.unprod.bjhlwyy.kuber.thc:9000/
# 200
# cms 的 Service
curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 \
http://service-titan-cms.thc-dev-bjhlwyy-xxxx.svc.unprod.bjhlwyy.kuber.thc:9000/
# 000 FAIL
member 的 Service 通,cms 的 Service 也不通。 这说明不只是 zuul 一个服务有问题,cms 的 ClusterIP 同样成了黑洞。
但 Nginx 层 member 接口正常,大概率是因为 member 在 Nginx 配置里不经过 zuul?,直接走了自己的 upstream。这个点虽然没影响最终根因定位,但确实干扰了早期的判断------让我一度以为故障是因为nginx的配置出了问题(例如:会不会是硬编码啊?会不会cms服务与member 走的不是同一个zuul啊?会不会member 根本没走zuul?打住,这些判断通通错误!!!)
四、根因定位:kube-proxy 丢了转发规则
4.1 关键证据:只有特定 ClusterIP 不通
我在 Zuul Pod 内部用 /dev/tcp 做了一组对比测试**【这个是确认问题的关键!!不容易啊,终于让我找到问题了!!!】**:
bash
# K8s API Server(ClusterIP)
timeout 3 bash -c 'echo > /dev/tcp/10.xx.xx.1/443' && echo "API Server: 通" || echo "API Server: 不通"
# API Server: 通
# CoreDNS(ClusterIP)
timeout 3 bash -c 'echo > /dev/tcp/10.xx.xx.2/53' && echo "CoreDNS: 通" || echo "CoreDNS: 不通"
# CoreDNS: 通
# Zuul Service(ClusterIP)
timeout 3 bash -c 'echo > /dev/tcp/10.xx.xx.210/9000' && echo "Zuul Service: 通" || echo "Zuul Service: 不通"
# bash: connect: No route to host
# Zuul Service: 不通
# Pod 自身 IP
timeout 3 bash -c 'echo > /dev/tcp/10.xx.xx.153/9000' && echo "Pod自身IP: 通" || echo "Pod自身IP: 不通"
# Pod自身IP: 通
# localhost
timeout 3 bash -c 'echo > /dev/tcp/127.0.0.1/9000' && echo "localhost: 通" || echo "localhost: 不通"
# localhost: 通
这组数据非常关键:
- API Server 和 CoreDNS 的 ClusterIP 都能通 → CNI 网络插件(Calico)和 Pod 网络本身没问题
- 唯独 zuul 的 ClusterIP 不通 → 不是全局网络故障
- Pod 自身 IP 和 localhost 都能通 → Zuul 应用完全健康
4.2 核心定位( Pod 无法通过 Service 的 ClusterIP 访问自己)
又执行了几个测试:
bash
[root@app-titan-zuul-0 spring]# # 测试 1:Zuul 自身是否响应(从 Pod 内部)
[root@app-titan-zuul-0 spring]# curl -I http://localhost:9000/actuator/health
HTTP/1.1 200
Content-Type: application/vnd.spring-boot.actuator.v2+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 12 Aug 2026 02:40:05 GMT
[root@app-titan-zuul-0 spring]#
[root@app-titan-zuul-0 spring]# # 测试 2:通过 Service ClusterIP 访问自身(从 Pod 内部)
[root@app-titan-zuul-0 spring]# curl -I http://10.xx.xx.210:9000/actuator/health
curl: (7) Failed connect to 10.xx.xx.210:9000; No route to host
[root@app-titan-zuul-0 spring]#
[root@app-titan-zuul-0 spring]# # 测试 3:模拟完整请求路径
[root@app-titan-zuul-0 spring]# curl -I http://10.xx.xx.210:9000/api-ih/titan-cms/imSendMessage/doctorHeartbeat?doctorId=3160
curl: (7) Failed connect to 10.xx.xx.210:9000; No route to host
[root@app-titan-zuul-0 spring]#
- 测试 1 成功 :
curl localhost:9000返回200,证明 Zuul 应用本身是健康的,正在正常运行。 - 测试 2 失败 :
curl 10.xx.xx.210:9000返回No route to host,这证明了 Pod 无法通过 Service 的 ClusterIP 访问自己。
极大概率是 Pod 所在节点的 kube-proxy 组件异常 或 CNI 网络插件(如 Calico, Flannel)故障。
简单来说,Kubernetes 集群内部有一个"虚拟网络" ,Service 的 ClusterIP 就是这个虚拟网络里的一个"虚拟地址"。kube-proxy 组件负责设置规则,把所有发往这个"虚拟地址"的流量,正确地转发到后端的真实 Pod 上。
现在的情况是,这个"虚拟网络 "的规则在 Pod 所在的节点上失效了。流量从 Pod 发出后,找不到去往 10.xx.xx.210 这个地址的路,所以直接报错 No route to host
4.3 控制面确认:Endpoints 其实是对的
通过 Kuboard 查看,Service -> Endpoints -> Pod 的链路看起来是正常的:
- Service 配置 :
service-titan-zuul的 ClusterIP 为10.xx.xx.210,端口 9000,配置正确。 - Endpoints 状态 :已正确关联 Pod
app-titan-zuul-0,IP 为 10.xx.xx.153 (Pod 重建后从10.xx.xx.152变过来的),状态显示 Ready。 - Label 匹配:Service Selector 与 Pod Labels 匹配正常,没有不匹配的情况。
- Pod 重建记录:Pod 确实被重建过,但 Endpoints 已经自动更新为新 IP,控制面同步看起来没问题。
也就是说,K8s 控制面认为一切正常,但数据面就是不通。
4.4 唯一的可能:kube-proxy 没写规则
ClusterIP 的访问路径是这样的:
typescript
Pod/Nginx 发起请求 -> ClusterIP (10.xx.xx.210)
-> kube-proxy 的 iptables/IPVS 规则做 DNAT
-> 转发到后端 Pod IP (10.xx.xx.153)
┌─────────────────────────────────────────────────────────┐
│ Kubernetes 集群 │
│ │
│ ┌──────────────────────┐ │
│ │ Nginx/Pod │ │
│ │ (客户端) │ │
│ │ │ │
│ │ curl │ │
│ │ 10.xx. │ │
│ │ xx.210 │ │
│ │ :9000 │ │
│ └──────────────────────┘ │
│ │ │
│ ▼ │
│ ① 发起请求到 Service ClusterIP (10.xx.xx.210) │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ kube-proxy 转发规则 │ │
│ │ (iptables/IPVS DNAT) │ │
│ │ │ │
│ │ ⚠ 规则缺失或错误! │ │
│ │ 没有 ClusterIP │ │
│ │ → Pod IP 的映射 │ │
│ └──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ ② 找不到转发规则 │ │
│ │ 内核返回 │ │
│ │ "No route to host" │ │
│ │ ▼ │ │
│ │ ✗ 请求失败 │ │
│ │ (无法到达任何后端 Pod) │ │
│ └──────────────────────┘ │
│ │ │
│ ✗ 流量中断,到不了 ↓ │
│ │
│ ┌──────────────────────┐ │
│ │ app-titan-zuul-0 │ │
│ │ Pod (服务端) │ │
│ │ │ │
│ │ 10.xx.xx.153:9000 │ │
│ │ (可达但未被路由到) │ │
│ └──────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
现在控制面(Endpoints)正确,但数据面不通,唯一的解释就是 kube-proxy 没有为这条 Service 生成对应的 iptables/IPVS 转发规则 。请求到达 ClusterIP 后,内核找不到路由,直接返回 No route to host。
4.5 通过cms服务再次验证ClusterIP是否通
bash
[root@app-titan-zuul-0 /]# # 测试 1:直连 CMS Pod IP(验证 Pod-to-Pod 网络是否通)
[root@app-titan-zuul-0 /]# timeout 3 bash -c 'echo > /dev/tcp/10.xx.xx.50/9000' && echo "CMS Pod IP (10.xx.xx.50:9000): 通 ✅" || echo "CMS Pod IP (10.xx.xx.50:9000): 不通 ❌"
CMS Pod IP (10.xx.xx.50:9000): 通 ✅
[root@app-titan-zuul-0 /]# # 测试 2:通过短服务名访问(验证同命名空间 DNS 解析 + Service 转发)
[root@app-titan-zuul-0 /]# timeout 3 bash -c 'echo > /dev/tcp/service-titan-cms/9000' && echo "CMS Service 短名 (service-titan-cms:9000): 通 ✅" || echo "CMS Service 短名 (service-titan-cms:9000): 不通 ❌"
CMS Service 短名 (service-titan-cms:9000): 不通 ❌
[root@app-titan-zuul-0 /]# # 测试 3:通过 FQDN 全名访问(验证跨命名空间 DNS 解析)
[root@app-titan-zuul-0 /]# timeout 3 bash -c 'echo > /dev/tcp/service-titan-cms.thc-dev-xxx-xxx.svc.unprod.bjhlwyy.kuber.thc/9000' && echo "CMS Service FQDN: 通 ✅" || echo "CMS Service FQDN: 不通 ❌"
CMS Service FQDN: 不通 ❌
[root@app-titan-zuul-0 /]# # 测试 4:查看 service-titan-cms 的 DNS 解析结果(看解析到哪个 IP)
[root@app-titan-zuul-0 /]# cat /etc/hosts | grep cms
[root@app-titan-zuul-0 /]# # 如果没结果,用以下方式尝试解析:
[root@app-titan-zuul-0 /]# python3 -c 'import socket; print(socket.gethostbyname("service-titan-cms"))' 2>/dev/null || \
> python -c 'import socket; print(socket.gethostbyname("service-titan-cms"))' 2>/dev/null || \
> echo "无法解析,需要手动获取 CMS 的 ClusterIP"
10.xx.xx.123
核心发现:不仅是 Zuul,CMS 的 ClusterIP 也不通
| 服务名称 | Pod IP(直连) | Service ClusterIP(通过 DNS 解析) | 结论 |
|---|---|---|---|
| Zuul | 10.xx.xx.153:9000 ✅ 通 |
10.xx.xx.210:9000 ❌ 不通 |
ClusterIP 规则缺失 |
| CMS | 10.xx.xx.50:9000 ✅ 通 |
10.xx.xx.123:9000 ❌ 不通 |
ClusterIP 规则缺失 |
结论:
- Pod 底层网络(CNI)是完全正常的(Pod 互访畅通)。
- DNS 解析也是正常的(能正确解析出
10.xx.xx.123)。 - 根本原因: 该节点(或集群)的
kube-proxy彻底罢工或卡死了,导致它没有为任何 Service 写入底层的 iptables 或 IPVS 转发规则。
4.6 在 Pod 内部继续深挖(调用 K8s API 排查)
(1)检查 CMS 的 Endpoints 绑定情况(确认后端 Pod IP 是否注册成功)
bash
[root@app-titan-zuul-0 /]# # 1. 查询 CMS 的 Endpoints
[root@app-titan-zuul-0 /]# curl -s -k -H "Authorization: Bearer $TOKEN" https://10.xx.xx.1/api/v1/namespaces/thc-dev-xx-xx/endpoints/service-titan-cms
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "endpoints \"service-titan-cms\" is forbidden: User \"system:serviceaccount:thc-dev-xx-xx:default\" cannot get resource \"endpoints\" in API group \"\" in the namespace \"thc-dev-xx-xx\"",
"reason": "Forbidden",
"details": {
"name": "service-titan-cms",
"kind": "endpoints"
},
"code": 403
}
(2)检查 Zuul 的 Endpoints 绑定情况
bash
[root@app-titan-zuul-0 /]# # 2. 查询 Zuul 的 Endpoints
[root@app-titan-zuul-0 /]# curl -s -k -H "Authorization: Bearer $TOKEN" https://10.xx.xx.1/api/v1/namespaces/thc-dev-xx-xx/endpoints/service-titan-zuul
{
"kind": "Status",
"apiVersion": "v1",
"metadata": {
},
"status": "Failure",
"message": "endpoints \"service-titan-zuul\" is forbidden: User \"system:serviceaccount:thc-dev-xx-xx:default\" cannot get resource \"endpoints\" in API group \"\" in the namespace \"thc-dev-xx-xx\"",
"reason": "Forbidden",
"details": {
"name": "service-titan-zuul",
"kind": "endpoints"
},
"code": 403
}[root@app-titan-zuul-0 /]#
(3)最终结论:集群的 kube-proxy 组件彻底故障
我们之前的排查已经确认:
- Pod 间网络是通的 :
app-titan-zuul-0能直接访问app-titan-cms-0的 Pod IP。 - Service 的 ClusterIP 是不通的:无论是 Zuul 还是 CMS,它们的 ClusterIP 都无法访问。
现在,这个 403 错误揭示了根本原因:
kube-proxy 组件用于连接 Kubernetes API Server 的 ServiceAccount 权限被撤销了。
kube-proxy 的工作就是监听 API Server 中 Service 和 Endpoints 的变化,然后在每个节点上更新 iptables/IPVS 规则。现在,它因为权限不足(cannot get resource "endpoints"),无法获取到最新的 Endpoints 列表,导致它无法为任何 Service 生成转发规则。
这完美解释了为什么:
- Pod 到 Pod 直连正常:因为底层 CNI 网络是独立的。
- 所有 Service 的 ClusterIP 都不通 :因为
kube-proxy无法工作,集群内没有任何 Service 的转发规则。
4.7 深挖 kube-proxy 权限问题【可选,如果还想深挖,可以试试】
既然怀疑 kube-proxy,那就得看看它为什么没写规则。最可能的原因是 kube-proxy 的 ServiceAccount 权限出了问题,导致它无法 watch API Server 的 endpoints 资源,进而无法生成或更新任何 Service 的转发规则。
在有 kubectl 权限的节点上,执行了以下排查命令:
(1)查看 kube-proxy Pod 状态和日志
bash
# 查看所有 kube-proxy Pod
kubectl get pods -n kube-system -l k8s-app=kube-proxy
# 查看具体节点上的 kube-proxy Pod
kubectl get pods -n kube-system -o wide | grep kube-proxy | grep 132.xx.xx.76
# 查看日志,重点关注是否有 403 Forbidden 或 cannot list/watch endpoints 等权限错误
kubectl logs -n kube-system <kube-proxy-pod-name> --tail=100
(2)检查 ClusterRoleBinding
bash
# 查看 kube-proxy 的 ClusterRoleBinding 是否存在
kubectl get clusterrolebinding | grep kube-proxy
# 查看绑定的 ClusterRole 是否包含 endpoints 的 get/list/watch 权限
kubectl describe clusterrolebinding kube-proxy
(3)检查 ServiceAccount
bash
# 确认 kube-system namespace 下的 kube-proxy ServiceAccount 状态
kubectl get sa -n kube-system kube-proxy
(4)检查节点上的 iptables 规则(辅助验证)
bash
# 在故障节点上直接查看 iptables nat 表,看是否有 10.xx.xx.210 的相关规则
iptables -t nat -nL | grep 10.xx.xx.210
# 或者查看 KUBE-SVC 链
iptables -t nat -nL KUBE-SVC-XXX | grep 10.xx.xx.210
排查发现,kube-proxy 的 ClusterRoleBinding 可能被误删或修改 ,导致其 ServiceAccount(system:serviceaccount:kube-system:kube-proxy)失去了对 endpoints 资源的 list/watch 权限。kube-proxy 无法感知 Endpoints 的变更,自然也就不会生成新的 iptables/IPVS 转发规则。
五、解决方案:全量重启 kube-proxy
5.1 修复 ClusterRoleBinding(如权限丢失)
如果确认是 ClusterRoleBinding 丢失,先重新绑定:
bash
kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kube-proxy
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: system:node-proxier
subjects:
- kind: ServiceAccount
name: kube-proxy
namespace: kube-system
EOF
5.2 全量重启 kube-proxy(建议)
确认根因后,操作就很简单了。在 Master 节点或具有 kubectl 权限的跳板机上执行:
bash
kubectl rollout restart daemonset kube-proxy -n kube-system
kube-proxy 的 DaemonSet 控制器会自动在每个节点上重建 Pod。新 Pod 启动后,会重新从 API Server 拉取所有 Service 和 Endpoints 信息,生成正确的 iptables/IPVS 规则。
5.3 重启后验证
(1)在 Zuul Pod 内测试 ClusterIP 连通性
bash
timeout 3 bash -c 'echo > /dev/tcp/10.xx.xx.210/9000' && echo "Zuul Service: 通" || echo "Zuul Service: 不通"
# Zuul Service: 通
(2)验证业务接口
bash
curl -v http://devcucc7000.xxx.cn/api-ih/titan-cms/admin/v1.1/getServiceItem/10017/
# 返回正常
(3)检查 kube-proxy 日志确认无异常
bash
kubectl logs -n kube-system <kube-proxy-pod-name> --tail=50
(4)检查该节点上其他 Service 的 ClusterIP 是否均可正常访问
bash
# 在 Pod 内批量测试其他 Service 的 ClusterIP
for ip in 10.xx.xx.123 10.xx.xx.170 10.xx.xx.252; do
timeout 3 bash -c "echo > /dev/tcp/$ip/9000" && echo "$ip: 通" || echo "$ip: 不通"
done
业务 502 全部消失,恢复如初。
六、复盘总结
| 阶段 | 关键动作 | 教训 |
|---|---|---|
| 初期 | 看到 10.xx.xx.210 不通,误以为是旧 Pod IP |
ClusterIP 和 Pod IP 必须严格区分,环境变量是最直接的验证方式 |
| 中期 | 在精简容器里找 ss、tcpdump |
生产环境容器通常没有这些工具,/dev/tcp + curl 才是最可靠的探测手段 |
| 后期 | 发现 API Server/CoreDNS 通、唯独业务 Service 不通 | 这种选择性不通是定位 kube-proxy 问题的经典信号 |
| 根因确认 | Kuboard 看到 Endpoints 正常,但数据面不通 | 控制面正常不代表数据面正常,kube-proxy 是中间的"黑盒" |
| 修复 | 全量重启 kube-proxy DaemonSet | 对于规则同步类故障,重启往往是最有效的恢复手段 |
这次故障给我最大的提醒是:看到 "No route to host" 不要只盯着防火墙和网络策略,kube-proxy 的规则同步状态才是 ClusterIP 场景下最隐蔽的故障点。 控制面(Endpoints)看起来一切正常,但数据面(iptables/IPVS)可能已经断联很久了。
附录:排查命令速查
以下命令供管理员参考,用于独立排查同类问题:
1. 在 Pod 内查看环境变量确认 ClusterIP
bash
env | grep SERVICE_ | grep <服务名>
2. 在精简容器内做端口连通性测试
bash
# 测试 TCP 端口是否通
timeout 3 bash -c 'echo > /dev/tcp/<IP>/<PORT>' && echo "通" || echo "不通"
# 示例
timeout 3 bash -c 'echo > /dev/tcp/10.57.163.210/9000' && echo "通" || echo "不通"
3. 查看 Service 的 Selector
bash
kubectl get svc service-titan-zuul -n thc-dev-xx-xx -o jsonpath='{.spec.selector}'
4. 查看 Pod 的 Labels
bash
kubectl get pod app-titan-zuul-0 -n thc-xx-xx-xx --show-labels
5. 查看 Endpoints
bash
kubectl get endpoints service-titan-zuul -n thc-dev-xx-xx -o wide
kubectl get endpoints service-titan-zuul -n thc-dev-xx-xx -o yaml
6. 查看 kube-proxy 日志
bash
kubectl logs -n kube-system <kube-proxy-pod-name> --tail=100
7. 查看 Calico Pod 状态
bash
kubectl get pods -n kube-system -o wide | grep calico | grep <节点IP>
8. 查看 Calico Pod 日志
bash
kubectl logs <calico-pod-name> -n kube-system --tail=50
9. 查看节点上所有系统组件 Pod
bash
kubectl get pods -n kube-system -o wide | grep <节点IP>
10. 查看 kube-proxy 的 iptables 规则(需进入节点)
bash
iptables -t nat -nL | grep <ClusterIP>
11. 检查 kube-proxy 的 ClusterRoleBinding
bash
kubectl get clusterrolebinding | grep kube-proxy
kubectl describe clusterrolebinding kube-proxy
12. 检查 kube-proxy 的 ServiceAccount
bash
kubectl get sa -n kube-system kube-proxy
13. 全量重启 kube-proxy
bash
kubectl rollout restart daemonset kube-proxy -n kube-system