首页
/ Moby Swarm 发布端口的 iptables 规则解析:docker_gwbridge 上的普通端口映射机制

Moby Swarm 发布端口的 iptables 规则解析:docker_gwbridge 上的普通端口映射机制

2026-09-06 13:38:54作者:翟江哲Frasier

本文以 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 实现)是:

  1. 为每个"场景"(section)在 L3Segment 中创建一个独立的网络命名空间宿主,并在其中启动一个真实的 dockerd;
  2. 按场景配置创建网络、容器或 Swarm 服务;
  3. 执行 iptables -vL --line-numbers -t filteriptables -S -t filteriptables -vL --line-numbers -t nat 等命令捕获真实规则;
  4. 将捕获结果填充到 templates/ 目录下对应的 text/template 文件(本主题即 swarm-portmap.md 模板,其中的 {{index . "LFilter4"}}{{index . "SFilter4"}}{{index . "LNat4"}}{{index . "SNat4"}} 是占位符);
  5. 将生成结果与 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 完成 SwarmInitd.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-FORWARDindex.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 链:把目的出接口为 docker0docker_gwbridge 的报文送入 DOCKER 链做逐规则裁决。

  • DOCKER-CT 链:对所有经这两个桥的 RELATED,ESTABLISHED 回包直接 ACCEPT——即 DNAT 会话的回程流量不需要再次匹配发布端口规则。

  • DOCKER-FORWARD 链:处理顺序为 DOCKER-CTDOCKER-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)上做一次普通端口映射,使用与其他任何发布容器端口相同的规则——nat DOCKER 链中的一条 DNAT 规则 + filter DOCKER 链中的一条 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)的报文进入 nat DOCKER 链;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/16docker_gwbridge 子网,EnableIPMasquerade 开启)与 172.17.0.0/16(默认 docker0 子网)的出站流量做源地址伪装,但排除"从本桥出去"的报文(! -o docker_gwbridge / ! -o docker0),使桥内东西向流量不被 NAT。这两条正是 docker_gwbridge 网络 EnableIPMasquerade=true 选项的体现。

五、源码佐证:docker_gwbridge 从何而来

文档描述的行为可以在守护进程源码中得到印证:

  1. 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 网络规则模式的区别。
  2. 端口映射的通用实现:无论普通容器还是 ingress 沙箱,发布端口最终都走 bridge 驱动的同一套 addPortMappings 逻辑(见 daemon/libnetwork/drivers/bridge/port_mapping_linux.go),在 nat 表写入 DNAT、在 filter 表写入 ACCEPT。测试源码 iptablesdoc_linux_test.gopollService 的注释同样直接陈述:"Ingress ports are published as ordinary port mappings on the load-balancer sandbox's docker_gwbridge gateway endpoint"。
  3. 架构背景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_gwbridge IPAM 配置变化);
  • 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 语义。

参考文件

登录后查看全文
热门项目推荐
相关项目推荐