☰
Kubernetes网络全链路实测:Pod IP到ClusterIP的转发与排障
2026/10/19 5:03:46 网站建设 项目流程

Kubernetes网络通信是个看起来复杂、实际拆开看逻辑很清晰的东西。很多人在集群里部署一套Deployment和Service之后,只要curl能返回内容就觉得“通了”,可一旦流量链路中间某一段出问题——Pod IP访问不了、ClusterIP转发不过去、DNS解析时好时坏——就彻底没了思路,最后只能靠重启kube-proxy碰运气。这篇文章我会用一个三副本的Nginx应用,把从Pod IP到Service ClusterIP的全链路做一次完整实测,每一步说清楚流量是怎么走的、该看哪个接口、用什么命令验证。适合刚把集群搭起来、想真正搞懂网络原理的人,也适合被线上网络问题折磨过、想建立系统化排障思路的运维和开发。

整个验证过程我会覆盖四个关键环节:Pod内部容器的网络共享、同一节点上Pod之间的通信、跨节点Pod的Overlay转发、以及Service层的kube-proxy转发和CoreDNS服务发现。

1. 链路拆解:从Pod IP到ClusterIP到底要经过几个环节

1.1 一条完整请求经过的4个转发平面

在Kubernetes里,网络并不是单一平面,而是由多个转发层叠加起来的。最底一层是Pod内部,一个Pod里的多个容器共享同一个网络命名空间,相当于它们用的是同一个IP、同一块网卡;第二层是节点内部,同一台Node上的不同Pod通过虚拟网桥(比如cni0)完成二层通信,跟传统虚拟机用Linux网桥互相通信的场景非常像;第三层是跨节点通信,由CNI插件负责,通过VXLAN或者其他Overlay技术把Pod网段的数据包封装到底层网络里传出去;最上面一层才是Service,由kube-proxy把ClusterIP这个虚拟IP转发到后端的Pod IP上。

整个链路走一遍,数据包要经过这么多层,任何一个环节配置不对都会出问题。比如你在一个Pod里访问另一个Pod的IP,看起来就是一条“点对点”的连接,实际上数据包可能已经完成了veth进入网桥、网桥转发、Overlay封装、隧道解封装这一整套动作。

1.2 为什么我选择手工验证而不是直接看文档

文档会把每个组件都讲得很清楚,但不会告诉你“流量到了这里应该看到什么现象”。手工验证的价值在于,你在每个转发节点上打点观测,能够建立真实的网络直觉。比如你还记得Pod的流量进入宿主机时,实际上是从veth虚拟网卡进入cni0网桥的吗?抓包的时候如果抓错了接口,你会觉得数据包“凭空消失”了。

我自己的经验是,把这条链路当成一条管道,管道上有几个观察口:容器内的eth0、宿主机的cni0、跨节点时的tunnel接口、Service的NAT链。只要在每个观察口确认数据包确实出现了,就说明这一层没有丢包;哪一层没看到包,问题就锁定了。这种排障思路比看任何文档都管用。

1.3 实验环境与网段规划

先说我用的环境,方便后面命令对得上。我这边是一个三节点集群,一个控制平面加两个工作节点,Kubernetes版本1.28,CNI用的是Calico,Pod网段是172.20.0.0/16,Service网段是10.96.0.0/16,NodePort范围是默认的30000-32767。

这几个网段要先确认清楚,因为后面观察路由、查iptables规则都要依赖它。你可以在自己的集群上用kubeadm的配置文件或者直接看CNI插件的配置来确认,命令很简单:

kubectl get cm -n kube-system cni-config -o yaml

拿到的Pod网段记下来,后面你理解路由表的时候会频繁用到。Service网段同样重要,kube-proxy生成的规则就是围绕这个网段做的。

2. Pod网络通信:同一份IP下的容器和跨介质转发的Pod

2.1 一个Pod内的多个容器共享一个网络命名空间

这是整个链路里最基础、也最容易忽略的一环。每个Pod创建时,实际上会先启动一个pause容器(也叫沙箱容器),它负责持有这个Pod的网络命名空间,其他业务容器启动时都加入这个命名空间。所以一个Pod里的nginx容器和sidecar容器看到的eth0是同一块网卡,IP和端口空间完全共享。

这就带来一个很实际的限制:同一个Pod里的两个容器不能用同一个端口监听。你在容器A里监听80,在容器B里也想监听80,第二个容器启动就会直接报地址被占用。很多刚把多容器Pod部署起来的人都会踩到这个坑。

