
作为一个常年泡在k8s集群里的运维我太清楚网络这块的水有多深了。很多朋友用kubeadm顺利地把集群初始化成功Master和Node节点状态也都是Ready结果一创建Pod就傻眼Pod一直Pending或者同节点的Pod能通跨节点的Pod ping不通更别提Service的ClusterIP访问时好时坏了。这些问题的根源几乎都指向同一个环节——容器网络。这一篇我就把k8s网络通信这条线彻底捋一遍重点是现在生产环境里最主流的两个CNI插件flannel和calico。我会从它们各自的原理、适用场景、部署要点到实际排障手段都过一遍让你看完之后不仅能选型遇到问题也知道从哪里下手。这篇文章适合刚完成集群基础搭建、正准备接入业务流量的人也适合那种“网络通了但不知道为啥通”的运维同学。我不会只给你甩一堆YAML而是会把每个关键步骤背后的“为什么”讲清楚这样你在企业内部做技术方案评审、出网络故障报告时才有东西可写、有依据可讲。1. K8s网络通信的核心模型与CNI机制1.1 K8s网络模型的四个硬性约定K8s对Pod网络提出了四点要求这四点直接决定了我们后面选什么插件、怎么配置。第一集群内每个Pod都拥有独立的ClusterIP所有Pod之间可以不通过NAT直接通信第二所有Node上的Pod都能与所有其他Node上的Pod通信第三Pod与Service之间也能直接通信第四Pod访问外部网络时通过NAT映射外部访问Pod则通过Service或其他入口。简而言之K8s希望Pod就像传统虚拟机一样拿到一个真实可路由的IP任何两个Pod之间能裸连。这个模型听起来简单但落地时有个最直接的矛盾Pod IP是K8s自己分配出来的网段比如默认的10.244.0.0/16或者10.233.0.0/16这些网段在物理网络里根本不存在路由器也不认识。那怎么让跨节点的Pod流量到达对方这就是CNI插件存在的根本意义——它需要在每台Node上做手脚让Pod网段的路由在集群内部变得“可见”或者把Pod流量封装起来穿过物理网络。1.2 CNI插件的标准工作流程CNIContainer Network Interface解决的其实是“容器运行时不知道怎么给容器配网”的问题。Kubelet在启动一个Pod时会调用CRIContainer Runtime Interface创建容器但容器的网络命名空间是空的没有网卡、没有IP。这时候CRI会回过头来调用CNI插件把网络配置好。我举个最直观的例子。Kubelet调用CNI时实际上是在执行一个可执行文件并传入一个网络配置的JSON。拿最基础的bridge插件来说它做的事情是创建一个Linux网桥cni0用veth pair把容器的一端接入cni0另一端放到容器的网络命名空间里同时给容器内的eth0配置IP、设置默认路由。整个流程就是下面这个JSON在驱动{ cniVersion: 0.3.1, name: mynet, type: bridge, bridge: cni0, isGateway: true, ipMasq: true, ipam: { type: host-local, subnet: 10.244.0.0/24 } }你注意看后面的ipam字段这里用的是host-local这种IP地址管理插件它的逻辑是从一个子网里跳着分配地址。而flannel和calico之所以被称为“CNI插件”是因为它们在标准的bridge或类似组件之上还实现了自己的IPAM比如calico-ipam和网络数据路径。这也解释了为什么你在每台Node上会看到一堆cni0、flannel.1、tunl0之类的网卡这些都是插件在配置网络时创建的虚拟设备。1.3 两种数据路径Overlay和路由方案既然底层物理网络不认Pod网段插件们就得各显神通。思路大体分两类。第一类是Overlay覆盖网络典型代表是flannel的VXLAN模式它把Pod的报文封装在UDP里外层用Node的真实IP做源和目的到了目标Node再解封装。好处是对底层网络没有要求只要Node间三层能通就行坏处是每个包都要多一层封装和解封装CPU开销和时延都上去了。第二类是纯路由方案典型代表是calico的BGP模式或者flannel的host-gw模式。它们的思路是直接在每台Node上加路由表比如“去10.244.1.0/24请走下一跳10.168.1.21”这样Pod流量不经过隧道直接通过物理网卡转发性能和传统网络几乎一样。但代价是它要求所有Node的网络二层是互通的也就是说Node之间能直接通过ARP找到对方如果中间隔着三层设备或跨机房路由模式就玩不转这时候只能回归overlay或者开启calico的IPIP封装。这两种路径没有绝对的优劣只有适配度的问题。我在第4节会专门做选型对比这里先记住结论网络环境简单、对性能敏感优先考虑路由模式网络环境复杂、Node跨网段多优先考虑overlay。2. Flannel实战最简单直接的Pod互通方案2.1 Flannel的三个后端到底选哪个Flannel最初是做覆盖网络出名的一个组件它的配置里有一项backend决定了数据路径。很多人配置时只抄官方YAML根本不看Backend类型结果被网络性能坑了才发现问题。第一个是历史上最早的udp后端用用户态的flanneld进程封装UDP包性能极差现在基本没人用了我提到它纯粹是为了让你在旧文档里看到时知道这是个过时方案。第二个是默认的vxlan后端这是目前生产环境里最常见的选择。VXLAN是内核实现的overlay方案把Pod的二层帧封装到UDP里默认走4789端口由内核的VTEP设备flannel.1处理性能比用户态UDP方案好得多。第三个是host-gw它直接在每个Node上写路由Pod流量从主机网卡原样出去性能和原生网络几乎没有区别。但就像前面说的它要求所有Node在同一个二层网络里也就是通过交换机直接相连中间不能有路由器。我给出的选型建议很直接。如果是测试环境、云服务器节点跨VPC、或者底层网络自己都说不清二三层拓扑直接用vxlan省心。如果确定整个集群所有Node都在一个广播域内且业务对时延敏感那就用host-gw把MTU还回来性能好不少。2.2 部署Flannel的详细过程用kubeadm初始化的集群里部署flannel基本就是apply一个文件的事。但要注意kube-controller-manager启动参数里必须带--allocate-node-cidrstrue和--cluster-cidr10.244.0.0/16否则Node不会拿到PodCIDRflannel也就没法分配网段。官方manifest里最重要的一段是ConfigMap里面定义了Pod网段和Backend类型。这是典型配置apiVersion: v1 kind: ConfigMap metadata: name: kube-flannel-cfg namespace: kube-system data: net-conf.json: | { Network: 10.244.0.0/16, Backend: { Type: vxlan } }部署完之后花两分钟做三件事来确认它正常工作。第一看Pod状态kubectl get pods -n kube-system -o wide确保flannel-kube-agents的所有Pod都Running。第二在Node上执行ip addr show flannel.1确认VTEP设备有IP。第三看路由表ip route | grep 10.244你会看到类似10.244.1.0/24 dev flannel.1 scope link的条目同时每个Node之间也有一条通过自己VTEP IP的前缀路由。如果这些都有基本可以断定网络组件工作正常。2.3 VXLAN模式下的跨节点通信路径我用自己的经验把VXLAN模式下跨Node的通信路径拆开讲保证你理解后排查问题有了体感。同Node上的两个Pod通信最简单PodA的包从veth进到cni0网桥网桥根据目的MAC直接转发给PodB的veth全程不出宿主机。跨Node就复杂了。Node1上的PodA10.244.1.2要访问Node2上的PodB10.244.2.3。PodA发现目标IP不在自己所在的子网于是把包发往默认网关也就是cni0网桥的IP。宿主机收到后查路由表找到10.244.2.0/24应该走flannel.1这个设备。内核于是开始VXLAN封装流程它需要一个VTEP对端的MAC地址这个MAC查询ARP表没有就发ARP请求。flanneld守护进程会监听ARP请求并且用自己的机制回复目标VTEP的MAC。拿到MAC后内核在原始报文外面包一层UDP头源IP是Node1的物理网卡IP目标IP是Node2的物理网卡IP端口是4789。这个UDP包到了Node2后Node2的内核解封装还原出原始的Pod报文再根据路由表转发给Node2上的cni0最后送到PodB。如果你要抓包验证在Node1上执行tcpdump -i eth0 udp port 4789 -nn -e就能看到源和目的MAC、IP都是Node的物理地址里面嵌套的才是Pod的IP信息。这个“外层是Node地址内层是Pod地址”的特征就是我判断流量是否走了VXLAN封装的关键。2.4 Flannel的先天短板我必须坦白讲flannel胜在简单但它的短板在企业生产环境里会越来越明显。第一它不支持NetworkPolicy这意味着你无法通过K8s原生的网络策略去做Pod级别的访问控制。如果你的企业有等保要求或者安全部门明确要求做微隔离那flannel基本可以告别生产环境了。第二它没有BGP能力所有路由都是flanneld自己计算下发集群规模大了之后路由条目和更新延迟都是问题。第三它在多网卡环境下容易犯迷糊如果Node有多块网卡你得手动指定--iface参数否则flannel可能选错物理网卡导致跨节点通信全部失败。我在实际项目里见过太多因为网卡选错导致整个集群网络瘫痪的案例这里提醒你注意每个Node上都要检查flanneld的启动参数和日志确认它用的网卡IP确实是Node之间能互通的那个IP。3. Calico实战BGP与NetworkPolicy的组合拳3.1 Calico的核心组件和职责Calico相比flannel是一套更完整也更复杂的三层网络方案。它的架构里有几个关键角色Felix跑在每个Node上负责管理和配置iptables规则、路由规则以及网卡接口确保Pod的流量按照策略被放行、阻断或转发。BIRD这是BGP路由协议的客户端实现每个Node上的BIRD会把本机管理的Pod网段通过BGP广播给其他Node同时从别的Node学习路由。calicoctl或kubectl calico则是管理工具用于查看和配置Calico的策略、节点状态。你有没有发现Calico本质上是在把整个集群当成一个“微型互联网”来管理——每一台Node都相当于一个路由器通过BGP协议交换各自管理的Pod网段路由信息。这种设计让它天然适合大规模部署因为BGP本来就是互联网级的路由协议。3.2 IPIP和VXLAN两种封装模式怎么选Calico的数据路径有两种封装模式加一种无封装模式。IPIP模式是Calico最经典的封装方案它把Pod报文封装进一个IP包里外层协议号是4也就是IP in IP对应的虚拟设备是tunl0。VXLAN模式和flannel类似走UDP 4789。无封装的就是BGP纯路由模式Pod报文不封装直接通过物理网络转发。部署时你会在calico的ConfigMap或安装参数里看到CALICO_IPV4POOL_IPIP这个关键项它有几种取值。Always表示所有跨Node流量都走IPIP封装Never表示从不封转走纯BGP路由CrossSubnet表示只有跨子网的流量才封装同子网内走纯路由。这个设计非常实用因为同机架同交换机下的流量封装纯属浪费CrossSubnet能兼顾性能和跨网段连通性。我的建议是如果网络环境简单把CALICO_IPV4POOL_IPIP设为Never享受最好的性能如果Node跨VPC或跨机房设成Always或CrossSubnet都行但要清楚封装带来的时延损耗。切换这个参数需要重建calico的IPPool资源或者重启所有calico-node业务会短暂中断一定要在维护窗口操作。3.3 部署Calico的两种方式对比现在企业里部署Calico的主流方式有两种。一种是老式的manifest直装方式官方会给你一个包含所有RBAC、DaemonSet、ConfigMap的YAML包你改改网段参数就可以apply。另一种是新版的Operator方式需要先安装tigera-operator再定义一个CustomResource看板。我给多数生产环境的建议是用Operator方式。原因很简单它把Calico组件的生命周期都托管了版本升级是平滑的配置变更也能通过自定义资源来调整很适合后续持续运维。如果只是临时测试或者维护一套存量老集群那manifest直装也没问题。Operator安装大致是这么几步。先apply operator清单kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/tigera-operator.yaml然后创建一个CustomResource文件关键参数是cidr和encapsulation。cidr必须和kubeadm初始化时指定的--pod-network-cidr一致否则Pod网络会乱套。示例apiVersion: operator.tigera.io/v1 kind: Installation metadata: name: default spec: calicoNetwork: ipPools: - name: default-ipv4-ippool cidr: 10.244.0.0/16 encapsulation: VXLANCrossSubnet这个封装方式我选的是VXLANCrossSubnet不同子网的Node之间走VXLAN同一子网内走纯路由。灵活而且不用太担心底层网络限制。部署完成后kubectl get pods -n calico-system看到calico-node和calico-kube-controllers都Running再用calicoctl node status查看BGP peer状态。如果BGP会话是Up说明各Node已经通过BGP交换了路由跨Node通信马上就能通。3.4 NetworkPolicyCalico比Flannel多出来的核心竞争力Calico能在企业生产环境里被大量选用很大一部分原因是它原生支持Kubernetes NetworkPolicy。K8s的NetworkPolicy是一套声明式API允许你定义Pod之间哪些流量可以被放行。它不只是在网络层做限制还能到四层的端口级别甚至配合Calico的扩展可以做到七层的HTTP方法级别。我举个例子。现在有一个后端服务叫backend只允许前端服务frontend访问它的8080端口其余Pod一概不许。那NetworkPolicy就长这样apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: backend-policy namespace: production spec: podSelector: matchLabels: app: backend policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app: frontend ports: - port: 8080这段配置的意思非常直白我不管你是哪个命名空间的只要Pod的label不是appfrontend你访问backend的8080端口就会被Calico在宿主机上用iptables规则给DROP掉。你可能会问这不就是安全组干的事吗对但区别在于安全组是配在虚拟机或物理机层面的粒度粗NetworkPolicy是配在Pod层面的能够做到同一台Node上不同Pod的精细隔离。这在微服务架构里特别有价值比如合规审计要求各个服务之间必须白名单访问NetworkPolicy就是落地工具。我服务过的所有企业客户凡是安全要求严格的最后几乎都选了Calico。4. 企业场景选型Flannel还是Calico4.1 六个维度的直接对比很多刚接触K8s的人会问既然Calico功能这么全为什么还有人用Flannel答案在运维成本和性能开销上是有差别的。对比维度Flannelvxlan/host-gwCalicoIPIP/BGP/VXLAN部署复杂度低apply即用中高组件多BGP概念需理解NetworkPolicy不支持完整支持扩展策略更强数据路径overlay或host路由纯BGP路由或隧道封装大规模集群支持一般路由更新机制朴素好BGP天然适合大规模底层网络要求vxlan模式要求低host-gw要求二层通纯BGP要求二层通封装模式要求低资源占用少flanneld很轻较多每节点有Felix和BIRD进程单独看每一种方案都能跑通但你把它放到企业的具体约束里答案就很清楚了。4.2 不同场景下的现实选择如果你的集群是开发测试环境、边缘节点的小集群、或者业务对性能完全不敏感我就推荐用Flannel vxlan。它配置简单出问题的概率小团队成员对它的理解和维护难度都低。我自己做PoC测试时也经常临时装一个flannel十分钟搞定方便验证业务逻辑。但如果是生产环境并且你看到下面几个条件中的任意两个我建议直接上Calico。条件一规模超过30个Node希望路由收敛更快更稳条件二需要NetworkPolicy做应用层访问隔离跑合规流程条件三集群跨多个网段底层网络复杂条件四有一个独立网络团队愿意为网络方案多花点运维精力。别小看这个决策。曾经有个客户用Flannel跑了半年业务稳定后来甲方要求提供网络隔离方案才发现需要整个切到Calico。这个迁移虽然不算地狱级难度但要维护窗口、要同步改安全策略、还要处理存量连接的中断工作量大不少。所以从一开始就做好选型比后面亡羊补牢划算得多。4.3 从Flannel迁移到Calico的注意点如果你需要在已有的Flannel集群上切换到Calico这里有几个我踩过的坑必须坦白提醒你。首先网络层的基础设施要清干净。Flannel在每台Node上创建了cni0网桥、flannel.1设备还往iptables里写了很多规则这些不会因为你卸载flannel的DaemonSet就自动消失。你必须手动在每个Node上删掉这些残留具体操作包括ip link delete flannel.1、ip link delete cni0以及清空CNI配置文件目录/etc/cni/net.d/下以10-开头的flannel配置换成calico生成的配置。其次切换期间Pod会中断这个是绕不过去的。要提前通知业务方最好是选在一个几乎没有流量的时间窗口操作。我建议先部署好Calico的所有组件确认BGP会话正常再一次性卸载Flannel。操作顺序是反过来的话集群会短暂出现“没有任何CNI可用”的状态新建Pod全部失败。最后原有的ServiceClusterIP不会变因为service网段在kubeadm阶段就定死了CNI插件不碰这块。但是NetworkPolicy是你之前没有的东西切换后如果业务网络异常先看看是不是有什么历史遗留的默认拒绝策略不要一上来就怀疑Calico本身出故障这个方向性错误我见过太多次了。5. 网络故障排查实战记录5.1 排查网络问题的基础工具箱K8s网络故障和传统网络故障有个不同点很多问题藏在不同层级的虚拟设备里。我平时排查的时候有一个固定顺序先看Pod的内部状态再逐层往外看宿主机的网卡、路由、iptables最后才抓包看数据包是否真的到了某个点。常用的命令集合我列一下kubectl get pod -o wide确认Pod所在的Nodekubectl exec -it pod -- /bin/sh进容器里看路由和网卡宿主机的ip addr看各网卡IPip route看路由表ip neigh看ARP邻居表arping测试二层连通性ping -c 4测试三层连通性tcpdump -i iface抓包看协议栈行为iptables -L -n -t nat/filter查一下有没有被意外DROP。如果你发现某个Pod网络时通时断优先怀疑MTU这个坑我在下一小节细说。5.2 经典场景一跨Node的Pod ping不通这是所有K8s网络问题里最经典的一个。一个Pod能ping通同Node的另一个Pod但是跨Node就不行。遇到这个情况我按下面的思路逐步排查。第一步在源Pod里kubectl exec执行ping 目标PodIP确认不通。第二步在源Pod所在的宿主机上ping 目标PodIP如果宿主机能通而Pod不通问题一定出在Pod里的路由或宿主机的策略上如果宿主机也不通问题就更靠外一层。第三步检查源Node和目标Node之间的物理连通性ping 目标NodeIP确认底层网络没断开。第四步查看源Node路由表有没有10.244.2.0/24这样的前缀路由。如果没有说明CNI没有把路由同步完成要么是flanneld有问题要么是Calico的BGP会话没起来。第五步检查防火墙很多云环境的安全组默认只放行某些端口VXLAN的UDP 4789、Calico的BGP 179端口都需要在Node的安全策略里放行。还有一招很多新手不知道检查宿主机iptables的FORWARD链。K8s的流量很多都要经过宿主机的FORWARD链而不是INPUT链但某些安全加固脚本会把FORWARD策略默认设为DROP这就导致Pod流量到了宿主机直接被丢。我在某客户那里排查了一个下午最后发现就是某个基线加固脚本把FORWARD设成了DROP。你可以用iptables -L FORWARD -n看策略发现是DROP就赶紧加放行规则。5.3 经典场景二Pod之间有IP但Service访问失败这种情况的表现是Pod的IP之间可以直接访问但换成ClusterIP就不行或者时好时坏。ClusterIP是虚拟IP由kube-proxy负责把流量转发到后端的Pod上。现代K8s默认用IPVS模式来做负载均衡。排查时先确认kube-proxy的转发模式是iptables还是ipvskubectl logs -n kube-system kube-proxy-xxxx | grep mode。如果是ipvs模式看ipvsadm -Ln里是不是有ClusterIP的记录没有的话说明Service的endpoints没同步或kube-proxy异常。然后检查IPVS规则里的后端地址如果有多个Pod看是不是都在。还有一种情况是连接跟踪表被打满现象表现为新连接建立失败而旧连接正常。这是在Node上查看cat /sys/module/nf_conntrack/parameters/hashsize和conntrack -L数量如果接近表容量上限要么调大参数要么排查是不是有大量短连接导致表项激增。5.4 经典场景三MTU不一致导致的大包不通这个问题我愿意单独拿出来讲因为它真的非常隐蔽。现象是ping通但curl业务接口很慢或直接超时或者ping -s 1472都不通但普通ping通。本质原因物理网卡MTU是1500VXLAN封装会把Pod报文加一个50字节左右的外层头那Pod网卡MTU如果不减逻辑上超过物理承载上限大包就被丢弃。Flannel的vxlan模式默认会给Pod网卡设置MTU为1450Calico的VXLAN也是类似逻辑。如果有人在创建CNI配置时手动改了MTU或者物理网络本身MTU是1400比如某些叠加了其他隧道的网络就会导致包能发出去但对方收不完整。排查方法用kubectl exec到Pod里执行cat /sys/class/net/eth0/mtu然后逐层比对。处理方式是把Pod的MTU调成比物理网卡小适当值或者在CNI配置里显式指定MTU。这个坑在云环境里尤其常见因为云厂商的底层网络可能已经叠加了各种隧道物理MTU可能根本不到1500。5.5 排障逻辑的最终心得我可以很负责任地讲K8s网络排障八成的坑都可以归纳到三个方向路由不对、策略阻塞、MTU不匹配。路由不对看路由表和BGP状态策略阻塞看iptables和云安全组MTU不匹配看各层网卡的MTU值和抓包对比。你把这三个方向想清楚哪怕不知道某个具体命令也能顺着思路找到问题。我个人在实际操作中的体会是别一上来就拿tcpdump去抓包先在一层一层把“哪里通、哪里不通”确认完最后再抓包验证细节效率才是最高的。很多新手喜欢直接抓包结果抓了一堆数据也不知道看什么浪费半天时间。链路状态确认是排障的地基地基都不打牢分析纯属碰运气。另外再分享一个小技巧网络插件在集群初始化之后第一件事要做的就是检查Pod的网段规划。很多人在搭集群时随手填一个Pod网段后来发现和公司内部某段网络冲突或者和VPC路由冲突导致Pod之间访问出现各种奇怪的路由跳变。这个规划一定要在初始化之前做和生产环境的现有网段避开最好用A类私有地址里比较不常用的段比如10.244.0.0/16、10.233.0.0/16。这种事看起来技术含量不高但直接影响集群的长期稳定值得认真对待。