☰
Kubernetes网络通信全解析:从数据链路到Flannel/Calico排障实战
2026/10/21 21:26:31 网站建设 项目流程

一直想写篇关于k8s网络通信的文章,因为问我这个问题的人实在太多了。前阵子刚处理完一个典型故障:服务A访问服务B,Deployment全是Running,日志里找不到任何报错,但请求就是卡到超时,最后定位下来是跨节点通信的报文在封装环节出了问题。这种“看起来全对,实际不通”的体验,几乎每个刚开始接触K8s的人都撞见过。

这篇文章想把这套东西从头到尾理一遍,从网络模型到底层数据链路,再到Flannel、Calico这类主流方案的差异,最后把我的排查套路和踩过的坑拿出来分享。适合两类人:一是被k8s网络通信搞到头大的新手,二是会部署但不会排障的运维同学。看完你至少能回答自己一个问题:一个请求从浏览器到后端容器,中间到底经过了哪些看不见的环节。

1. 为什么k8s网络通信是初学者最先崩溃的一关

1.1 传统网络经验为什么会失灵

做传统运维的时候,网络很好理解。服务器IP是固定的,交换机路由表一配,包从哪来、往哪去清清楚楚。排查问题也简单,ping不通就逐跳看路由,再不通就抓包,整个链路是有形的。

到了K8s里,这套经验直接失效,原因就三个:

  • Pod不是常驻的,随时可能被重建,重建后IP就变了;
  • Service对外暴露的是一个虚拟IP,它不直接绑定到任何容器上;
  • 网络访问不再靠某个静态路由表,而是由网络插件动态维护。

三个变化叠加的结果是:原来“查路由表”能解决的问题,现在必须先理解整个抽象层次。IP漂移、虚拟IP、动态路由、负载转发,全混在一起。新手最常见的操作就是到处ping、到处看日志,最后也没定位到问题在哪。这不是不努力,而是脑子里没有数据链路图,只能乱试。

所以我的建议是:学K8s网络,第一步不是先装集群,而是先把“从用户到Pod”这条链路画出来。

1.2 先在心里画一张完整数据链路图

一个典型的用户请求在K8s集群里大概要走这么几步:

用户请求 → Ingress或NodePort入口 → Service的ClusterIP → kube-proxy的转发规则 → 后端Pod的IP:Port → 容器内端口

每一段都有自己的组件在负责。Ingress负责域名和七层路由,Service负责抽象和负载均衡,kube-proxy负责把虚拟IP转发到真实Pod,CNI插件负责让Pod真正拿到IP且互相可达。你排查时遇到的任何一个“网络不通”,本质上都是这张链路图中某个环节断了。

把这个链路背下来,后面看什么都顺了。下面挨个拆这些环节。

2. 三层网络模型:Pod、Service、Node谁在跟谁通信

2.1 网络模型的三条铁律

K8s本身不实现网络功能,它只是定义了一套规则,真正干活的是CNI插件,也就是Container Network Interface,容器网络接口,翻译成人话就是一把统一的插头标准。你的Pod、Service、节点怎么组网,由选定的CNI插件决定。

这套网络模型有三条铁律:

  1. 同一个Pod里的所有容器共享一个网络命名空间,共享同一个IP、同一组端口,连回环地址都是同一个;
  2. 任意两个Pod之间可以直接通信,不需要NAT;
  3. 不管两个Pod在同一个节点还是不同节点,通信效果必须一样。

这三条决定了K8s网络的设计基调。你写yaml的时候感觉不到它们,但每一条背后都有实在的机制在支撑。

2.2 一个Pod是怎么拿到IP的

要理解Pod的IP,得先知道它的网络命名空间是怎么搭出来的。每个Pod启动时,会先创建一个基础设施容器,名字一般叫pause或者sandbox。这个容器先初始化一个独立的网络命名空间,并且持有它。业务容器启动之后,再加入这个网络命名空间。

这么设计有个很实际的好处:业务容器随便重启、崩溃、替换,网络栈不重建,Pod的IP保持不变。这也是为什么你经常看到“容器重启了好几次但Pod IP一直没变”的原因,根本不是巧合,是pause容器把网卡和IP攥在手里了。