理解这一点对排查问题也有帮助。比如你在Pod里看到了3个进程在监听端口,但你只部署了1个业务容器,另一个是sidecar容器,那很正常;但如果它们监听的是同一个端口,那肯定起不来。

2.2 同一节点上Pod之间通过虚拟网桥通信

同一台Node上的Pod通信,依靠的是Linux虚拟网桥。每个Pod有一个veth对(virtual ethernet pair),一头在宿主机上,一头在Pod的命名空间里。宿主机那一头会挂到一个叫cni0的网桥上,相当于所有Pod都插在同一台虚拟交换机上。

这个模型跟物理世界很像:两台服务器插在同一个交换机上,IP在同一网段,数据包通过交换机转发,不需要经过路由器。所以在集群里,同一个Node上的Pod之间是直接二层通信的,效率高,延迟低。

验证方法很简单。登录任意一个Node,查看网桥和veth接口:

brctl show cni0 ip link show type veth

你会看到cni0上挂了一堆veth接口,每个veth对应一个Pod。再结合Pod的IP,用ip命令就能一一对应上。

2.3 跨节点Pod通信:CNI插件怎么搞定Overlay

跨节点通信比同节点复杂,因为Pod的IP可能不在同一个物理网段。比如节点A上的Pod是172.20.1.5,节点B上的Pod是172.20.2.8,这两个IP在网络层上不能直接互通,必须通过CNI插件做一层转发。

我用到的Calico走的是IPIP或者VXLAN隧道模式。当一个数据包要从节点A的Pod发往节点B的Pod时,节点A上的路由表会把流量引导到tunnel接口,然后数据包被封装成底层网络可以传输的格式,到达节点B后解封装,再通过cni0网桥转发给目标Pod。

所以你在Node上执行ip route,会看到类似这样的路由:

172.20.1.0/24 dev cni0 proto kernel scope link src 172.20.1.1 172.20.2.0/24 via 192.168.50.102 dev tunl0 proto bird onlink

这个路由表信息量很大。第一行说明这个节点直接管理172.20.1.0/24网段,走cni0;第二行说明172.20.2.0/24网段要经过另一个Node,走tunl0隧道口。

2.4 对初学者容易混淆的几个概念

有几个概念我经常看到有人在群里问,顺手说一下。Pod IP是真实分配在容器上的IP,在集群内部是可达的(前提是CNI正常工作),但它不保证能从集群外部直接访问;ClusterIP是Service的虚拟IP,只存在于kube-proxy的规则里,没有任何设备真正监听这个IP,你ping不通它是正常的,不是故障。

还有一个容易混淆的点是NodePort。NodePort本质上是ClusterIP的延伸,它会在每台Node上监听一个端口,流量到达NodePort后会先转发到ClusterIP,再通过kube-proxy转发到Pod。所以NodePort依赖ClusterIP链路,如果ClusterIP链路坏了,NodePort也不可用。

3. 实操:用三副本Nginx验证Pod IP链路

3.1 创建应用并确认Pod的IP与节点分布

开始实测。先创建一个三副本的Nginx应用,命名为web-app:

kubectl create deployment web-app --image=nginx:1.25 --replicas=3 kubectl scale deployment web-app --replicas=3

等Pod全部Running后,查看它们都被调度到了哪些节点、拿到什么IP:

kubectl get pods -o wide

输出大致长这样:

NAME READY STATUS RESTARTS AGE IP NODE web-app-6f8b5c9d6c-abcde 1/1 Running 0 62s 172.20.1.5 node-a web-app-6f8b5c9d6c-fghij 1/1 Running 0 62s 172.20.2.8 node-b web-app-6f8b5c9d6c-klmno 1/1 Running 0 62s 172.20.2.9 node-b

这时候你已经能看到,Pod被分在了3个不同的节点上,IP也分别落在了两个子网里。跨节点的那条访问链路,马上就会用到。

3.2 从宿主机访问Pod IP验证直连能力

在控制平面节点上直接访问Pod的IP:

curl -v http://172.20.1.5/

如果返回200和Nginx的首页HTML,说明节点到Pod这条链路是通的。这一步验证的是CNI的转发是否正常。能通,说明路由表没有问题,tunnel或者veth链路没有问题。

接下来测试跨节点访问,从控制平面节点访问位于node-b上的Pod IP:

