容器编排这件事,单机跑得好好的,一跨到多主机就原形毕露——这是我在实际项目里反复验证过的结论。很多人第一次接触 Swarm 模式,是被它"开箱即用"的集群能力吸引的:几条命令就能把几台机器组成一个集群,服务一发布,容器自动调度、自动拉起。但真正让人栽跟头的,往往不是调度本身,而是跨主机容器之间到底怎么通信这件事。默认的 overlay 网络看起来"配好了就能通",可一旦出现丢包、DNS 解析失败、跨节点延迟异常,或者你想搞清楚数据到底走了哪条路径,光靠docker network ls那几行输出是完全不够的。
这篇内容我想把 Docker Swarm 的 overlay 网络模型从头到尾拆一遍。它是什么、解决什么问题、底层靠什么机制把不同主机上的容器塞进同一个"虚拟二层"里、VXLAN 封装到底怎么发生、控制平面和数据平面分别由谁负责、以及在实际运维中哪些参数和现象值得盯。适合已经用过 Docker、想搞清楚 Swarm 网络底层原理的开发者,也适合正在评估要不要上 Swarm、需要判断它网络模型边界的中高级运维。我会尽量用生活化的类比把抽象机制讲透,同时给出可以直接上手验证的命令和排查思路,让你读完能自己动手把整条链路跑一遍、看一遍。
1. 先搞清楚 overlay 网络到底在解决什么矛盾
要理解 overlay,得先回到它诞生前的困境。假设你手上有三台物理机或虚拟机,每台跑若干容器。容器 A 在主机 1,容器 B 在主机 2,它们要互相访问。最朴素的做法是端口映射:把容器 B 的端口发布到主机 2 的宿主端口上,容器 A 通过"主机 2 的 IP + 宿主端口"访问。这个方案在小规模下能用,但问题很快暴露:容器 A 的配置里硬编码了主机 2 的地址,一旦容器 B 被调度到主机 3,配置就得改;服务一多,端口映射表变成一团乱麻;更致命的是,容器之间失去了"直接对话"的能力,所有流量都得绕经宿主端口这一层。
overlay 网络要解决的核心矛盾就是这个:让分布在不同主机上的容器,像处在同一个局域网里一样直接通信,而不关心对方物理上落在哪台机器。这层"虚拟局域网"叠加在物理网络之上,所以叫 overlay(覆盖网络)。它给每个容器分配一个集群内唯一的虚拟 IP,容器之间用这个虚拟 IP 直接互访,底层的物理拓扑被完全屏蔽。
1.1 和 bridge 网络的本质区别
单机上的 bridge 网络,本质是宿主内核里的一块虚拟交换机,容器 veth 一端插在这块桥上,同一桥上的容器二层直达。它的作用域被死死限制在一台主机内——因为 bridge 是宿主本地的,跨主机没有共享的二层域。overlay 则不同,它在每台主机上都有一个"本地入口"(一个特殊的网络命名空间和虚拟网卡),再通过隧道把各主机的本地入口连起来,形成一个逻辑上连通的网络。你可以把 bridge 想成一栋楼里的内部走廊,overlay 则是把好几栋楼的地下通道打通,让不同楼里的人也能直接串门。
这个区别带来一个关键推论:overlay 网络里的容器通信,必然要经过封装和隧道,因为物理网络不认识这些虚拟 IP。封装带来的开销、MTU 的变化、以及隧道端点之间的连通性要求,都是后续所有问题的根源。理解这一点,后面排查故障时你才不会把"跨主机不通"简单归咎于"网络配错了"。
1.2 Swarm 模式下 overlay 的特殊地位
在 Swarm 模式里,overlay 网络不是可选项,而是服务发现和负载均衡的基石。当你创建一个 overlay 网络并让服务接入,Swarm 会给每个服务分配一个虚拟 IP(VIP),并在集群内置的 DNS 里注册服务名。容器访问服务名时,DNS 返回 VIP,流量再被分发到后端某个具体任务容器。这套机制完全建立在 overlay 之上——没有 overlay,跨主机的服务发现就无从谈起。所以搞懂 overlay,等于搞懂了 Swarm 网络的大半壁江山。
2. VXLAN 封装:数据包在跨主机时经历了什么
overlay 的底层封装协议,Docker 用的是VXLAN(Virtual Extensible LAN)。这是理解整个模型的技术核心,绕不开。我用一个具体场景把数据包的旅程走一遍:容器 A(虚拟 IP 10.0.0.2)在主机 1,要访问容器 B(虚拟 IP 10.0.0.3)在主机 2。
2.1 从容器 A 发出到隧道入口
容器 A 发出一个目的地址为 10.0.0.3 的包。这个包先走容器 A 的默认路由,到达它所在 overlay 网络的网关——这个网关位于主机 1 上一个专门为 overlay 创建的网络命名空间里。这个命名空间里有一块虚拟网卡(通常叫br0之类的网桥,以及一个 VXLAN 接口)。包到达这里后,主机 1 的网络栈查表发现 10.0.0.3 属于本 overlay 网段,需要走 VXLAN 隧道发出。
关键一步来了:主机 1 需要知道 10.0.0.3 对应哪台物理主机。这个映射关系由 Swarm 的控制平面维护——每个节点上跑着一个分布式的网络状态存储,记录了"哪个虚拟 IP 落在哪个节点"。主机 1 查到 10.0.0.3 在主机 2 上,于是把原始包整个塞进一个新的 UDP 包里:外层源地址是主机 1 的物理 IP,外层目的地址是主机 2 的物理 IP,外层目的端口是 VXLAN 约定的 4789。原始包(含它自己的以太网头、IP 头)作为载荷被完整封装进去。这就是 VXLAN 的"MAC in UDP"封装。
2.2 隧道传输与解封装
这个 UDP 包走物理网络到达主机 2。主机 2 的 VXLAN 接口收到 4789 端口的包,剥掉外层 UDP/IP 头,取出里面的原始包,发现目的虚拟 IP 是 10.0.0.3,于是交给本地 overlay 网桥,最终送达容器 B。整个过程对容器 A 和 B 完全透明——它们只看到自己和对方在同一个 10.0.0.0/24 网段里,根本不知道中间发生了封装。
我用一个表格把封装前后的字段对照列出来,方便你建立直观印象:
| 层次 | 封装前(容器视角) | 封装后(物理网络视角) |
|---|---|---|
| 源地址 | 10.0.0.2(容器 A 虚拟 IP) | 主机 1 物理 IP |
| 目的地址 | 10.0.0.3(容器 B 虚拟 IP) | 主机 2 物理 IP |
| 协议 | 任意(TCP/UDP/ICMP) | UDP,目的端口 4789 |
| 载荷 | 应用数据 | 完整的原始以太网帧 |
2.3 为什么是 VXLAN 而不是别的
这里有个值得展开的"为什么"。早期也有用 GRE、IP-in-IP 之类隧道做 overlay 的方案,Docker 最终选 VXLAN 有几个现实理由。第一,VXLAN 用 24 位 VNI(VXLAN Network Identifier)标识不同网络,理论上支持约 1600 万个隔离网络,远超 VLAN 的 4094 个上限,多租户场景下不会撞号。第二,VXLAN 封装的是完整二层帧,天然支持二层语义,容器看到的是一块"真正的"以太网,ARP、广播都能正常工作,兼容性好。第三,4789 是 IANA 分配的正式端口,标准化程度高,跨厂商设备互通时少踩坑。
代价也很明确:每个包多了 50 字节的封装开销(外层以太网头 14 + 外层 IP 头 20 + UDP 头 8 + VXLAN 头 8)。这直接引出下一个必须处理的问题——MTU。
2.4 MTU 这个坑,几乎人人都会踩一次
物理网络的标准 MTU 是 1500。封装后多出 50 字节,如果容器内还按 1500 发大包,封装后就是 1550,超过物理链路 MTU,要么被分片(性能差、部分场景直接丢),要么被丢弃。Docker 的默认处理是把 overlay 网络的 MTU 设为 1450(1500 - 50),容器内的网卡 MTU 也是 1450,这样封装后正好 1500,不超限。
但这里有个隐藏陷阱:如果你的物理网络本身 MTU 就不是 1500(比如某些云环境、或者你调过底层网卡),那 1450 就不一定对了。我见过实际案例,某环境底层 MTU 是 1400,overlay 还按 1450 配,结果大包全部静默丢弃,表现为"小请求正常、大文件传输卡死"这种极其迷惑的现象。排查这类问题的思路是:先在容器里ping -M do -s 1472逐步减小包大小测出实际可通 MTU,再反推 overlay 该配多少。这个技巧后面排查章节还会细说。
3. 控制平面:谁在维护"虚拟 IP 到主机"的映射
数据平面靠 VXLAN 搬包,但搬包之前得知道往哪搬。这个"往哪搬"的信息,就是控制平面的职责。Swarm 的 overlay 控制平面由一套分布式的键值存储承担,每个节点本地都有一份网络状态数据,记录着 overlay 网络里所有端点(endpoint)和它们所在节点的对应关系。
3.1 分布式网络数据库的角色
Swarm 集群内部维护着一个网络数据库,它随集群一起初始化,不需要你额外部署。当你创建一个 overlay 网络、启动一个接入该网络的服务任务时,Swarm 会把"这个任务的虚拟 IP 落在哪个节点、对应哪个 VXLAN 端点"这类信息写进这个分布式存储。每个节点都能读到全量数据,所以任意节点上的容器要访问另一个虚拟 IP 时,本地就能查到目标在哪个物理主机,无需中心化查询。这种设计的好处是没有单点、查询延迟低;代价是状态同步有最终一致性窗口——极短时间内新起的容器可能还没被其他节点感知到,表现为"刚启动瞬间连不上,几秒后恢复"。这个现象在压测或快速扩缩容时偶尔能观察到,属于正常范围,不必惊慌。
3.2 服务发现与 VIP 的配合
控制平面还负责服务发现这一层。每个 Swarm 服务在创建时会分配一个 VIP,服务名注册进集群 DNS。容器解析服务名拿到 VIP,然后发往 VIP 的流量被本地的负载均衡规则(基于内核的 IPVS 或 iptables)分发到某个具体任务容器。这里要注意:VIP 本身也是个虚拟地址,它的可达性同样依赖 overlay。所以一条完整的访问链路是"DNS 解析服务名 → 得到 VIP → 流量经 overlay 到达 VIP → 本地负载均衡选中后端任务 → 再经 overlay 到达任务容器"。任何一环的 overlay 状态不一致,都会表现为服务访问异常。
3.3 加密选项对控制与数据平面的影响
Swarm overlay 支持在创建网络时开启加密(--opt encrypted)。开启后,节点之间的 VXLAN 流量会额外套一层加密,防止物理网络被嗅探时泄露容器间通信内容。这个选项的代价是每个包都要加解密,CPU 开销上升,吞吐下降,延迟增加。我的经验是:同机房内网、信任域内的服务间通信,一般不开;跨不可信网段、或者合规要求高的场景才开。开启后如果发现跨节点吞吐明显掉档,先怀疑加密开销,而不是网络本身有问题。另外加密是在数据平面做的,控制平面的状态同步不受影响,这点要分清。
4. 动手验证:把整条链路亲手跑一遍看一遍
光看原理容易飘,我习惯把关键机制亲手验证一遍,眼见为实。下面这套流程可以在任意一个多节点 Swarm 环境里复现,帮你把前面讲的封装、路由、DNS 全部对上号。
4.1 搭一个最小可用的多节点集群
先准备两台及以上主机(虚拟机即可),都装好 Docker,确保彼此物理网络互通、4789/UDP 端口没被防火墙拦。在第一台执行docker swarm init拿到加入令牌,其余机器docker swarm join加入。用docker node ls确认所有节点状态为 Ready。这一步的常见坑是防火墙:很多环境默认只放行 TCP,VXLAN 走的是 UDP 4789,不放行的话集群能建起来、服务能调度,但跨节点容器就是不通,而且报错很隐晦。先把 UDP 4789 放行,能省掉大量排查时间。
4.2 创建 overlay 网络并观察端点
执行docker network create -d overlay --attachable testnet建一个可手动接入的 overlay 网络(--attachable让非 Swarm 服务的独立容器也能接入,方便调试)。然后起两个服务,强制调度到不同节点:
docker service create --name svc-a --network testnet --replicas 1 \ --constraint 'node.hostname==node1' alpine sleep 3600 docker service create --name svc-b --network testnet --replicas 1 \ --constraint 'node.hostname==node2' alpine sleep 3600起来之后,在 node1 上docker exec进 svc-a 的容器,ip addr看它的虚拟 IP 和 MTU(应该是 1450),ip route看默认路由指向 overlay 网关。再nslookup svc-b或getent hosts svc-b,验证集群 DNS 能解析出 svc-b 的 VIP。这一套看下来,控制平面的服务发现就验证完了。
4.3 抓包看 VXLAN 封装的真面目
最有说服力的一步是抓包。在 node1 的物理网卡上执行tcpdump -i <物理网卡> -n udp port 4789 -w vxlan.pcap,然后在 svc-a 容器里ping svc-b或发起一次 TCP 连接,停止抓包后用分析工具打开。你会清楚看到:外层是 node1 到 node2 的 UDP 包,端口 4789,剥开外层后里面是完整的原始以太网帧,源和目的都是 10.0.x.x 的虚拟 IP。这一眼下去,前面讲的"MAC in UDP"就不再是抽象概念了。我建议每个想真正掌握 overlay 的人都亲手做一次这个抓包,比读十篇文章都管用。
4.4 验证 MTU 边界
在容器里执行ping -M do -s 1422 svc-b(1422 + 8 ICMP 头 + 20 IP 头 = 1450,正好等于 overlay MTU),应该能通;把-s加到 1423 就会失败,因为超过 1450 且禁止分片。这个实验能让你对 MTU 边界建立精确的手感。如果 1422 就不通,说明底层物理 MTU 可能不是 1500,需要继续往下调,直到找到实际可通的最大值,再据此调整 overlay 的 MTU 配置。
5. 故障排查:跨主机不通时按这个顺序查
overlay 出问题时,现象往往很"糊"——有时通有时不通、小包通大包不通、新容器通老容器不通。盲目重启解决不了根本问题,得有一套从底往上的排查链路。我把自己常用的顺序整理出来,基本能覆盖绝大多数场景。
5.1 第一步永远是物理层与端口连通性
先确认节点之间物理网络通、UDP 4789 放行。用nc -u -z <对端IP> 4789或直接在一台上抓包、另一台上发包来验证。这一步不过,后面全白搭。我遇到过好几次"集群正常、服务正常、就是跨节点不通",最后都是防火墙没放 UDP。另外注意有些云环境的安全组是独立于主机防火墙的,两处都要查。
5.2 第二步查控制平面状态一致性
在任意管理节点执行docker network inspect testnet,看各容器的端点信息是否齐全、虚拟 IP 是否都登记在册。如果某个任务的端点缺失或状态异常,说明控制平面同步出了问题。常见诱因是节点间时钟偏差过大、或者某个节点短暂失联后状态没完全追平。可以尝试docker service update --force重建任务,让端点重新注册。如果频繁出现状态不一致,要检查节点间的基础网络质量,丢包和抖动会直接影响状态同步的可靠性。
5.3 第三步用抓包定位封装是否发生
如果控制平面看着正常但就是不通,抓包是分水岭。在源节点物理网卡抓 4789 端口:如果能看到封装包发出,说明源端封装正常,问题在传输或目的端解封装;如果根本抓不到封装包,说明源端就没往外发,问题在本地路由或 overlay 网关配置。到了目的节点再抓一次,看封装包是否到达、是否被正确解封装。这一来一回,问题卡在哪一段就非常清楚了。抓包时记得同时抓 overlay 内部接口和物理接口,对比着看。
5.4 第四步怀疑 MTU 与分片
如果表现为"能 ping 通小包、TCP 建连成功但传大数据卡死",几乎可以锁定 MTU 问题。按前面说的方法在容器内用ping -M do -s逐步测实际可通 MTU,再对照 overlay 配置。修复方式是在创建网络时用--opt com.docker.network.driver.mtu=<值>显式指定合适的 MTU。注意这个值要在创建网络时就定好,事后改比较麻烦,通常得重建网络。所以规划阶段就要把底层 MTU 摸清楚。
5.5 一张排查对照表
把常见现象和对应根因、处理方式整理成表,方便对照:
| 现象 | 可能根因 | 处理方向 |
|---|---|---|
| 跨节点完全不通,同节点正常 | UDP 4789 被拦 | 放行防火墙与安全组 |
| 新起容器短暂不通,几秒后恢复 | 控制平面最终一致性 | 属正常,观察是否频繁 |
| 小包通、大包卡死 | MTU 不匹配 | 测实际 MTU 并调整配置 |
| 服务名解析失败 | 集群 DNS 或 VIP 异常 | 查服务端点与 DNS 状态 |
| 跨节点吞吐低、延迟高 | 加密开销或链路质量 | 评估是否关闭加密、查链路 |
| 偶发丢包、状态不同步 | 节点间网络抖动 | 检查基础网络质量 |
6. 几个容易被忽略的实践细节
原理和排查讲完,再补几个我在实际使用中踩出来、但文档里不太强调的点,这些往往决定你用得顺不顺。
6.1 overlay 网络数量与性能的权衡
每创建一个 overlay 网络,每个节点上就会多一套对应的网络命名空间、网桥和 VXLAN 接口。网络数量多了,节点上的虚拟接口和路由表项随之膨胀,管理复杂度和资源开销都上升。我的建议是按业务域划分,而不是按服务划分——比如"前端域""后端域""数据域"各一个 overlay,域内服务共享,域间按需打通。这样既保证了隔离,又不至于网络数量失控。见过有团队给每个微服务单独建 overlay,几十个网络叠在一起,排查问题时光理清哪个容器在哪个网络就够呛。
6.2 独立容器接入 overlay 的调试价值
--attachable这个选项在调试时特别好用。你可以起一个临时的独立容器接入目标 overlay 网络,在里面随意执行ping、curl、nslookup、tcpdump,而不影响正式服务。排查跨节点通信问题时,我经常用这种方式快速验证"从某个节点上的容器能不能到另一个服务",比反复重建服务高效得多。用完docker rm -f清掉即可,不留痕迹。
6.3 服务 VIP 与直接访问任务 IP 的取舍
Swarm 默认给服务分配 VIP,访问服务名走 VIP 再负载均衡到后端任务。但有时你会想直接访问某个具体任务容器(比如排查某个实例的问题)。这时可以用docker service ps找到任务所在节点,再通过docker inspect拿到任务容器的虚拟 IP 直接访问。要注意的是,直接访问任务 IP 绕过了 VIP 的负载均衡,只适合调试,不适合作为常规访问方式——因为任务随时可能被重新调度,IP 会变。
6.4 加密网络的性能实测建议
如果打算开启 overlay 加密,上线前务必做一次吞吐和延迟的对比实测。方法很简单:同一对容器,先在未加密网络上跑一次iperf3,再在加密网络上跑一次,对比结果。根据我的经验,加密带来的吞吐下降在不同硬件上差异很大,有的环境掉 20%,有的能掉一半以上,跟 CPU 加解密能力直接相关。不做实测就上线,很可能在流量高峰时才发现瓶颈。实测数据也是你决定"这个业务到底要不要加密"的依据——安全和性能的平衡得靠数据说话,不能拍脑袋。
6.5 节点规模与 overlay 的适用边界
overlay 的控制平面是分布式的,节点越多,状态同步的负担越重,最终一致性窗口也可能拉长。小到中等规模(十几到几十个节点)的集群,Swarm overlay 用起来很省心;再往上,状态同步的复杂度和故障排查难度都会明显上升。这不是说大规模不能用,而是要有心理准备:规模上去后,网络问题的排查会更依赖系统化的抓包和状态比对,而不是靠经验直觉。评估是否选用时,把这一点纳入考量,比事后补救强。
7. 把 overlay 模型装进脑子里
走到这里,overlay 的完整图景应该清晰了:控制平面用分布式存储维护"虚拟 IP 到物理主机"的映射,数据平面用 VXLAN 把容器间的原始帧封装进 UDP 隧道跨主机传输,服务发现和 VIP 负载均衡叠在这套机制之上,MTU 和加密是两个必须提前规划的参数,而排查问题的核心方法是"从物理连通性往上,逐层用抓包和状态比对定位"。这套模型不只适用于 Swarm,理解透了,再看其他基于 VXLAN 的容器网络方案,底层逻辑是相通的。
我个人在实际操作中的体会是,overlay 的坑大多不在"配不对",而在"没意识到它存在"——MTU 悄悄超限、UDP 端口悄悄被拦、状态同步悄悄延迟,这些都不会给你一个明确的报错,只会表现为"时好时坏"。所以真正省事的做法不是出了问题再查,而是在搭建阶段就把 MTU 摸清、端口放行、抓包验证一遍,把这条链路亲手跑通一次。跑通过一次之后,再遇到问题,你脑子里能立刻浮现出数据包在哪一段、被谁处理、可能卡在哪,排查效率完全不是一个量级。