Moby 源码实战:用 Swarm Service Driller(ssd)排查 Docker Swarm 网络的控制面与内核数据面不一致
ssd(Swarm Service Driller)是 Moby 仓库内置的 Swarm 网络故障排查工具,用于核对 Docker 守护进程内存中的网络控制面状态与内核中实际编程的数据面(IPVS 负载均衡、iptables 转发规则)是否一致,以及各节点通过 gossip 协议同步出的控制面状态是否收敛。本文基于 README 与 ssd.py 源码,完整讲解两类检查的原理、操作命令和输出判读方法,帮助你在出现"网络 inspect 正常但流量不通"这类疑难问题时快速定位责任层。
ssd 的定位:控制面与数据面之间的"一致性探针"
在 Docker Swarm 网络中,服务发现与负载均衡由两部分协作完成:
- 控制面:由 Docker 守护进程(daemon/libnetwork 下的集群 agent)维护,记录每个服务的 VIP、本地负载均衡索引(LocalLBIndex)与后端任务列表;
- 数据面:由内核实际编程,overlay 网络在每个容器沙箱内用 IPVS(以 fwmark 标识服务)做本地负载均衡,ingress 网络则用 iptables 的 DNAT 规则把发布端口转发到 ingress 沙箱。
ssd 的核心职责就是把这两份"账本"逐条比对,确认没有漂移。它当前只覆盖负载均衡(IPVS 实现)的一致性检查,这一点在 README 中明确说明。
运行方式与依赖
ssd 以容器形式运行,通过挂载 Docker socket 和 netns 目录获得对 daemon API 与内核网络命名空间的访问权。README 给出的标准命令为:
docker run -v /var/run/docker.sock:/var/run/docker.sock \
-v /var/run/docker/netns:/var/run/docker/netns \
--privileged --net=host sanimej/ssd ov2
从源码看,两个挂载点各有用途:
/var/run/docker.sock:ssd 通过 docker-py 客户端(docker.APIClient(base_url='unix://var/run/docker.sock'),见 ssd.py L141)拉取network inspect --verbose的控制面数据;/var/run/docker/netns:ssd 用nsenter --net=<namespace>进入每个容器的网络沙箱执行ipvsadm -ln读取内核中真实的 IPVS 表(ssd.py L94),因此必须--privileged。
命令的第一个参数是网络名;第二个可选参数为子命令 gossip-consistency,默认值为 default(即一致性检查,ssd.py L136-L145)。如果第一个参数是 ingress,则只检查 ingress 网络。
仓库提供了现成的 Dockerfile,基于 Alpine,预装了检查所需的工具链:python2、iproute2、ipvsadm、bash、nsenter(util-linux)以及网络诊断工具 strace、iperf、ethtool 等,并将 ssd.py 设为入口 python /ssd.py。你可以用它自行构建镜像,替代 README 中引用的外部 sanimej/ssd 镜像,例如:
docker build -t local/ssd daemon/libnetwork/cmd/ssd
检查一:节点内的控制面 / 数据面一致性(IPVS 编程核对)
default 模式下,ssd 对指定网络执行 check_network,流程如下(见 check_network):
- 拉取控制面真值:调用
network inspect --verbose,从返回的Services字段提取每个服务的LocalLBIndex(即 IPVS 中使用的 fwmark)和任务列表的EndpointIP,构建"fwmark → 后端 IP 集合"的期望映射; - 读取内核真值:对网络上每个容器(沙箱键来自
NetworkSettings.SandboxKey),nsenter进其网络命名空间执行ipvsadm -ln,解析输出中以FWM开头行的标记值和->开头的后端条目; - 双向比对:
- IPVS 中有、daemon 中没有的 LB Index → 报
LB Index X present in IPVS but missing in docker daemon(内核残留); - daemon 中有、IPVS 中没有的 → 报
present in docker daemon but missing in IPVS(编程丢失); - 两边都有但后端集合不一致 → 报
Incorrect LB Programming for service ...,并分别打印控制面与内核 IPVS 的后端列表,方便肉眼定位多出来的或少掉的实例。
- IPVS 中有、daemon 中没有的 LB Index → 报
在 agent.go 中可以看到 ServiceInfo 结构的定义:VIP、LocalLBIndex、Tasks、Ports——这正是 ssd 比对所依赖的 network inspect --verbose 输出的数据来源。
README 中一个三节点集群(网络 ov2,3 个服务各 3 副本)的正常输出如下,每个沙箱下逐一核对所有服务后输出 OK,随后自动追加对 ingress 网络的检查:
$ docker run -v /var/run/docker.sock:/var/run/docker.sock \
-v /var/run/docker/netns:/var/run/docker/netns \
--privileged --net=host sanimej/ssd ov2
Verifying LB programming for containers on network ov2
Verifying container /s2.3.ltrdwef0iqf90rqauw3ehcs56...
service s2... OK
service s3... OK
service s1... OK
Verifying container /s3.3.nyhwvdvnocb4wftyhb8dr4fj8...
service s2... OK
service s3... OK
service s1... OK
Verifying container /s1.3.wwx5tuxhnvoz5vrb8ohphby0r...
service s2... OK
service s3... OK
service s1... OK
Verifying LB programming for containers on network ingress
Verifying container Ingress...
service web... OK
ingress 网络:iptables 发布端口 DNAT 规则核对
对 ingress 网络(或 default 模式自动追加的 ingress 检查),ssd 额外调用 check_iptables(ssd.py L25-L45):
- 先
nsenter进入ingress_sbox网络命名空间,读取eth1的 IP(ingress 沙箱在docker_gwbridge网络上的地址,发布端口最终会被 DNAT 到该 IP); - 对服务
Ports中的每个Target: x, Publish: y条目,用iptables -t nat -C DOCKER-INGRESS -p tcp --dport <publish> -j DNAT --to <ingressIP>:<publish>探测 nat 表DOCKER-INGRESS链中是否存在对应规则; - 缺失时输出:
Service <name>: host iptables DNAT rule for port <x> -> ingress sandbox <ip>:<x> missing。
这与数据面实现相互印证:libnetwork 的 bridge 驱动内部在主机命名空间的 nat 表 PREROUTING 中挂接 DOCKER-INGRESS 链(见 iptabler/network.go L408),服务流量经 DNAT 进入 ingress 沙箱后再由 IPVS 分发到后端。
检查二:跨节点控制面一致性(gossip-consistency)
Swarm 网络状态通过 gossip 协议在集群内同步。gossip-consistency 子命令验证所有节点对该网络的控制面状态是否一致(README):
docker run -v /var/run/docker.sock:/var/run/docker.sock \
-v /var/run/docker/netns:/var/run/docker/netns \
--privileged sanimej/ssd ov2 gossip-consistency
三节点集群、ov2 网络上的正常输出——三行 hash 完全相同即表示已收敛:
Node id: sjfp0ca8f43rvnab6v7f21gq0 gossip hash c57d89094dbb574a37930393278dc282
Node id: bg228r3q9095grj4wxkqs80oe gossip hash c57d89094dbb574a37930393278dc282
Node id: 6jylcraipcv2pxdricqe77j5q gossip hash c57d89094dbb574a37930393278dc282
源码级实现:全局服务 + 每节点 hash
该子命令的机制比表面更巧妙(ssd.py L147-L165):
- ssd 在本地节点通过 daemon API 创建一个名为
gossip-hash的 global 服务,其任务镜像为docker/ssd,参数为<网络名> gossip-hash,并把/var/run/docker.sock挂载进每个任务; - global 服务保证集群中每个节点各运行一个任务,每个任务执行
gossip-hash分支:inspect_network --verbose后,把该节点视角下所有服务的 VIP 及任务明细字段排序,逐条喂给 MD5,打印节点 ID 与摘要,然后signal.pause()挂起等待被收集; - 创建服务后短暂等待,用
service logs gossip-hash收集全部任务的输出(即每节点一行 hash),最后调用remove_service清理。
因此输出的每行 Node id: <node-id> gossip hash <md5> 对应集群中的一个节点。hash 相同说明各节点经 gossip 同步出的网络控制面状态一致;如果出现不一致,按 README 的建议,在各节点上执行 docker network inspect --verbose 逐个对比输出,即可定位具体差异字段(通常是某个服务的任务列表或端点 IP 未同步到位)。值得注意的是:hash 是对"排序后的状态字段"计算的,与任务的枚举顺序无关,可以消除字段顺序造成的假阳性。
故障排查建议路径
结合两类检查,可以形成一条从现象到结论的定位路径:
- 服务 VIP 可达性异常、个别后端失联 → 先跑 default 模式的一致性检查。若报
missing in IPVS/Incorrect LB Programming,问题在本机内核数据面编程,可进一步用ipvsadm -ln(经 nsenter 到对应沙箱)与docker network inspect --verbose手工复核; - ingress 发布端口不通、容器内直连正常 → 关注 iptables DNAT 规则缺失输出,检查
iptables -t nat -L DOCKER-INGRESS -n中发布端口规则; - 部分节点行为与主节点不一致(如新加入节点上服务不可见) → 跑
gossip-consistency。若 hash 不一致,逐节点docker network inspect --verbosediff 出未收敛的节点与字段。
适用前提与注意事项
- 工具依赖 Linux 内核特性(IPVS、iptables、netns + nsenter),在 Linux 节点上运行才有效;
- ssd.py 使用 Python 2 语法(
print语句),Dockerfile 中显式安装python2并建立python软链,自行移植到 Python 3 环境需做语法迁移; - 一致性检查当前仅覆盖 IPVS 负载均衡这一项数据面内容(README 明示 "currently the tool checks only for the consistency of the Load balancer"),其他数据面要素(如路由、VXLAN 配置)不在核对范围内;
gossip-consistency会在集群中短暂创建并删除一个 global 服务,请确认当前节点具有 swarm 管理权限,且集群允许创建临时服务。
延伸阅读(仓库内相关路径)
- README:ssd 工具官方说明与示例输出;
- ssd.py:检查逻辑全部实现,约 194 行,可读性高,适合作为理解 Swarm 网络数据面编程的"活文档";
- agent.go:
ServiceInfo(VIP / LocalLBIndex / Tasks / Ports)结构定义,即 ssd 比对的控制面数据来源; - service_linux.go:daemon 侧通过
moby/ipvs库在沙箱内创建/删除 IPVS 服务与后端的实现,与 ssd 读取的内核状态一一对应; - networkdb:gossip 同步的集群数据库层,
gossip-consistency校验的对象即其收敛结果。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00