Moby Swarm 发布端口的 iptables 规则解析:docker_gwbridge 上的普通端口映射机制
本文以 Moby(Docker Engine)集成测试中的 iptables 文档模板 integration/network/bridge/iptablesdoc/templates/swarm-portmap.md 及其生成的黄金参考文档为主线,深入解析「Swarm 服务发布端口」这一场景下,Docker 守护进程在 Linux 主机上生成的 filter/nat 两表 iptables 规则:读者将理解 docker_gwbridge 桥接网络的角色、ingress 端口映射如何复用与普通容器完全一致的 DNAT + ACCEPT 规则(即不存在独立的 DOCKER-INGRESS 链),并能亲手读懂 docker service create -p 8080:80 busybox top 背后主机网络栈的完整行为。
一、文档来源与生成机制:一份"黄金文件"式的技术文档
在展开规则之前,先说明这份文档在仓库中的定位。swarm-portmap.md 位于 integration/network/bridge/iptablesdoc/ 目录下,该目录是一套"用集成测试生成 iptables 文档"的机制,整体结构见 index.md。
其工作方式(由 iptablesdoc_linux_test.go 中的 TestBridgeIptablesDoc 实现)是:
- 为每个"场景"(section)在
L3Segment中创建一个独立的网络命名空间宿主,并在其中启动一个真实的 dockerd; - 按场景配置创建网络、容器或 Swarm 服务;
- 执行
iptables -vL --line-numbers -t filter、iptables -S -t filter、iptables -vL --line-numbers -t nat等命令捕获真实规则; - 将捕获结果填充到
templates/目录下对应的 text/template 文件(本主题即 swarm-portmap.md 模板,其中的{{index . "LFilter4"}}、{{index . "SFilter4"}}、{{index . "LNat4"}}、{{index . "SNat4"}}是占位符); - 将生成结果与
generated/目录下的黄金文件(即 generated/swarm-portmap.md)做 diff,任何规则差异都会使测试失败——因此文档与源码行为强绑定。
测试源码注释也明确了维护流程:当规则变化时,需检查 diff、更新对应 _templ.md 的描述,再以 TESTFLAGS='-update' 重跑以刷新参考文档。同时 index.md 中的警告必须保留在读者心中:这份文档面向开发用途,Docker 的 iptables(含 ip6tables)规则结构会随版本变化,不是稳定接口;且 bridge 驱动在重启初始化时会删除自定义链、网络恢复时再重建,filter-FORWARD 链本身不清空,因此守护进程重启后规则顺序可能不同。ip6tables 规则遵循与 iptables 相同的模式,所以文档只展示 IPv4。
二、场景设定:一个带发布端口的 Swarm 服务
swarm-portmap 场景的等价操作是:
docker service create -p 8080:80 busybox top
在测试实现中,该场景(section.swarm == true)的准备工作包括(见 iptablesdoc_linux_test.go):
- 守护进程以
--swarm-default-advertise-addr=<宿主接口>参数启动,并先检查内核是否支持 IPVS netlink 族(netlink.GenlFamilyGet("IPVS")),不支持则跳过; - 通过 API 完成
SwarmInit(d.SwarmInit(ctx, t, swarmtypes.InitRequest{})); - 用
CreateService创建服务,EndpointSpec.Ports配置为PublishedPort: 8080, TargetPort: 80, Protocol: tcp; - 轮询等待 nat 表
DOCKER链中出现 DNAT 规则,确认 ingress 端口映射就绪——注释特别说明:ingress 端口会在任务容器启动后的几毫秒内,作为 load-balancer 沙箱docker_gwbridge网关端点上的普通端口映射出现。
测试用的子网规划来自固定测试网段(docNetworks 使用 192.0.2.0/24 等 TEST-NET 地址);而 Swarm ingress 场景涉及的是守护进程自动创建的内部桥 docker_gwbridge,其子网在本例中为 172.18.0.0/16。
三、filter 表:ingress 流量走的是"普通"DOCKER 链
generated/swarm-portmap.md 记录的完整 filter 表如下(-vL 格式,包计数在生成时归零):
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 DOCKER-USER all -- any any anywhere anywhere
2 0 0 DOCKER-FORWARD all -- any any anywhere anywhere
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
Chain DOCKER (2 references)
num pkts bytes target prot opt in out source destination
1 0 0 ACCEPT tcp -- !docker_gwbridge docker_gwbridge anywhere 172.18.0.2 tcp dpt:http-alt
2 0 0 DROP all -- !docker0 docker0 anywhere anywhere
3 0 0 DROP all -- !docker_gwbridge docker_gwbridge anywhere anywhere
Chain DOCKER-BRIDGE (1 references)
num pkts bytes target prot opt in out source destination
1 0 0 DOCKER all -- any docker0 anywhere anywhere
2 0 0 DOCKER all -- any docker_gwbridge anywhere anywhere
Chain DOCKER-CT (1 references)
num pkts bytes target prot opt in out source destination
1 0 0 ACCEPT all -- any docker0 anywhere anywhere ctstate RELATED,ESTABLISHED
2 0 0 ACCEPT all -- any docker_gwbridge anywhere anywhere ctstate RELATED,ESTABLISHED
Chain DOCKER-FORWARD (1 references)
num pkts bytes target prot opt in out source destination
1 0 0 DOCKER-CT all -- any any anywhere anywhere
2 0 0 DOCKER-INTERNAL all -- any any anywhere anywhere
3 0 0 DOCKER-BRIDGE all -- any any anywhere anywhere
4 0 0 ACCEPT all -- docker0 any anywhere anywhere
5 0 0 DROP all -- docker_gwbridge docker_gwbridge anywhere anywhere
6 0 0 ACCEPT all -- docker_gwbridge !docker_gwbridge anywhere anywhere
Chain DOCKER-INTERNAL (1 references)
num pkts bytes target prot opt in out source destination
Chain DOCKER-USER (1 references)
num pkts bytes target prot opt in out source destination
等价的 -S(iptables 命令形式):
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N DOCKER
-N DOCKER-BRIDGE
-N DOCKER-CT
-N DOCKER-FORWARD
-N DOCKER-INTERNAL
-N DOCKER-USER
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-FORWARD
-A DOCKER -d 172.18.0.2/32 ! -i docker_gwbridge -o docker_gwbridge -p tcp -m tcp --dport 8080 -j ACCEPT
-A DOCKER ! -i docker0 -o docker0 -j DROP
-A DOCKER ! -i docker_gwbridge -o docker_gwbridge -j DROP
-A DOCKER-BRIDGE -o docker0 -j DOCKER
-A DOCKER-BRIDGE -o docker_gwbridge -j DOCKER
-A DOCKER-CT -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A DOCKER-CT -o docker_gwbridge -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A DOCKER-FORWARD -j DOCKER-CT
-A DOCKER-FORWARD -j DOCKER-INTERNAL
-A DOCKER-FORWARD -j DOCKER-BRIDGE
-A DOCKER-FORWARD -i docker0 -j ACCEPT
-A DOCKER-FORWARD -i docker_gwbridge -o docker_gwbridge -j DROP
-A DOCKER-FORWARD -i docker_gwbridge ! -o docker_gwbridge -j ACCEPT
逐链解读(与 libnetwork 的 bridge 驱动 生成规则的模式一致):
-
FORWARD 链:默认策略 ACCEPT,仅挂两条跳板规则到
DOCKER-USER(用户自定义扩展点,空)和DOCKER-FORWARD。index.md特别强调:filter 的 INPUT/OUTPUT 链不被 Docker 使用——来自宿主物理网络或宿主本身的报文是"路由进"桥接网络的,因此都经过 filter-FORWARD。 -
DOCKER 链(核心):共 3 条规则,其中第 1 条就是本场景的主角:
ACCEPT tcp -- !docker_gwbridge docker_gwbridge anywhere 172.18.0.2 tcp dpt:http-alt它放行"目的为 ingress 负载均衡沙箱在
docker_gwbridge上的网关端点172.18.0.2、目的端口 8080(即http-alt,DNAT 之前的端口)"且从桥外进入、再从该桥发出的 TCP 报文。这正是与任何普通容器发布端口完全相同的规则形态。第 2、3 条则是两个"禁止桥内自环"的 DROP:! -i docker0 -o docker0与! -i docker_gwbridge -o docker_gwbridge,对应"容器间通信受控"的常规模式。 -
DOCKER-BRIDGE 链:把目的出接口为
docker0或docker_gwbridge的报文送入DOCKER链做逐规则裁决。 -
DOCKER-CT 链:对所有经这两个桥的 RELATED,ESTABLISHED 回包直接 ACCEPT——即 DNAT 会话的回程流量不需要再次匹配发布端口规则。
-
DOCKER-FORWARD 链:处理顺序为
DOCKER-CT→DOCKER-INTERNAL(空,internal 网络专用)→DOCKER-BRIDGE→ 桥级兜底规则:docker0入接口直接 ACCEPT(默认 bridge 的默认行为);docker_gwbridge则体现"容器间通信被禁用"的网络模式——桥内流量 DROP(第 5 条),从该桥出到桥外 ACCEPT(第 6 条)。
关键结论(模板文档的 "Note that" 部分,必须完整保留):
- 存在一个名为
docker_gwbridge的桥接网络,专供 swarm ingress 使用;其规则遵循"禁用容器间通信"(ICC disabled)网络的常规模式; - 发布端口的建立方式是:在 ingress 负载均衡沙箱的
docker_gwbridge网关端点(172.18.0.2)上做一次普通端口映射,使用与其他任何发布容器端口相同的规则——natDOCKER链中的一条 DNAT 规则 + filterDOCKER链中的一条 ACCEPT 规则; - 因此不存在独立的
DOCKER-INGRESS链。
四、nat 表:一条 DNAT 加上两条 MASQUERADE
同一场景的 nat 表如下:
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 DOCKER all -- any any anywhere anywhere ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 DOCKER all -- any any anywhere !loopback/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
num pkts bytes target prot opt in out source destination
1 0 0 MASQUERADE all -- any !docker_gwbridge 172.18.0.0/16 anywhere
2 0 0 MASQUERADE all -- any !docker0 172.17.0.0/16 anywhere
Chain DOCKER (2 references)
num pkts bytes target prot opt in out source destination
1 0 0 DNAT tcp -- !docker_gwbridge any anywhere anywhere tcp dpt:http-alt to:172.18.0.2:8080
等价的 -S 形式:
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.18.0.0/16 ! -o docker_gwbridge -j MASQUERADE
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A DOCKER ! -i docker_gwbridge -p tcp -m tcp --dport 8080 -j DNAT --to-destination 172.18.0.2:8080
要点:
- PREROUTING/OUTPUT → DOCKER:仅对目的地址为"本机"(
ADDRTYPE dst-type LOCAL)的报文进入 natDOCKER链;OUTPUT 规则额外排除回环网段。这与用户网络发布端口的入口一致。 - DNAT 规则:
! -i docker_gwbridge -p tcp --dport 8080 -j DNAT --to-destination 172.18.0.2:8080——外部(或宿主本机)发往:8080的 TCP 报文被改写到 ingress 负载均衡沙箱在docker_gwbridge上的端点172.18.0.2:8080。注意它没有像旧版 ingress 实现那样绑定到某个固定外部 IP:-i条件是"只要不是从该桥内部来的",因此宿主任意网卡上的 8080 端口都生效。 - POSTROUTING 的两条 MASQUERADE:分别对来自
172.18.0.0/16(docker_gwbridge子网,EnableIPMasquerade开启)与172.17.0.0/16(默认docker0子网)的出站流量做源地址伪装,但排除"从本桥出去"的报文(! -o docker_gwbridge/! -o docker0),使桥内东西向流量不被 NAT。这两条正是docker_gwbridge网络EnableIPMasquerade=true选项的体现。
五、源码佐证:docker_gwbridge 从何而来
文档描述的行为可以在守护进程源码中得到印证:
docker_gwbridge网络的创建:daemon/libnetwork/default_gateway_linux.go 中定义了常量libnGWNetwork = "docker_gwbridge",Controller.createGWNetwork()通过 bridge 驱动创建该网络,驱动选项为bridge.BridgeName: "docker_gwbridge"、bridge.EnableICC: "false"、bridge.EnableIPMasquerade: "true"——这解释了第三节中"ICC 被禁用"的 DROP 规则与第四节中172.18.0.0/16的 MASQUERADE 规则的存在原因,也解释了它与默认docker0网络规则模式的区别。- 端口映射的通用实现:无论普通容器还是 ingress 沙箱,发布端口最终都走 bridge 驱动的同一套
addPortMappings逻辑(见 daemon/libnetwork/drivers/bridge/port_mapping_linux.go),在 nat 表写入 DNAT、在 filter 表写入 ACCEPT。测试源码 iptablesdoc_linux_test.go 中pollService的注释同样直接陈述:"Ingress ports are published as ordinary port mappings on the load-balancer sandbox's docker_gwbridge gateway endpoint"。 - 架构背景:daemon/libnetwork/docs/network.md 描述了 Swarm ingress 的整体拓扑——主机命名空间内创建
docker_gwbridge桥(本地子网,不泄漏到外部)、ingress_sbox 网络命名空间通过 veth 对接入该桥并持有172.18.0.2这样的端点地址;外部请求先经 nat 规则 DNAT 到172.18.0.2,再由 FORWARD 规则放行,回程经 conntrack 解 NAT 后原路返回。该文档还说明了--swarm-default-advertise-addr相关场景下流量如何经由docker_gwbridge中转并被 MASQUERADE——与本文 nat 表中! -o docker_gwbridge的排除项相互印证。
六、读者自检清单:如何在自己的主机上核对
由于本文档明确声明规则结构不是稳定接口,以下命令仅适用于"与当前仓库行为相同的守护进程版本":
# 查看 filter 表(含行号与包计数)
iptables -vL --line-numbers -t filter
# 查看 nat 表 DOCKER 链(等价于测试中 pollService 的判定命令)
iptables -t nat -S DOCKER
# 确认是否存在 ingress 端口 DNAT
iptables -t nat -S DOCKER | grep DNAT
对照要点:
- filter
DOCKER链应出现形如-A DOCKER -d 172.18.0.2/32 ! -i docker_gwbridge -o docker_gwbridge -p tcp --dport <宿主端口> -j ACCEPT的规则(子网/网关地址随实际docker_gwbridgeIPAM 配置变化); - nat
DOCKER链应出现-A DOCKER ! -i docker_gwbridge -p tcp --dport <宿主端口> -j DNAT --to-destination 172.18.0.2:<宿主端口>; - 用
iptables -S | grep DOCKER-INGRESS应查不到任何DOCKER-INGRESS链——这是当前实现与旧版 ingress 机制最直观的差异点。
七、注意事项与适用边界
- 开发用途警告(index.md 原文):Docker 的 iptables/ip6tables 规则结构在不同版本之间会变化,不构成稳定接口;生产环境应把 iptables 规则视为实现细节而非依赖。
- 重启后的规则顺序:bridge 驱动初始化时删除自定义链、随网络恢复重建;filter-FORWARD 链本身不清空,且网络重建顺序不确定,所以重启后规则排列可能与新建时不同,但语义等价。
- firewalld 交互:若系统运行 firewalld 且发生重载,iptables 规则会被清空,守护进程通过 dbus 监听其重载事件并重建规则。
- 测试运行前提:
TestBridgeIptablesDoc在 firewalld 运行中、rootless 模式或 nftables 后端(FirewallBackendDriver() == "nftables")下会跳过——本文展示的是传统 iptables 语义。
参考文件
- 模板文件:本文主线的模板(含
LFilter4/SFilter4/LNat4/SNat4占位符与 "Note that" 结论) - 生成的黄金文档:场景对应的完整 filter/nat 规则快照
- 总览与警告:生成机制说明、场景索引、稳定性警告
- 集成测试源码:
TestBridgeIptablesDoc、createServices、pollService的实现 - docker_gwbridge 网络创建:
createGWNetwork及其驱动选项 - bridge 驱动端口映射:
addPortMappings的通用实现 - Swarm ingress 架构说明:
docker_gwbridge、ingress_sbox 与流量路径的背景描述
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 StartedRust0623
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