curl http://172.20.2.8/

这里如果通,说明跨节点路由和Overlay封装全部正常。实测下来,这两个curl都能正常返回,说明Calico的IPIP隧道配置没有问题。

3.3 跨节点、跨容器的Pod间互访验证

光从宿主机访问还不够,我们真正关心的是Pod与Pod之间的互通。进入第一个Pod,让它访问另一个节点的Pod:

kubectl exec -it web-app-6f8b5c9d6c-abcde -- curl -s http://172.20.2.8/

这种情况下,数据包的路径是:Pod A的eth0 → veth → node-a的cni0 → 路由查表 → tunl0隧道 → node-b → cni0 → Pod B的eth0。全程跨越了Pod网络命名空间、节点网桥和Overlay隧道三部分,只要最终能curl通,就说明这条转发链路完整可用。

很多人在这一步会忽略一个细节:镜像里不一定有curl。Nginx的官方镜像默认没有curl。需要先进入Pod安装,或者用一个自带网络工具的镜像做跳板,比如netshoot:

kubectl run -it --rm tmp-shell --image=nicolaka/netshoot -- /bin/bash

在netshoot容器里执行同样的curl就能访问。这个镜像内置了tcpdump、nslookup、ip等等一系列网络排查工具,值得常备。

3.4 在这个环节最容易踩的坑

我在这个阶段遇到过几次问题,都是跟环境相关的。

第一个坑是防火墙。云厂商的安全组默认会挡住节点之间的隧道端口(比如IPIP用的协议号4,或者VXLAN的UDP 4789端口)。集群里的Pod平时访问正常,一旦跨节点就开始丢包,十有八九是这个原因。解决方法是放行相关协议和端口,或者换用纯路由模式的CNI方案。

第二个坑是MTU不一致。默认MTU如果是1500,但底层的物理网络实际MTU只有1450,隧道封装后数据包会超过MTU导致丢包。现象很隐蔽,小包能通,大包就卡。用ping -s 1400这种命令测一下就能复现。

第三个坑是容器里没有网络排查工具。前面说过了,Nginx官方镜像是精简版,连curl都没有。要在Pod里临时验证网络,建议用netshoot这种瑞士军刀式镜像。

4. Service全链路:ClusterIP、kube-proxy与负载均衡

4.1 创建Service:标签匹配与Endpoints

Pod链路通了,接下来验证整个Kubernetes网络通信中最关键的组件——Service。创建Service的方式有两种,一种是用kubectl expose命令,一种是直接写YAML。这里用expose:

kubectl expose deployment web-app --port=80 --target-port=80 --type=ClusterIP --name=web-svc

查看Service的详细信息:

kubectl get svc web-svc kubectl describe svc web-svc

会看到类似这样的信息:

Name: web-svc Namespace: default Type: ClusterIP IP: 10.96.44.11 Port: <unset> 80/TCP TargetPort: 80/TCP Endpoints: 172.20.1.5:80,172.20.2.8:80,172.20.2.9:80

这里有个非常关键的点:Endpoints列表里的IP就是前面创建的那三个Pod IP。Service本身不直接感知Pod,它靠标签选择器匹配Pod,然后由endpoint控制器把匹配到的Pod IP和端口汇总成Endpoints列表。如果Selector没有匹配到任何Pod,Endpoints就是空的,Service就是个空壳。

所以排查Service问题,第一件事永远是看Endpoints。没有它就等于Service后面什么都没有,访问必然失败。

4.2 kube-proxy的iptables模式:规则链怎么走

现在我们有了一个ClusterIP 10.96.44.11,但它是个虚拟IP,节点上没有任何网卡监听这个地址。那数据包到了这里之后是怎么被转发的?

答案在iptables的NAT表里。kube-proxy会为每个Service生成一条规则链。数据包访问ClusterIP时,实际上命中的是KUBE-SERVICES规则,然后跳到对应的KUBE-SVC-XXXX链,最后DNAT到具体的Pod IP上。

可以这样查看:

iptables -t nat -nvL | grep web-svc

你会看到类似这样的规则链:

KUBE-SVC-XXXX tcp dpt:80 -> KUBE-SEP-YYYY KUBE-SEP-YYYY tcp -> 172.20.1.5:80 KUBE-SEP-YYYY tcp -> 172.20.2.8:80 KUBE-SEP-YYYY tcp -> 172.20.2.9:80

