Moby 内置网络诊断服务器详解:调试 Overlay 与 Swarm 网络问题的完整实战指南
【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby
Moby(Docker 上游项目)在 libnetwork 中内置了一个网络诊断服务器(diagnostic server),用于检查 overlay 网络与 Swarm 服务发现数据在“网络数据库”中的实际状态。本篇以daemon/libnetwork/cmd/diagnostic/README.md为核心,结合 诊断服务器实现、客户端实现 和 热加载入口,完整讲解如何启用该工具、使用其 RESTful API 与diagnosticClient命令行工具排查集群网络故障,以及如何在排查结束后安全关闭它。
一、工具定位与风险边界:它看的是什么数据
该诊断工具自 Docker CE 17.12 引入,专门帮助定位运行在 Linux 主机上的 overlay 网络与 Swarm 服务问题。启用后,诊断服务器会在指定端口监听,对外提供诊断接口。文档明确要求:该工具只应在调试特定问题时临时启动,不应长期运行。
WARNING(原文档警示):该工具会改变 libnetwork API 的内部状态,使用必须谨慎并仔细阅读文档。误用会损坏甚至永久破坏网络配置。
理解它的前提是理解 overlay 驱动的数据模型:网络信息存储在“网络数据库”(networkdb)中,当前包含两类关键信息:
endpoint_table:服务发现(service discovery)信息,即各服务的 endpoint 记录;overlay_peer_table:overlay 转发信息,即各节点间的 peer 记录。
从源码结构看,这两张表的条目都带有owner(属主节点)语义:每条记录归属于插入它的节点,并在该节点退出集群前保持持久。这一点直接决定了工具的使用姿势——例如对已加载 daemon 使用-a标志会触发 join/leave 网络的操作,副作用是“离开网络”,从而切断该 daemon 的数据路径。诊断客户端 main.go 中对孤儿条目(orphan entry)的检测也正是基于 owner 与当前网络 peer 集合的比对。
工具提供两种形态:
- 纯客户端:
dockereng/network-diagnostic:onlyclient,用于向本地 daemon 的诊断端口发请求; - Docker-in-Docker 版本:
dockereng/network-diagnostic:17.12-dind,让诊断容器自身加入 Swarm,可诊断运行旧版引擎(早于 17.12)的集群。
二、启用诊断服务器:daemon.json 配置 + 免重启热加载
2.1 操作步骤
该工具目前仅支持运行在 Linux 上的 Docker 主机。启用步骤如下:
在
/etc/docker/daemon.json中把network-diagnostic-port设置为一个空闲端口:"network-diagnostic-port": <port>获取
dockerd进程的 PID(ps aux输出中的第二列,通常是 2~6 位数字):$ ps aux |grep dockerd | grep -v grep向该 PID 发送
HUP信号,在不重启 Docker 的前提下热加载配置:kill -HUP <pid-of-dockerd>如果系统使用 systemd,执行
systemctl reload docker即可达到同样效果。
成功后,Docker 主机日志中会出现类似消息:
Starting the diagnostic server listening on <port> for commands2.2 源码级验证:配置项如何生效
该配置项是 dockerd 的一个(被标记为隐藏的)命令行/JSON 配置项,定义于 daemon/command/config.go:
flags.IntVar(&conf.NetworkDiagnosticPort, "network-diagnostic-port", 0, "TCP port number of the network diagnostic server") _ = flags.MarkHidden("network-diagnostic-port")HUP/systemctl reload触发的热加载逻辑位于 daemon/reload.go 的reloadNetworkDiagnosticPort:
- 若
network-diagnostic-port未配置或值为 0,则调用netController.StopDiagnostic()确保诊断关闭; - 若配置了有效端口,则调用
netController.StartDiagnostic(conf.NetworkDiagnosticPort)启动诊断服务器。
服务器本体在 daemon/libnetwork/diagnostic/server.go:Server结构持有enable状态与http.Server,Enable(ip, port)启动监听并打印日志“Starting network diagnostic server listening on … for commands”(即上文日志消息的来源),Shutdown()负责优雅停止并打印 “Network diagnostic server shutdown complete”。从Enable实现看,服务端设置了 5 分钟的ReadHeaderTimeout以缓解 Slowloris 类攻击,且源码注释标明该端口在 reload 时存在不可重配置的已知限制。
三、关闭诊断工具
对参与 Swarm 的每个节点重复执行以下操作:
从
/etc/docker/daemon.json中删除network-diagnostic-port键;再次获取
dockerd的 PID:$ ps aux |grep dockerd | grep -v grep发送
HUP信号热加载:kill -HUP <pid-of-dockerd>
主机日志中会出现:
Disabling the diagnostic server这对应源码中配置未设置时走StopDiagnostic()分支的 reload 逻辑,实现的是调用http.Server.Shutdown()的优雅关闭(见 server.go 的 Shutdown 方法)。
四、访问诊断工具的 RESTful API
诊断工具暴露自己的 RESTful API:直接向监听端口发送 HTTP 请求即可。以下示例假设工具监听在 2000 端口(这也是客户端默认端口)。
4.1 获取帮助
$ curl localhost:2000/help OK /updateentry /getentry /gettable /leavenetwork /createentry /help /clusterpeers /ready /joinnetwork /deleteentry /networkpeers / /join/help会列出当前注册的全部端点(实现见 server.go 的 help handler,遍历handlersmap 输出路径)。/ready返回OK,供客户端做就绪探测——diagnosticClient启动时的第一件事就是请求http://<ip>:<port>/ready并校验响应包含OK。
4.2 加入或退出网络数据库集群
$ curl localhost:2000/join?members=ip1,ip2,...$ curl localhost:2000/leave?members=ip1,ip2,...ip1、ip2… 是 Swarm 节点 IP(通常一个即可)。
4.3 加入或离开某个网络
$ curl localhost:2000/joinnetwork?nid=<network id>$ curl localhost:2000/leavenetwork?nid=<network id>network id需在 manager 上通过docker network ls --no-trunc获取,且必须是完整长度的标识符。客户端源码中也对这一点做了防御:当查询某网络但 peer 数为 0 时会提示“check the network ID, and verify that is the non truncated version”(见 main.go)。
4.4 列出集群 peer 与网络 peer
$ curl localhost:2000/clusterpeers列出集群级 peer;列出连接到指定网络的节点:
$ curl localhost:2000/networkpeers?nid=<network id>4.5 转储数据库表
两张表的含义:
overlay_peer_table:包含所有 overlay 转发信息;endpoint_table:包含所有服务发现信息。
$ curl localhost:2000/gettable?nid=<network id>&tname=<table name>4.6 操作指定表中的条目
$ curl localhost:2000/<method>?nid=<network id>&tname=<table name>&key=<key>[&value=<value>]其中<method>为createentry、getentry、updateentry、deleteentry。
注意(原文档强调):表操作具有**节点所有权(node ownership)**语义——条目会保持持久,直到插入它的节点仍在集群中。这正是排查“孤儿条目”的理论依据,也是删除操作不可逆的原因。
4.7 输出格式控制选项
从 server.go 的ParseHTTPFormOptions与 types.go 的HTTPReply可以看到,所有端点都支持 URL 表单参数控制输出:
- 追加
&json返回 JSON 格式(application/json); - 追加
&json=pretty返回缩进美化的 JSON; - 响应统一为
HTTPResult结构:message字段取值OK/FAIL等,details承载具体内容(如TableObj的size与entries,每个条目含key、value(base64 编码值)、owner,见 types.go)。
diagnosticClient内部请求 peers 与 table 时正是带上&json参数并反序列化TablePeersResult/TableEndpointsResult(见 main.go 的 fetchNodePeers / fetchTable)。
五、使用 diagnosticClient 命令行工具
CLI 以 preview 形式提供、尚不稳定,命令和选项可能随时变化。可执行文件名为diagnosticClient,通过独立容器提供:
docker run --net host dockereng/network-diagnostic:onlyclient -v -net <full network id> -t sdREADME 给出的标志如下:
| 标志 | 说明 |
|---|---|
| -t | 表名,sd或overlay之一。 |
| -ip | 要查询的 IP 地址,默认 127.0.0.1。 |
| -net | 目标网络 ID。 |
| -port | 目标端口,默认 2000。 |
| -a | join/leave 网络。 |
| -v | 启用 verbose 输出。 |
补充(来自当前源码):现在的 main.go 还实现了 README 未收录的-r标志(perform remediation deleting orphan entries),可在发现孤儿条目后交互式确认后通过deleteentry接口将其删除;删除前会明确提示“this operation is irreversible”并要求输入Yes才执行。
5.1 关于-a标志的关键注意事项(原文档 NOTE)
- 默认情况下工具不会尝试 join 网络。这符合“不改变诊断客户端运行时节点状态”的设计意图,因此对运行中的 daemon 执行
diagnosticClient是安全的——它只会转储当前状态; - 相反,在容器化版本中使用
diagnosticClient时必须传-a,否则会取回空结果; - 而对已加载的 daemon 使用
-a会产生副作用:leave network 会切断该 daemon 的数据路径。
源码中对此有硬保护:若未设置DIND_CLIENT环境变量却带了-a,客户端会直接 Fatal 退出并提示移除该标志(见 main.go);Dockerfile.dind 正是通过ENV DIND_CLIENT=true解锁该标志,并把 daemon.json(内容为{"debug": true, "network-diagnostic-port": 2000})拷贝为容器内的/etc/docker/daemon.json。
5.2 典型用法示例(原文档 Examples)
记得使用完整网络 ID,可用docker network ls --no-trunc快速获取。
服务发现与负载均衡:
$ diagnosticClient -t sd -v -net n8a8ie6tb3wr2e260vxj8ncy4 -aOverlay 网络:
$ diagnosticClient -port 2001 -t overlay -v -net n8a8ie6tb3wr2e260vxj8ncy4 -a-t sd对应转储endpoint_table,-t overlay对应转储overlay_peer_table(见 main.go 的 switch 分支)。工具会解码每条记录的 base64 值(sd表解析为libnetwork.EndpointRecord,overlay表解析为overlay.PeerRecord),并把owner不在当前网络 peer 集合中的条目标记为孤儿发出 Warn。
六、容器化版本的完整调试流程
容器化 CLI 基于 17.12 引擎,需要以 privileged 模式运行。
NOTE(原文档强调):表操作具有 ownership 语义,因此在诊断容器处于 Swarm 期间,任何
create entry操作都会保持持久。
流程如下:
确保运行诊断客户端的节点不属于 Swarm,若属于则先执行
docker swarm leave -f;启动容器:
$ docker container run --name net-diagnostic -d --privileged --network host dockereng/network-diagnostic:17.12-dind通过
docker exec -it <container-ID> sh进入容器,启动其中内置的诊断服务器:$ kill -HUP 1(向 PID 1 的 dind 内 dockerd 发 HUP,触发其加载容器内的
daemon.json,从而在 2000 端口启动诊断服务器;该容器镜像定义见 Dockerfile.dind,纯客户端镜像定义见 Dockerfile.client,基于 alpine + curl,ENTRYPOINT即diagnosticClient。)将诊断容器加入 Swarm,然后在容器内运行诊断 CLI:
$ ./diagnosticClient <flags>...调试结束后,离开 Swarm 并停止容器。
七、使用建议小结
结合原文档警示与源码实现,可归纳出几条实践原则:
- 临时性:诊断端口只应在排查期间打开,排查完立即从
daemon.json移除配置并 reload; - 每节点操作:Swarm 场景下启用/关闭需要对每个参与节点执行;
- 只读优先:默认不加
-a的客户端是纯只读的,可安全地对运行中 daemon 执行;一旦使用写入类端点(createentry/updateentry/deleteentry)或-a、-r,就要意识到 ownership 持久性与删除不可逆这两点; - 完整网络 ID:所有
nid参数都必须使用非截断的完整 ID,否则查询会得到空 peer 列表; - 输出自动化:脚本化采集时优先使用
&json参数,响应结构稳定(message+details),便于程序解析。
相关源码入口:诊断服务器 daemon/libnetwork/diagnostic/server.go、响应类型 daemon/libnetwork/diagnostic/types.go、客户端 daemon/libnetwork/cmd/diagnostic/main.go、热加载逻辑 daemon/reload.go、配置项定义 daemon/command/config.go。
【免费下载链接】mobyThe Moby Project - a collaborative project for the container ecosystem to assemble container-based systems项目地址: https://gitcode.com/GitHub_Trending/mo/moby
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考