具体到一台节点上,Pod启动后,CNI插件会在Pod的网络命名空间里创建一张虚拟网卡(veth),另一头挂在宿主机的网桥上。于是Pod发出的包先到了宿主机网桥,再根据宿主机的路由规则决定下一步走向哪里。

2.3 同一个节点和跨节点,数据走的完全是两条路

同节点的两个Pod通信,路径很短:Pod A → 虚拟网卡 → 宿主机网桥 → 虚拟网卡 → Pod B,不经过物理网卡,所以特别快。这也是为什么同节点通信几乎不会出问题。

跨节点通信才是重头戏。每个节点都会从Pod大网段里分到一个子网,比如Pod网段是10.244.0.0/16,那节点1可能分到10.244.1.0/24,节点2分到10.244.2.0/24。节点上的路由表会写入类似于“访问10.244.2.0/24的包,下一跳是节点2的物理IP”这样的规则。包从Pod A出来后,通过虚拟网卡到本节点网桥,一看目标IP网段不属于本节点,就根据路由把包从物理网卡送出去。

这里面最核心的部分是overlay封装。以VXLAN模式为例:内层是完整的原始数据包,源IP是Pod A,目标IP是Pod B,这个包整体被作为payload,再套一层新的UDP头,外层源IP是本节点,外层目标IP是目的节点。包到达对端节点后,解开外层UDP头,还原出内层原始包,再送入对端Pod。整个过程对两个Pod来说是完全透明的,它们感知不到中间还有这么一层。

用一个生活化的比喻:两个不同园区的朋友寄文件,各自把文件放进信封,写上对方地址,但你不会直接用这个信封去寄,而是把信封再塞进一个快递包裹里,填上对方所在园区的收发室地址。对方收发室收到包裹后拆开,再把里面的信封送去给你朋友。快递包裹就是外层UDP封装,里面的信封就是原始数据包。

2.4 Service的虚拟IP到底虚在哪

Service是一个很抽象的组件。它暴露一个ClusterIP,但这个IP不是任何一张真实网卡,你抓包时抓不到一个“属于Service”的包。真实转发靠的是kube-proxy。

kube-proxy会监听Service和后端Pod的变化,动态维护一套iptables规则或者IPVS规则。当数据包访问ClusterIP时,iptables里的DNAT规则会把目标IP改写成一个真实后端Pod的IP,然后继续走Pod间通信的链路。所以你可以把Service理解成一张“便利贴”,它不干活,干活的是拿着便利贴做转发的kube-proxy。

不少初学者会在这个地方绕很久,总觉得Service的IP应该像物理机的VIP一样可以ping通、可以做路由。其实没必要,ClusterIP就只在K8s集群内部有效,而且它存在的意义就是提供一个稳定的访问入口,让你不用关心后端Pod怎么变。

3. Flannel与Calico:两种主流CNI的选型实测

3.1 Flannel的VXLAN和host-gw两种后端

Flannel是新手入门最常用的CNI,部署简单,一条kubectl apply就能搞定。它有两种常用的工作后端。

VXLAN模式就是前面说的overlay封装方案,数据包走UDP封装,对底层网络要求宽松,能跨三层网络工作,只要节点之间UDP能通就行。代价是多了一层封装,性能和同节点通信没法比,而且每个包会多几十字节的额外开销。这个开销平时没感觉,一旦MTU配置不对就容易出问题,后面单独讲。

host-gw模式则完全不加封装。它做的其实是静态路由管理,每个节点相当于其他节点Pod网段的网关,路由表直接写“目标网段 → 对端节点物理IP”。因为不封装,性能好很多,转发效率和传统路由基本持平。但它有个硬要求:节点之间必须在同一个二层网络里,网关都能直接到达,否则路由写进去也白搭。

3.2 Calico走的是纯三层路由而不是封装

Calico的核心思路和Flannel完全不同。它不搞overlay,而是让每个节点都扮演一个路由器,用BGP协议把本节点的Pod网段路由通告给其他节点。数据包从一个节点到另一个节点,就是标准的三层路由转发,没有额外封装。没有封装意味着更好的性能和更低的开销,网络路径也更符合传统运维的直觉。