KUBE-SVC链里的多个规则默认是随机概率模式,也就是每条规则按比例随机选中,实现大概率的负载均衡。从规则上看,ClusterIP的流量最终改写了目标地址,变成了Pod IP。这一步做完,流量才真正进入了Pod网络平面。

这里需要区分kube-proxy的另一个模式:IPVS。iptables模式在Service数量非常多的时候,规则数量会上千,性能有瓶颈;IPVS模式把规则下沉到内核的IPVS表,性能更好,规则也更简洁。kube-proxy默认模式现在普遍是iptables,生产环境建议评估IPVS。验证当前用的什么模式:

kubectl logs -n kube-system kube-proxy-xxxxx | grep "Using iptables proxy"

4.3 实际测试负载均衡效果

现在我们来验证流量进入ClusterIP之后,是不是真的被送到多个后端。在集群某个节点上反复访问ClusterIP:

for i in $(seq 1 10); do curl -s http://10.96.44.11/ | grep -o "<title>.*</title>"; done

Nginx默认页面每个实例都是一样的,单纯看HTML看不出差异。更好的方式是看后端Pod的访问日志。先找到三个Pod,分别tail日志:

kubectl logs -f deployment/web-app

然后另一边持续访问ClusterIP,观察日志里访问来源IP的分布。你会发现三次curl里面,流量会被均匀或者按概率分配到不同的Pod上。这说明Service层的负载均衡在工作。

补充一个细节:访问ClusterIP时,默认externalTrafficPolicy是Cluster,意思是在任意Node上进入ClusterIP都会被转发到任意后端Pod,即使后端Pod不在当前Node上。这会导致一个问题:后端Pod看到的Source IP被SNAT成了Node的IP,而不是真实客户端IP。如果你的应用需要真实客户端IP做日志审计或者风控,可以改成externalTrafficPolicy: Local,这样流量只会转发到当前Node上的Pod,保留源IP,但代价是负载可能不均匀。

4.4 DNS链路:CoreDNS的解析过程

除了通过ClusterIP访问,我们在集群里更常用的方式是直接用服务名。这里涉及DNS链路。

集群里的DNS记录是CoreDNS负责提供的。当你创建了一个名为web-svc的Service,在default命名空间下,它就会被注册为web-svc.default.svc.cluster.local。

在任意Pod里解析这个服务名:

kubectl run -it --rm dns-test --image=nicolaka/netshoot -- nslookup web-svc.default.svc.cluster.local

如果DNS正常,你会看到这个域名解析到了10.96.44.11,正是Service的ClusterIP。这个解析过程,本质上是Pod把DNS查询请求发给了CoreDNS的ClusterIP,CoreDNS又从APIServer拿到了Service和数据,然后返回给客户端。

这里有一个容易出问题的点:CoreDNS的Pod如果被调度到了不通畅的节点,可能导致整个集群的DNS解析变慢或者超时。排查方法很简单:

kubectl get pods -n kube-system -l k8s-app=kube-dns kubectl logs -n kube-system -l k8s-app=kube-dns

如果Pod状态正常但解析还是不稳定,检查一下CoreDNS的ConfigMap,看看是否有不当的缓存配置或者自定义上游DNS配置。我实际遇到过的情况是,CoreDNS的Pod没有被节点调度到合适的位置,或者节点上的resolv.conf配置指向了不可达的内网DNS服务器,导致集群内域名解析时好时坏。

5. 常见问题与排查技巧实录

5.1 高频故障速查表

这一节整理我在实际排查中遇到的典型问题,直接做成速查表,方便你对照定位。

现象可能原因优先排查方向
从Node访问ClusterIP超时kube-proxy异常或者iptables规则被覆盖检查kube-proxy Pod状态和iptables规则链
Service没有Endpoints标签选择器没匹配到Podkubectl get endpoints + describe svc
从Pod访问ClusterIP被拒绝targetPort设错或者Pod容器没监听端口检查targetPort和Pod内监听状态
Pod之间跨节点访问不通CNI隧道端口被防火墙挡检查集群节点防火墙和隧道端口
容器内无法解析服务名CoreDNS问题检查CoreDNS Pod状态和CoreDNS配置文件
连接偶发超时后端Pod不均匀或MTU问题检查Pod日志、后端数量、MTU一致性
后端收到的Source IP不是客户端IPexternalTrafficPolicy为Cluster切换为Local模式验证

