
容器网络这个话题放到技术群里永远是热门。凡是上手过容器的人十有八九都在这里栽过跟头容器明明跑起来了互相之间却 ping 不通服务都部署好了负载均衡就是不生效换一套网络插件整个集群的流量直接卡死。这些问题的根源往往不在容器本身而在容器背后那套网络算法和模型。这篇文章要聊的是容器网络的核心机制与算法从网络命名空间隔离、Bridge 桥接与端口映射到 IPAM 地址分配、VXLAN 封装转发再到服务发现与负载均衡。也正好对应“网络的主要算法”这个系列里最具当代意义的容器网络部分。适合两类人看一是刚接触容器、准备系统打基础的后端开发者二是已经在跑容器服务、遇到网络问题只会“重启大法”的运维新手。把底层逻辑想清楚之后你会发现排障不是靠试而是靠推理。1. 容器网络到底在解决什么问题1.1 从“单机单服务”到“单机几十个容器”的跃迁传统服务器部署一个服务流程非常简单装好操作系统绑定一个 IP开放 80、3306、6379 这些端口防火墙按端口放行就行了。一台物理机通常只跑一个或少数几个应用端口冲突很少发生网络配置基本是一次性的。容器化之后这个前提被彻底打破了。一台物理机上能同时跑几十个甚至上百个容器每个容器在逻辑上都是一个独立的小系统。如果所有容器都直接共用宿主机的 IP 和端口那 80 端口就只能给一个容器用其他容器都得排队这显然不可接受。所以容器网络要回答一个根本问题如何让软件自动创建的每一个容器在几秒钟内拿到正确的网络身份包括独立 IP、路由规则、DNS 配置既能与同一台机器上的邻居通信也能跨机器通信还能受控地访问外网。这里说的每一个细节背后都是算法在支撑。1.2 网络命名空间容器网络的独立王国想理解容器网络绕不开 Linux 的网络命名空间Network Namespace。这是一个很朴素但极其重要的机制内核允许你创建出多个互相隔离的网络栈每个栈都拥有自己独立的网卡、回环接口、路由表和 iptables 规则。容器就是靠这个机制实现网络隔离的。你可以把网络命名空间想象成一个个一模一样的“独立房间”房间之间原本没有门互不打扰。Docker 这类容器引擎的职责就是在这些房间之间修路、架桥。你执行docker run的时候引擎会做这么几件事创建一个全新的网络命名空间给这个命名空间分配一个虚拟网卡再创建一对 veth 虚拟网线一头插进容器的命名空间变成 eth0另一头插进宿主机上的虚拟交换机也就是 Bridge。容器看到的网络拓扑跟宿主机是完全不同的两张地图。因为每个容器都有自己的 IP所以两个容器同时监听 8080 端口也互不冲突。这就是隔离带来的好处。但坏处也很明显跨容器通信、跨主机通信的复杂度全上来了必须由额外的网络模型和算法来弥补。1.3 容器网络里到底涉及哪些经典算法把容器网络拆开看里面的算法其实都是计算机网络的“熟人”只不过换了个场景重新登场。第一类是二层算法比如 MAC 地址学习、ARP 请求与应答负责解决同一链路里“谁是谁”的问题。第二类是三层算法比如最长前缀匹配的路由查询、IP 地址分配与回收负责解决“数据包往哪走”的问题。第三类是四层的连接与转发算法比如 NAT 端口映射、conntrack 连接跟踪、IPVS 调度器里的轮询和哈希哈希负责解决“服务怎么被人稳定访问”的问题。后面几个部分我会按这个脉络逐个展开。你会发现容器网络并不是一套全新发明的技术而是把传统网络算法在虚拟化环境里重新组合了一遍。理解了这个角度很多现象就能解释通了。2. 四大基础网络模型的算法基因2.1 Bridge 模式MAC 自学习与 DNAT 映射Docker 默认的网络模式是 Bridge 模式。docker run出来的容器默认会连到宿主机上的docker0这个虚拟网桥上拿一个类似 172.17.0.x 的私有 IP。这个网桥在 Linux 内核里实现的就是一个标准二层交换机它遵循的算法跟物理交换机一模一样MAC 地址自学习。我给你还原一下这个过程。假设容器 A172.17.0.2想和容器 B172.17.0.3通信。A 首先检查自己的 ARP 缓存如果不知道 B 的 MAC就发一个广播 ARP 请求。这个请求会经过 veth 网线到达 docker0 网桥。网桥一开始并不知道 B 的 MAC 在哪个端口于是把广播帧向所有端口转发。B 收到之后应答自己的 MAC网桥就把“B 的 MAC 对应哪个端口”记进自己的 MAC 地址表里。后续 A 和 B 之间的数据帧网桥查一下 MAC 表就能精确转发不再广播。这个叫“MAC 地址自学习”的算法是二层交换的核心也是同一台宿主机上容器互通的基础。容器要访问外网时就没这么简单了。172.17.0.2 是一个私有地址外面的网络根本不认它。Docker 的处理方式是容器发出的数据包先在宿主机上做一次源地址转换SNAT / MASQUERADE把源 IP 换成宿主机的公网或物理网卡 IP回来的时候再反向转换回去。这里起作用的是 iptables 规则。反过来外部访问容器需要使用端口映射也就是你常见的-p 8080:80。这个参数背后的算法是一条 DNAT 规则当外部数据包到达宿主机的 8080 端口时内核会查找 iptables 的 NAT 表命中之后把目的 IP 和端口改写成 172.17.0.2:80再转发进容器网络。为了让应答包能原路返回内核还有一个连接跟踪机制conntrack它维护一张“连接状态表”把这条改写前后的五元组信息记下来。这就是为什么你不需要为每一个返回包再写一条规则连接跟踪算法帮你做了状态对称性处理。2.2 Host 模式与 None 模式两个极端Host 模式是另一个极端容器不创建新的网络命名空间直接共享宿主机的网络栈。容器里的 eth0 就是宿主机的物理网卡容器监听 8080 端口就相当于宿主机监听 8080 端口。这种模式的好处很明显没有 veth 转发没有 NAT没有额外的封装性能几乎和裸机进程一样高。很多对网络延迟极其敏感的场景比如高性能缓存、实时数据处理会倾向于用 Host 模式。但代价也很大端口隔离彻底失效一个容器占用了 8080另一个容器就不能再用。生产环境里除非你很清楚自己在做什么否则不建议大规模使用 Host 模式否则端口冲突会让你怀疑人生。None 模式则最干净容器里只有一个回环接口没有任何外部网卡。这种模式适合纯粹做计算的临时容器不需要网络访问也适合那些后续需要自己手工接入复杂网络环境的场景先用 None 模式把容器创建出来再由外部工具把网卡和路由配置进去。它把“网络配置”这件事完全交给了你的脚本或编排系统算是一种把控制权最大化的模式。2.3 Overlay 网络VXLAN 封装与 VTEP 学习算法单机上的 Bridge 模式解决不了跨主机通信的问题。容器 A 在宿主机 X 上容器 B 在宿主机 Y 上两个宿主机之间隔着一张物理网络A 的 172.17.0.2 这个地址在 Y 上完全没有意义。Overlay 网络叠加网络的思路是在已有的物理网络之上再虚拟出一张覆盖所有容器的逻辑网络。最主流的实现是 VXLAN。它的核心算法看起来复杂拆开其实就三步。第一步容器 A 发一个二层帧给容器 B这个帧到达宿主机 X 上的 VTEPVXLAN 隧道端点。第二步VTEP 查自己的转发表发现目的 MAC 在宿主机 Y 后面于是把原始二层帧整个装进一个 UDP 包里外层目的地址写成宿主机 Y 的物理 IP通过物理网络发出去。第三步宿主机 Y 的 VTEP 收到 UDP 包解封装还原出原始二层帧再投递给容器 B。这里有两个关键算法。一个是 VTEP 的地址学习它跟普通交换机一样需要学习“哪个 MAC 藏在哪个远端 VTEP 后面”只不过学习结果不是一层 MAC 表而是一张“MAC 到远端主机 IP”的映射表。另一个是 VNI 隔离VXLAN 报文里有一个 24 位的 VXLAN Network Identifier相当于二层网络里的 VLAN但数量从 4000 多直接跳到 1600 万不同 VNI 之间的流量天然隔离。你必须记住的代价是封装开销。VXLAN 会额外增加大约 50 字节的头部如果物理网络 MTU 是 1500那 overlay 网络里的内层报文就必须把 MTU 降到 1450 或者更小。很多“能 ping 通但传大文件失败”的诡异问题罪魁祸首就是 MTU后面排查部分我会细说。3. IPAM地址分配里的算法设计3.1 单机地址池的分配与回收每个容器都要有唯一的 IP谁来给谁来保证不重复这就是 IPAMIP Address Management地址管理要做的事。单机场景下Docker 守护进程会在创建容器时从配置好的子网里挑一个空闲 IP。这个分配过程用到的算法本质上就是一个位图管理把 172.17.0.0/24 里的 254 个可用地址当成一张位图每个 bit 表示对应地址是否被占用。容器创建时按顺序扫描位图找到第一个空闲地址把 bit 置 1容器销毁后把 bit 清 0。这个算法简单、快速、确定性高非常适合单点的场景。但如果你以为生产环境就是这么做 IPAM 的那就太小看规模问题了。试想一下一个集群有几千个节点每个节点同时在创建和销毁容器如果所有分配请求都集中到一个中心服务那这个服务就是性能瓶颈而且一旦它抖动整个集群新增容器的速度都会被拖垮。3.2 多主机场景下的子网划分与协调为了解决中心化分配的瓶颈主流方案选择了“按节点划分网段”的策略。以 Flannel 为例它给整个集群设置一个大网段比如 10.244.0.0/16然后给每个节点划分一个 /24 的子网。节点 A 拿到 10.244.1.0/24节点 B 拿到 10.244.2.0/24以此类推。划分好之后节点内部的容器从哪个网段取 IP就完全由本节点的本地 IPAM 决定不需要再向中心服务逐个申请。这样做的算法优点非常明显把全局的地址申请压力分散到了每个节点只有节点上线时才会做一次子网分配平时的容器创建销毁都是本地操作延迟极低也不存在中心瓶颈。代价是地址利用率下降。比如一个节点实际只跑了 3 个容器但它独占了一个 /24 网段254 个地址里有 251 个是浪费的。好在 IPv4 私有地址本来就规划了比较大的范围对于绝大多数集群规模来说这点浪费是完全可以接受的。这里面的权衡逻辑值得反复体会分布式系统里用空间换时间、用冗余换确定性是极其常见的手法。3.3 地址冲突与分布式一致性多节点各拿各的网段看起来完美但它依赖一个前提节点之间分配子网时不能发生冲突。两个节点如果同时拿到了 10.244.1.0/24那这个网段里的所有容器 IP 都会撞车网络立刻变得不可用。为了防止这种情况节点领取子网这个动作必须被严格串行化。Kubernetes 和 Flannel 的做法是把分配记录写入 etcd利用 etcd 的分布式一致性能力和租约机制保证一次只有一个节点能成功领取某个子网。这背后的算法是分布式锁和租约lease节点领取成功后获得一个租约租约到期前节点持续持有这个网段如果节点挂了租约超时后这个网段才会被回收重新分配。这里有一个在生产环境里很常见的坑如果你在排障时发现两个节点上报了完全相同的 Pod 网段千万不要只盯着网络层看先查 etcd 一致性和节点租约状态。网络分层之间的问题常常是上层算法失序导致的这个方向错了排查多久都是白费。4. 服务发现与负载均衡算法的主战场4.1 DNS 服务发现与轮询算法容器 IP 是短时间内频繁变化的资源。一个服务从 3 个副本扩容到 10 个副本旧容器销毁新容器创建地址一直在变。如果客户端直接写死某一个 Pod IP副本一换就失联。于是就有了服务发现通过一个稳定名字找到一组随时变化的后端地址。最基础的实现是 DNS。在 Kubernetes 里Pod 可以把服务的全限定域名例如 svc-name.namespace.svc.cluster.local交给 CoreDNS 解析得到的答案是服务的稳定虚拟 IP。如果服务是 Headless ServiceDNS 会直接返回所有后端 Pod 的真实 IP 列表。客户端拿到多个 IP 之后轮流使用这就是最简单的轮询算法Round-Robin。轮询算法实现成本极低它的问题在于状态感知能力弱。DNS 客户端会缓存解析结果如果某个 Pod 已经挂了但缓存里还有它的 IP请求就可能打到不存在的地址上。所以生产环境一般不会拿普通 DNS 轮询做唯一负载均衡手段而是把它和 TTL 策略、健康检查机制结合起来使用。4.2 iptables 与 IPVS 的负载均衡算法当请求到达集群的 Service 层时真正干活的是 kube-proxy。它有两种主流转发模式对应两套完全不同的算法。iptables 模式的工作原理是kube-proxy 为每个 Service 生成一组 iptables 规则规则用 statistic 模块做概率匹配让每个新连接按近似等概率被 DNAT 到某一个后端 Pod。这里用的是随机选择算法配合 conntrack 做到连接级别的粘滞同一个连接一旦被选中某个后端后续包都走同一个后端不会被中途换掉。iptables 模式的优点是简单、兼容性好缺点是大规则集下查找是线性的后端数量一多规则数量膨胀性能和稳定性都会变差。IPVS 模式则内置了完整的内核级调度算法。通过kube-proxy --modeipvs启动后你可以在节点上用ipvsadm -L -n查看它为每个 Service 建立的虚拟服务以及调度策略。可用的算法很多常用的几个是rr轮询请求依次分发到每个后端适合后端性能均等的场景。wrr加权轮询给高性能后端配更大的权重让它承担更多流量。lc最少连接优先把请求分给当前活跃连接数最少的后端适合长连接场景。wlc加权最少连接在最少连接基础上叠加权重因子也是 LVS 默认算法。sh源地址哈希同一来源的请求永远打到同一个后端适合需要会话保持的业务。下面这个对比表能帮你更直观地做选择算法核心依据典型场景需要注意的点rr计数器顺序后端配置均匀的 Web 集群后端性能差异大会拖慢整体wrr权重比例混合配置的新老机器混部权重需要根据实测调整lc活跃连接数长连接服务、数据库中间层短连接场景下统计意义弱wlc连接数与权重大多数 LVS 生产场景权重和连接数需同时监控sh源地址哈希需要会话粘滞的业务后端扩容时哈希分布会变化选算法从来不是越快越好。很多团队把默认调度从 rr 改成 lc 之后发现响应时间反而上升了原因就在于业务是短连接每条请求的生命周期极短连接数算法还没来得及发挥统计优势。理解业务连接特征再选调度算法才是正确顺序。4.3 一致性哈希从缓存到容器网关在网关、服务网格或者分布式缓存组件的实现里经常会看到一个比轮询和连接数都更聪明的算法一致性哈希。普通哈希取模有一个大缺陷。假设后端从 4 个扩容到 5 个按hash(key) % N计算绝大多数请求的映射结果都会改变意味着缓存全部失效后端之间要发生大量数据迁移。这对容器环境尤其致命因为扩缩容是常态。一致性哈希的思路是把所有哈希值组织成一个环节点按自己的哈希值分布在环上请求也按哈希值落位然后顺时针找到最近的节点。新增一个节点时只有环上它前后那一小段范围内的请求会受影响其他请求完全不动。再配合虚拟节点机制让哈希环更均匀就能比较好地解决数据倾斜问题。在容器网络里这个算法的应用很多Ingress 网关做基于 URI 的会话保持、服务网格里的定向路由、Redis 集群的 key 分布底层都在用一致性哈希。理解它的核心思想“影响最小化”你就知道为什么容器世界这么偏爱它了——因为容器世界里节点生生死死本来就是常态。5. CNI 与主流容器网络方案对比5.1 CNI 接口与插件调用流程Docker 有自己的一套网络实现但容器编排生态真正广泛使用的是 CNIContainer Network Interface。CNI 是一套标准接口定义了一种简单的命令协议容器引擎需要给容器配网时就调用 CNI 插件的ADD命令容器销毁时调用DEL命令查询状态用CHECK。整个调用链是这样一个流程Kubernetes 的 kubelet 想启动一个 Pod它通过 CRI 接口通知 containerd 这类容器运行时运行时发现这个 Pod 需要加入网络就按照配置调用 CNI 插件。插件拿到 Pod 的网络命名空间和网络配置后会创建 veth、配置路由、分配 IP最终返回一份标准的网络结果。这份结果里包含了容器的接口名称、IP 地址、网关等关键信息。这套标准化协议的意义在于网络方案和容器运行时解耦了你可以把 Flannel 换成 Calico容器运行时完全不需要重新适配。5.2 Flannel、Calico、Cilium 的算法视角主流方案非常多但从算法和数据平面的角度可以分成三大类。Flannel 走的是 VXLAN overlay 路线。它简单易用默认情况下每个节点分一个子网节点之间用 VXLAN 隧道通信。它的转发路径是Pod → veth → cni0 网桥 → flannel.1 → VXLAN 封装 → 物理网卡 → 目标节点。多了封装解封装这一层吞吐量和延迟会有一点折损但部署和运维门槛最低非常适合中小集群。Calico 走的是纯三层路由路线。它不封装流量每个 Pod 的网段被当作一条路由通过 BGP 协议在集群内外传播。数据包路径是Pod → veth → 宿主机路由表 → 物理网卡 → 目标节点。没有额外封装转发路径短性能好代价是需要管理和维护 BGP网络层排查复杂度更高。它更适合对性能有要求且团队有网络基础的大规模集群。Cilium 是新一代的 eBPF 方案。它的数据包路径不再依赖 iptables 和传统路由表而是让数据包在挂载于内核的 BPF 程序里完成转发、负载均衡和策略控制。算法层面它用 BPF map 做哈希查找把 Service 的负载均衡从规则遍历变成了 O(1) 的查表性能和扩展性都领先。代价是新东西的学习曲线陡峭对内核版本要求也比较高。三者对比可以浓缩成这张表方案数据面核心封装开销性能特征适合规模FlannelVXLAN 隧道有约 50 字节中部署简单中小集群优先CalicoBGP 路由无原生模式较高需懂路由大规模、性能敏感CiliumeBPF 钩子无高但内核要求高大规模、微服务重度场景5.3 路由查询最长前缀匹配算法无论用哪种方案最终在宿主机的路由表里都会出现成百上千条路由规则。数据包到了宿主机之后内核怎么从这么多条路由里快速选出正确的出口答案是 LPM最长前缀匹配算法。这个算法的精髓一句话就能说透多条路由都能匹配时前缀最长的那条获胜。比如有三个网段的路由10.244.0.0/16 指向一个方向10.244.1.0/24 指向另一个方向10.244.1.128/25 再指向第三个方向。现在有一个目的地为 10.244.1.200 的包它同时匹配这三个前缀但内核只会选择 /25 那条。这个规则对容器网络有一个很实际的影响你必须谨慎设计网段和路由发布范围。大规模集群里如果无意中写了一条过宽的汇总路由所有本来该走细分路由的流量都会被错误引导。我见过不止一次因为某节点上手工加了一条 0.0.0.0/0 或者过宽的网段路由导致整个节点的容器出网异常。排查到最后问题不是方案本身而是路由算法里“最长前缀匹配”的优先级被忽略了。6. 实操还原容器网络的关键算法细节6.1 创建 Bridge 网络并验证容器间通信理论部分说了这么多不如直接动手验证一遍。下面的操作在安装了 Docker 的 Linux 环境下就能复现。先创建一个自定义的 Bridge 网络指定子网docker network create -d bridge --subnet172.20.0.0/24 demo-net接着启动两个容器并加入这个网络docker run -itd --rm --network demo-net --name c1 busybox docker run -itd --rm --network demo-net --name c2 busybox进入 c1尝试 ping c2docker exec -it c1 sh -c ping c2你会惊讶地发现c2这个名字在容器里直接被解析成了 172.20.0.3。这不是 Linux 系统自带能力而是 Docker 内置的 DNS 服务在做解析容器名到 IP 的映射被自动注册并下发。这意味着你不需要记住任何 IP直接按名字访问容器即可。这就是一个简化版的服务发现理解这一点再去看 Kubernetes 的 Service 和 DNS逻辑就是同一个套路的升级版。6.2 查看端口映射背后的 iptables 规则启动一个带端口映射的容器docker run -d --rm -p 8080:80 --name web nginx然后在宿主机上查看 NAT 表规则iptables -t nat -L -n | grep 8080你会看到一条 DNAT 规则类似下面这种DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80这条规则就是端口映射的算法实体所有目的端口为 8080 的数据包目标地址都被改写成 172.17.0.2 的 80 端口。你还可以再看一下 POSTROUTING 链里针对 172.17.0.0/16 的 MASQUERADE 规则那就是容器出网的源地址转换。6.3 用 tcpdump 抓一次 VXLAN 封装包如果你的环境里有 Flannel 或其他 VXLAN 方案的集群在节点上执行tcpdump -i eth0 udp port 4789 -nn -vv然后从 Pod A ping Pod B跨节点你会看到大量目的端口为 4789 的 UDP 包。这些包里内层才是真正的 ICMP 报文外层只是普通的 UDP/IP 头。这一刻你就能直观感受到 Overlay 的算法路径原始二层帧被装进了一个“信封”靠着外层的宿主 IP 穿越物理网络到了目标节点再拆开。没有这个抓包过程你很难真正相信容器数据包在网络上原来是长这个样子的。6.4 用 ipvsadm 验证不同调度算法如果你有节点开启了 kube-proxy 的 IPVS 模式可以直接用ipvsadm -L -n查看当前所有 Service 对应的虚拟服务和调度算法。想手动验证不同算法的行为差异也可以在节点上直接用 LVS 模拟ipvsadm -A -t 10.0.0.1:80 -s wrr ipvsadm -a -t 10.0.0.1:80 -r 172.20.0.2:80 -w 2 ipvsadm -a -t 10.0.0.1:80 -r 172.20.0.3:80 -w 1接着用连续请求打到 10.0.0.1:80再观察连接统计。你会发现权重为 2 的后端拿到的连接数大约是权重为 1 的后端的两倍。看到这个分布之后再回看前面那张算法表就能记住 wrr 的核心是“按权重配比”而不是绝对平均。7. 常见问题与排查技巧实录7.1 容器网络修不好先按数据包的路径拆解遇到容器网络故障最容易犯的错误是“头痛医头”ping 不通就反复重启容器或者直接重刷网络插件。正确的思路是像一个数据包一样沿着它的完整路径一段一段排查。以跨主机 Pod 通信为例典型路径是Pod 内 veth → 节点 cni0 网桥 → 路由表 → VXLAN 隧道或物理网卡→ 目标节点 → 目标 Pod。每一段都可以用一条命令验证。先看 Pod 内的 IP 和路由kubectl exec -it pod -- ip addr再看节点上的网桥和路由ip addr show cni0、ip route再看隧道是否存在ip link show flannel.1最后用 tcpdump 确认数据包是否真的到达了物理网卡。按照这条链路逐段排查问题总能定位在某一段区间里而不是在某一个模糊的现象里。7.2 连接一多就随机不通conntrack 满的坑这个坑我踩过而且印象非常深刻。某次集群运行一段时间后开始出现零星的连接超时ping 大部分时间是通的但 curl 经常随机失败。一开始以为是负载均衡或者 DNS 的问题排查一圈都没找到原因。最后在宿主机上执行dmesg | grep conntrack看到了“nf_conntrack: table full, dropping packet”的报错。原因很清晰conntrack 连接跟踪表的容量是有限的当表的条目写满后内核只能丢弃新连接的数据包。容器环境里连接生命周期短、量大非常容易把这张表打爆。查看当前状态可以这样sysctl net.netfilter.nf_conntrack_max sysctl net.netfilter.nf_conntrack_count如果count已经接近甚至等于max说明表快满了。解决办法是调大上限、优化连接复用参数或者缩短超时时间释放空闲记录。但最关键的还是监控把 conntrack 的使用率加入日常告警比事后排障有效十倍。这也是我后来一直坚持的底线。7.3 MTU 引发的“能 ping 通但传大文件失败”另一个高发问题跟 MTU 有关。现象非常有迷惑性小包数据正常应用能连上ping 也能通但一旦传输大文件或者下载大对象就极速超时或断开。原因是 VXLAN 这类 overlay 网络引入了额外封装头内层报文的最大长度必须相应缩减。如果容器内部的 MTU 仍然是 1500物理链路的 MTU 也是 1500那加上 VXLAN 的 50 字节后大包在物理网络上就直接被丢弃了。小包能过是因为它们本来就没超限大包会触发分片而很多场景下分片包被直接丢弃于是表现为大流量传输失败。检查方法很简单在容器里 ping 宿主机先发 1472 字节的数据再发 1473 字节的数据如果前者通后者不通基本就是 MTU 超限的问题。把 Pod 和隧道接口的 MTU 统一调成 1450 或更小这个问题通常立刻消失。7.4 地址冲突与重启失效的排查还有一个容易被误判的问题是容器重启后网络反复异常。表象是某个 Pod 重启了很多次每次都能成功启动但集群里总有人反馈访问该服务有时失败。如果一查发现这个 Pod 的 IP 在 ETCD 状态里显示归属正常但实际网络上这个地址被两个 Pod 同时使用那问题大概率出在 IPAM 层节点本地状态与中心存储之间不一致旧 Pod 的地址没有被及时回收新 Pod 又把同一地址分配了出去。遇到这种情况先看 CNI 插件的日志确认 ADD 和 DEL 操作是否成对出现。如果发现异常中断导致 DEL 没执行优先清理节点上的残留网络设备再让 kubelet 重新创建 Pod。平时养成好习惯查看 ETCD 中节点的子网分配记录比对各节点上报的子网是否重叠。地址冲突在容器环境里比传统环境更容易被隐藏不要太依赖“一定不会分配重了”的假设。我个人在这些年的容器网络维护里最深的一个体会就是遇到网络问题不要急着动容器先把数据包路径在脑子里画一遍再按图索骥逐段验证。容器网络看起来复杂但它并没有发明全新的规则MAC 自学习、最长前缀匹配、NAT 连接跟踪、哈希与轮询这些算法每一层都是成熟的、可验证的。真正要练的是把现象映射到算法层的能力。下次再遇到诡异的网络故障不妨先问自己一句这一步用的是哪个算法这个算法的正常行为应该是什么答案往往就在这个问题里。