Calico的另一个优势是原生支持NetworkPolicy。它通过在每个节点上生成iptables规则来强制执行Pod间的访问控制,可以精确到“哪个Pod能访问哪个Pod的哪个端口”。这对生产环境非常重要。但注意,Calico对底层网络的依赖也更强,它要求节点之间的三层网络本来就能通,如果底层网络设备做了奇怪的ACL或者路由策略,排查起来会牵扯到更多外部因素。

3.3 选型对比与我的决策逻辑

直接给一张对比表,就是我实际选型时参考的维度:

对比维度Flannel VXLANFlannel host-gwCalico BGP
部署复杂度极低极低中
性能损耗中(有UDP封装)低(无封装)低(无封装)
底层网络要求宽松,三层可通即可要求节点二层互通要求节点三层路由可达
支持NetworkPolicy不支持不支持支持
大规模集群扩展性中中好
排查难度需要关注overlay链路简单直观需要了解BGP状态

我的决策逻辑比较简单,也分享给很多团队过。如果是测试环境,或者规模在几十个节点以内的内部系统,选Flannel VXLAN就够了,省心、够用、好排查。如果底层网络条件好,节点二层通,又对性能敏感,可以换成host-gw模式。一旦需要NetworkPolicy做细粒度访问控制,或者集群规模会扩大到上百节点,直接选Calico,别纠结。大部分犹豫不决的项目,最后都因为“要用NetworkPolicy”或者“不想老处理大包问题”倒向Calico了。

4. 一次线上故障的完整排查链路:从“服务全正常”到“超时不背锅”

4.1 现象与初步判断

某次在内部环境遇到一个很难啃的问题:服务A需要调用另一个命名空间里的服务B,控制台持续报连接超时。看任务状态,所有Pod都是Running,B服务本地端口可以正常访问,日志也没有报错。也就是说Pod活着、端口在听,问题一定出在A到B的“路上”。

这里有个很重要的排查习惯:别急着进代码里找原因,先确认这是不是一个“网络链路问题”。“连接超时”和“连接拒绝”是两个方向。连接拒绝通常是端口没起来或监听在内网地址,连接超时则多半是包没到、到了没人回、或回包丢了。这类问题不按链路排查,靠翻日志基本找不到答案。

4.2 一步步沿着数据链路段段验证

我的排查顺序是固定的,从CNI状态到路由,再从Pod内测到抓包,最后看转发规则和DNS。

第一步,先确认CNI插件的Pod状态。如果Flannel或Calico的Pod在CrashLoopBackOff,新Pod根本拿不到IP,集群早就乱了。命令是kubectl get pod -n kube-system,一眼看过去RR状态,问题立刻锁定。

第二步,检查节点路由表。在源节点或目标节点上执行ip route,看Pod网段的路由是否存在且指向正确。比如节点1上应该能看到类似“10.244.2.0/24 via 192.168.1.12 dev eth0”的条目。路由少了,包就找不到出口。

第三步,从Pod内部做连通性测试。kubectl exec -it app-xxx -- ping 目标Pod IP。这里有个坑要提醒:ping走的是ICMP,很多环境会屏蔽ICMP,所以ping不通不一定代表网络不通。更可靠的测试是TCP端口探测,比如kubectl exec进去执行nc -vz 目标IP 端口,或者用busybox的telnet命令。

第四步,当Pod IP连通但Service域名不通时,重点检查kube-proxy和DNS。先看iptables规则:iptables -t nat -L KUBE-SERVICES -n | grep ClusterIP,确认DNAT规则存在。再看CoreDNS的情况。实际排查中,域名解析超时占了一大半的比例,后面单独说。

第五步,跨节点通信仍然不通的时候才考虑抓包。在目标节点执行tcpdump -i eth0 port 4789(VXLAN模式)或者直接抓Pod网卡流量,确认封装层是否收到了包,以及有没有回包。

4.3 那次故障的真实根因:封装端口被防火墙拦截

那次问题是这么定位出来的:同节点上的服务访问B一切正常,换一个节点上的服务去访问B就超时。根据这个现象,基本排除B本身的问题,问题锁定在“跨节点通信”。在源节点和目标节点抓包时发现,源节点一直在发UDP 4789端口的包,但目标节点完全没收到。继续查目标节点的防火墙策略,发现防火墙放行了业务端口,但没放行集群通信用的UDP 4789端口。VXLAN封装包全被防火墙静默丢掉,跨节点通信自然全断。