这张表核心想表达的是:无论什么网络故障,先分清楚到底是哪一层的问题。Pod IP层的问题多在CNI和路由,ClusterIP层的问题多在kube-proxy和规则链,DNS层的问题多在CoreDNS本身。分层定位才不会像无头苍蝇一样乱试。

5.2 我常用的排障命令清单

这里分享一套我在实际工作中反复用到的排查命令,基本覆盖全链路。

确认Pod和Service情况:

kubectl get pods -o wide kubectl get svc -o wide kubectl get endpoints

查看路由和接口(在节点上执行):

ip route | grep 172.20 ip link show type veth ip addr show cni0

查看iptables转发规则,确认Service对应的NAT链:

iptables -t nat -nvL | grep -E "KUBE-SVC|KUBE-SEP|web-svc"

直接抓包定位丢包点:

# 在节点cni0网桥上抓包 tcpdump -i cni0 port 80 -n # 在tunnel接口上抓包 tcpdump -i tunl0 -n # 在Pod的veth接口上抓包(veth名称通过ip link确认) tcpdump -i vethXXXX port 80 -n

抓包是个体力活,但很见效。有一次我排查跨节点Pod通信不通,tcpdump抓了node-a的eth0,完全没看到发往对端Node的包;再抓tunl0也没抓到。然后排查route,发现Pod网段路由缺失,最终定位到是Calico的BGP peer配置错误。这个案例里,如果没有抓包,根本定位不到这么深。

5.3 几个减少网络故障的配置建议

基于我自己的运维经验,有几个配置提前做对,能避免大部分网络问题。

第一个是Pod的readinessProbe一定要配置。如果一个Pod已经起来但应用还没ready,比如Nginx还没有正确加载配置,流量转发到它上面就直接连接拒绝。配置了readinessProbe之后,Pod没就绪就不会加入Endpoints列表,Service流量就不会被转发到坏Pod上。这个配置在网络排障里起的作用非常大。

第二个是Service端口命名规范。如果你的Service是多端口的,端口必须命名,比如http、https。多端口Service在YAML定义时如果不写端口名,kube-proxy配置规则的时候可能会出错,造成一些看起来很诡异的访问问题。

第三个是合理规划Pod网段和Service网段。生产环境一定要把这两个网段和公司底层物理网络错开,预留足够空间。如果Pod网段和办公网段冲突,后期会非常痛苦,扩容都扩不了。

第四个是关于CNI的升级。升级CNI插件之前一定要看release note,因为路由模式、隧道模式、接口命名都有可能变化。我经历过一次CNI升级后,所有节点上的路由表都变了,但因为我提前录入了旧路由表做比对,才快速恢复了业务。

第五个是网络排障要预留出抓包通道。生产环境很多节点没有安装tcpdump,要提前装好或者准备静态编译的二进制。如果在出问题的时候再去找包,通常已经错过最关键的现场了。

6. 一些我个人的实测心得

写到最后,分享几点我在这套链路验证里的真实体会。

第一个体会是,Kubernetes的网络问题,90%都可以通过“分层定位”的方式快速解决。不要一遇到Pod访问Service不通就去重启kube-proxy,那是赌运气。正确路径是:先确认Pod IP层通不通,再确认Endpoints有没有,最后确认iptables规则在不在。每一层都有明确的验证命令,照着一层一层打下去,问题自己会浮出来。

第二个体会是,一定要动手实测一遍链路。哪怕你已经很熟悉原理,亲手敲一遍curl、看一次iptables规则链、抓一次包,建立的感觉跟看文档完全不一样。我见过很多开发者理论一套一套,可一遇到“为什么我Pod IP ping不通Service IP”这种问题就开始怀疑人生——因为ClusterIP本来就是ping不通的,它就不是一个真实设备。

第三个体会是,网络经验是积累出来的,但积累要有章法。每次排查完一个问题,把现象、定位过程、根因、修复手段记录下来。时间久了,你看到“连接超时”脑子里就会跳出三个候选原因,看到“DNS解析失败”就会自动去查CoreDNS——这种直觉,就是靠一次次的链路验证喂出来的。

最后再分享一个我的习惯:常备netshoot镜像。不管在哪个集群,排查网络问题的时候,能curl、能nslookup、能tcpdump、能ip route的镜像永远比裸的Nginx镜像好用得多。一次投入,长期受益。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询