修复方式就是放通节点之间UDP 4789端口。这个案例典型的点在于:所有业务端口都是通的,网络插件Pod也都是Running,表面看“一切正常”,实际上节点之间悄悄断了一条路。这也再次说明,排查K8s网络问题必须按链路逐段验证,只看静态状态远远不够。

5. 新手最容易踩的五个网络坑和处置方案

5.1 MTU不匹配导致“时通时不通”

这个坑非常隐蔽,症状是:ping小包通,ping大包(比如-s 1472以上)不通,或者大文件传输频繁卡住、超时。原因是VXLAN封装会在原始数据包外加几十字节的头部。物理网卡MTU默认1500,如果你没给网络插件设置MTU,它会默认按物理网卡的MTU来创建Pod虚拟网卡,结果封装后超过1500,只能分片,而很多网络设备是不处理分片包的,直接丢弃。处理方式就是统一把Pod网卡MTU调低,比如物理MTU为1500时,VXLAN模式的MTU改成1450左右,保留封装开销。规划集群时就要确认所有节点的物理MTU一致,别只在部分节点调。

5.2 集群网段和现有网络段冲突

某环境的Pod网段用了10.244.0.0/16,结果这个网段正好和办公网某个段重叠了。集群外部设备不管这些Pod是不是私有的,路由表里一看到这个网段就直接把包导去了错误的方向。最典型的表现是:某些服务访问正常,某些服务偶发超时,因为部分网络设备做了精确路由而另外一些没做。处理方案是,部署前强制规划好Pod网段和Service网段,用一套独立的、确认不跟现有业务网段重叠的地址段。集群建好再改网段,代价极大,基本等于重建。这个坑一定要踩在规划阶段之前。

5.3 NetworkPolicy的默认拒绝规则埋雷

Calico部署后通常会配NetworkPolicy,但很多人忽略了Namespace上可能被创建了一条默认拒绝规则。K8s的NetworkPolicy逻辑是允许优先还是拒绝优先,和防火墙设备一样,有默认策略。一旦有人创建了一条“default-deny”策略,没有被明确放行的跨命名空间流量就会全部被丢弃。现象极为隐蔽:所有Pod都是Running,容器内单独访问本地端口正常,但两个服务一联动就超时。排查时记得查看NetworkPolicy,kubectl get networkpolicy -A,不要只在Deployment和Service里打转。很多“看起来是网络故障”的问题,最后都在策略配置上翻车。

5.4 CoreDNS解析超时,反而让后端服务背了锅

有一类故障的根子在DNS解析。现象是应用里偶发“connection refused”或“could not resolve host”,每次重试又好了。不少人先怀疑后端服务挂了,其实问题出在CoreDNS上。CoreDNS作为集群内DNS服务,一旦所在节点CPU资源紧张,或者requests和limits没有合理配置,高并发时会丢弃或延迟解析请求。处置办法:给CoreDNS设置合理的资源请求与上限,把它的副本数调起来,必要时用节点亲和调度到性能稳的节点上。另外可以通过kubectl exec进入业务Pod内,执行nslookup服务名来验证解析是否正常。能单独把DNS这一层摘出来测,排错效率会高很多。

5.5 节点增删后的路由残留和conntrack问题

节点下线后,如果相关路由没有清理干净,其他节点的路由表或核心转发表里还保留着旧节点的信息,跨节点通信时包被发往一台已经不在的机器,表现为部分Pod通、部分不通,甚至同一个服务一会行一会不行。所以在生产环境删除节点前,一定要先执行节点排空和下线流程,让网络插件自己回收路由。如果已经出现路由残留,重启一下网络插件Pod会让路由表重新收敛,通常能解决大部分问题。另外宿主机conntrack表满也会造成丢包,现象是连接建立不了、超时严重,这种时候可以通过增大conntrack相关系统参数来缓解。这两个属于节点运维层面的细节,新手很容易忽略,但线上故障一半以上和它们有关。

先分享到这儿。K8s网络这块还能往下拆很多,比如Ingress控制、多集群通信、流量策略的精细化配置,每一块都有大量实操细节。如果这篇对你有帮助,顺手点个赞关注一下,后面我继续更新企业运维里的K8s实战系列。下次再遇到网络报错,记得先问自己一句:这层流量走到哪了,再从那个位置往下排查。

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

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

立即咨询