Docker 基础体系 · 第 46/70 篇。示例以现代 Docker Engine、BuildKit 与 Compose 规范为基础,并明确 Linux 容器边界。
Docker Bridge 与 iptables/nftables:NAT、端口发布、转发和冲突
Docker Bridge 网络不是“容器直接连到物理网卡”。在 Linux 容器中,Docker 通常创建一个 Linux bridge,例如默认的 docker0,再通过 veth pair 把每个容器的网络命名空间连接到这个 bridge。iptables 或 nftables 则负责在这个二层网络之上实现三类关键行为:
- 容器访问外部网络时的地址转换,即 SNAT/MASQUERADE;
- 外部或宿主机访问容器发布端口时的 DNAT;
- 是否允许数据包跨越宿主机网络接口转发,即 forwarding/filtering。
这里的“iptables”既可能指传统的 xtables 内核接口,也可能指运行在 nftables 后端上的 iptables-nft 命令。两者命令形式相似,但实际规则存储和与原生 nftables 规则的交互方式可能不同。现代 Docker Engine 的具体防火墙后端取决于 Engine 版本、发行版和 daemon 配置;Linux 容器默认应先按“Docker 管理 iptables 兼容规则”理解,再通过实际规则集确认。
一、先建立网络模型:Bridge、Namespace 和 veth
1. Linux Bridge 是什么
Linux bridge 是内核中的虚拟二层交换机。它有多个端口,可以转发以太网帧。Docker 的典型拓扑如下:
graph LR
C1["容器 netns<br/>eth0 172.17.0.2"]
V1["veth pair"]
B["docker0 bridge<br/>172.17.0.1"]
H["宿主机网络栈"]
U["物理网卡<br/>192.0.2.10"]
R["外部网络"]
C1 --- V1 --- B --- H --- U --- R
容器内部看到的是自己的 eth0,宿主机通常看到的是对应的 veth 一端。另一端被加入 docker0。
例如:
docker network inspect bridge
ip addr show docker0
ip link
bridge link
典型结果中可能看到:
docker0: inet 172.17.0.1/16
以及某个容器:
容器 eth0: 172.17.0.2/16
默认网关: 172.17.0.1
这里的地址只是默认配置示例,不是 Docker 的规范固定值。自定义 bridge、Compose 网络或其他 Docker 网络都可能使用不同子网。
2. Network namespace 决定“从哪里看网络”
容器的网络设备、路由表和部分 iptables 视图属于容器自己的 network namespace。宿主机执行:
ip route
看到的是宿主机路由,不是容器路由。
可以用以下方式查看容器内网络:
docker exec <container> ip addr
docker exec <container> ip route
docker exec <container> cat /etc/resolv.conf
如果镜像中没有 ip 命令,可以使用宿主机进入容器 namespace:
PID=$(docker inspect -f '{{.State.Pid}}' <container>)
nsenter -t "$PID" -n ip addr
nsenter -t "$PID" -n ip route
nsenter 要求宿主机具备相应权限,并且容器正在运行。
3. Bridge 网络的三种通信方向
对于一个典型 bridge 网络,需要区分三个方向:
- 容器到容器:数据包通常只经过 bridge 的二层转发,是否允许同一 bridge 上的容器互通还受 Docker 网络配置和过滤规则影响;
- 容器到外部网络:数据包离开 Docker bridge,经过宿主机三层转发,通常需要 SNAT/MASQUERADE;
- 外部网络到容器:如果使用端口发布,宿主机上的目标端口会被 DNAT 到容器 IP 和容器端口。
“容器有 IP”不等于“外部主机可以直接访问这个 IP”。容器子网通常只存在于宿主机及其二层网络内部,外部路由器并不知道如何返回 172.17.0.0/16 这类 Docker 私有网段。
二、iptables 与 nftables 在这里分别负责什么
1. iptables 是规则管理接口,不是单一的数据包处理机制
iptables 命令用于管理 Linux 内核中的 netfilter 规则。常见表和职责如下:
| 表 | 主要用途 | Docker 场景 |
|---|---|---|
nat |
地址转换 | 容器出站 MASQUERADE、发布端口 DNAT |
filter |
放行或丢弃 | 转发控制、bridge 间隔离 |
mangle |
修改标记、TTL、DSCP 等 | 通常不是 Docker Bridge 的核心 |
raw |
conntrack 前置控制 | 诊断或特殊网络策略 |
常见链包括:
PREROUTING:路由判断前处理收到的数据包;INPUT:目标是本机的数据包;FORWARD:需要经过本机转发的数据包;OUTPUT:本机进程发出的数据包;POSTROUTING:路由判断之后、离开接口之前处理。
Docker 通常还会创建自定义链,例如:
DOCKER
DOCKER-USER
DOCKER-FORWARD
DOCKER-BRIDGE
DOCKER-CT
具体链集合和顺序随 Docker Engine 版本、配置和防火墙后端变化。不要把某一台机器上看到的链顺序当成稳定 API。
2. nftables 是内核规则体系,iptables-nft 是兼容入口
现代 Linux 发行版常见两种情况:
iptables --version
可能输出:
iptables v1.8.x (nf_tables)
这表示 iptables 命令通过 nftables 兼容后端工作,规则通常会映射到 nftables 的 ip、ip6 或兼容表中。
也可能输出:
iptables v1.8.x (legacy)
这表示使用传统 xtables 后端。
直接查看 nftables 规则:
sudo nft list ruleset
查看 iptables 规则及计数器:
sudo iptables -t nat -vnL --line-numbers
sudo iptables -vnL --line-numbers
使用 iptables-nft 时,不能简单认为“iptables 规则”和“nft 规则”是两套完全独立且互不影响的机制。它们可能共同进入 netfilter,但表、优先级、兼容链和原生 nftables 链之间的关系依赖实现。反过来,如果系统仍使用 legacy 后端,原生 nftables 规则也不一定会与 legacy 规则按预期协同。
因此,排障时应同时确认:
iptables --version
sudo nft list ruleset
sudo iptables-save
3. Docker 的防火墙后端必须以实际版本为准
Docker Engine 长期以来主要通过 iptables 管理 bridge 网络所需的 NAT 和过滤规则。较新的 Engine 版本可能提供 nftables 防火墙后端或相关实验能力,但可用性、配置项、与现有防火墙管理器的交互属于版本相关行为。
不能仅因为系统安装了 nftables,就推断 Docker 一定使用原生 nftables;也不能仅因为执行了 iptables 命令,就推断规则一定存储在 legacy 后端。应检查当前 Engine 文档、daemon 配置和实际规则集。
三、容器出站:为什么需要 MASQUERADE
设:
- 容器地址为
C = 172.18.0.2; - Docker bridge 地址为
B = 172.18.0.1; - 宿主机外部地址为
H = 192.0.2.10; - 外部服务器为
S = 198.51.100.20; - 容器访问外部 TCP 443 端口。
容器最初发送的数据包可以表示为:
源地址:端口 = 172.18.0.2:40000
目的地址:端口 = 198.51.100.20:443
如果宿主机直接把这个包转发出去,而不做源地址转换,外部服务器的响应会发给:
172.18.0.2:40000
外部网络通常没有到 172.18.0.0/16 的路由,因此响应无法返回容器。
Docker 常见的规则逻辑是:
源地址属于 Docker 子网
并且出口接口不是该 Docker bridge
=> 对源地址执行 MASQUERADE
发送到外部网卡后,数据包变成:
源地址:端口 = 192.0.2.10:53000
目的地址:端口 = 198.51.100.20:443
这里端口可能被保留,也可能因为冲突被改写。MASQUERADE 是一种动态 SNAT,适合宿主机外部 IP 可能变化的场景。若宿主机有固定地址,也可以从设计上使用 SNAT,但 Docker 自动管理的规则通常会选择 MASQUERADE。
1. conntrack 让返回包能够还原
NAT 不是“每个包独立改地址”。内核通过 connection tracking,也就是 conntrack,保存连接映射:
内部方向:
172.18.0.2:40000 -> 198.51.100.20:443
转换为:
192.0.2.10:53000 -> 198.51.100.20:443
返回方向:
198.51.100.20:443 -> 192.0.2.10:53000
根据 conntrack 状态还原为:
198.51.100.20:443 -> 172.18.0.2:40000
因此,一条 TCP 连接通常只在第一个数据包经过 NAT 规则时决定映射,后续数据包依靠连接跟踪状态处理。这也解释了两个现象:
- 修改 NAT 规则后,已有连接可能继续沿用旧映射;
- 清空 conntrack 或重启相关网络组件可能中断已有连接。
查看连接跟踪需要安装 conntrack 工具:
sudo conntrack -L
权限、内核模块和发行版配置可能导致该命令不可用。
2. 转发开关是另一个独立条件
即使 NAT 规则存在,Linux 也必须允许 IPv4 转发:
sysctl net.ipv4.ip_forward
预期通常是:
net.ipv4.ip_forward = 1
如果结果为 0,容器可以访问自己的 bridge 网关,却可能无法访问外部网络。临时修改:
sudo sysctl -w net.ipv4.ip_forward=1
永久配置应由系统网络管理规范维护,而不是只依赖一次手工执行。IPv6 使用不同的 sysctl 和地址转换模型,不能把 IPv4 的结论直接套用到 IPv6。
3. 出站访问的必要条件
对一个典型 IPv4 bridge 出站连接,至少需要满足:
容器有可用默认路由
∧ bridge 能把包送到宿主机
∧ 宿主机允许 FORWARD
∧ 出口路径存在
∧ 外部回程可达
∧ 若外部没有 Docker 子网路由,则存在 SNAT/MASQUERADE
这是一个合取条件。只缺任意一项,都可能出现“容器能解析 DNS,但不能访问互联网”或“TCP SYN 发出但没有 SYN-ACK”的故障。
四、端口发布:-p 到底改变了什么
1. EXPOSE 不等于端口发布
Dockerfile 中的:
EXPOSE 8080
主要是镜像元数据,表达应用预计监听 8080 端口。它不会自动在宿主机打开端口,也不会创建 DNAT。
以下命令才会创建发布行为:
docker run -d --name web -p 8080:80 nginx
其语义通常是:
宿主机地址:8080
DNAT
容器地址:80
如果未指定宿主机地址,Docker 常见行为是绑定宿主机所有 IPv4 地址:
0.0.0.0:8080 -> 容器:80
显式绑定本机回环地址:
docker run -d --name web-local -p 127.0.0.1:8080:80 nginx
此时外部机器不能通过宿主机的物理地址访问该发布端口,宿主机本地进程仍可以访问。
IPv6、协议类型和绑定地址应明确指定。例如:
docker run -d --name dns-tcp -p 127.0.0.1:8053:53/tcp nginx
这只是端口映射语法示例;Nginx 默认并不提供 DNS 服务,不能把它当作可工作的 DNS 容器。
2. 外部数据包的实际路径
假设:
宿主机外部地址: 192.0.2.10
发布端口: 8080
容器地址: 172.18.0.2
容器端口: 80
外部客户端发送:
203.0.113.50:50000 -> 192.0.2.10:8080
在宿主机的 nat PREROUTING 阶段,Docker 规则将其转换为:
203.0.113.50:50000 -> 172.18.0.2:80
随后进行路由判断。因为新目标 172.18.0.2 位于 Docker bridge 子网,数据包进入 FORWARD 路径,经过 veth 和 bridge,到达容器。
完整路径可以概括为:
sequenceDiagram
participant C as 外部客户端
participant N as 宿主机 PREROUTING
participant D as Docker DNAT
participant F as FORWARD/filter
participant B as docker bridge
participant A as 容器应用
C->>N: 192.0.2.10:8080
N->>D: 进入 nat PREROUTING
D->>F: DNAT 到 172.18.0.2:80
F->>B: 允许转发
B->>A: veth -> eth0
A-->>C: 返回流量按 conntrack 反向 NAT
关键点是:发布端口的包在 DNAT 后通常不是“宿主机本地服务接收”,而是“宿主机转发到容器”。因此它主要经过 FORWARD,不应只检查 INPUT。
3. Docker 自定义链与规则位置
在传统 iptables 视图中,可能看到类似:
sudo iptables -t nat -S DOCKER
sudo iptables -S DOCKER-USER
sudo iptables -S DOCKER-FORWARD
具体输出可能类似:
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 8080 -j DNAT --to-destination 172.18.0.2:80
不要手工依赖容器 IP 编写永久规则,因为容器重建后 IP 可能变化。Docker 会根据容器生命周期创建和删除相关规则。
DOCKER-USER 的意义是给管理员放置自定义过滤规则。它通常位于 Docker 允许规则之前,但一个非常重要的事实是:
到达
DOCKER-USER时,发布端口的数据包通常已经完成 DNAT。
因此,在 DOCKER-USER 中匹配发布端口时,常常要匹配容器目标端口,而不是原始宿主机端口。例如原始目标是 192.0.2.10:8080,进入过滤路径时目标可能已经是 172.18.0.2:80。
如果必须匹配 DNAT 前的原始地址和端口,应使用 conntrack 扩展,例如:
sudo iptables -I DOCKER-USER -p tcp -m conntrack \
--ctorigdst 192.0.2.10 --ctorigdstport 8080 -j ACCEPT
这条规则依赖内核和 iptables 扩展支持,且读取原始连接元组通常比普通端口匹配更昂贵。规则真正生效前应先用计数器验证,不要直接把它用于生产全局策略。
五、宿主机本地访问发布端口与 Hairpin NAT
发布端口不只服务于外部客户端。宿主机自身访问:
curl http://127.0.0.1:8080
通常经过 nat OUTPUT,然后被 DNAT 到容器。
如果容器访问宿主机的外部地址和发布端口,例如:
容器 172.18.0.3
访问 192.0.2.10:8080
目标容器 172.18.0.2:80
这属于 hairpin NAT,也称 NAT loopback。数据包可能需要:
- 对发布端口执行 DNAT;
- 对源地址执行额外 SNAT,避免目标容器直接把响应发给原始容器而绕过 conntrack 的预期路径。
Docker 是否、何时以及如何为特定 bridge 场景创建 hairpin 相关规则,取决于网络模式、端口发布方式和 Engine 实现。出现以下现象时,不应只测试外部访问:
- 外部访问
宿主机地址:发布端口正常; - 宿主机本地访问正常;
- 容器访问同一宿主机地址的发布端口失败。
应分别从宿主机和容器内抓包,并检查 conntrack 与 NAT 规则。对于容器间调用,通常更可靠的方式是直接使用 Compose 服务名或容器网络别名,而不是绕回宿主机发布端口。
六、转发、过滤与 NAT 不是同一个概念
1. NAT 决定“地址如何改写”
NAT 解决的是:
这个连接的源地址或目标地址如何转换
它不等价于访问控制。存在 DNAT 规则不意味着一定放行,存在 MASQUERADE 规则也不意味着一定允许转发。
2. filter 决定“数据包是否允许通过”
容器访问外部网络通常涉及:
docker0 -> eth0
外部访问发布端口通常涉及:
eth0 -> docker0
这两种方向都可能经过 FORWARD。检查默认策略和规则:
sudo iptables -L FORWARD -vn --line-numbers
sudo iptables -S FORWARD
如果默认策略是 DROP,则必须存在明确允许规则。Docker 通常会创建自己的转发链,但宿主机的 firewalld、云初始化脚本、安全产品或管理员规则也可能改变最终结果。
3. 默认 bridge 的容器互通并非单纯依赖 IP 路由
同一 bridge 上的容器通常可以通过二层交换互通。Docker 的 bridge 网络还涉及:
- 是否允许同一网络上的容器间通信;
- 不同 Docker bridge 之间是否隔离;
- bridge 数据包是否经过 netfilter;
- 宿主机上的转发策略是否允许该方向。
不同网络的容器不能仅因为都属于 172.18.0.0/16 就假定可以互通。实际应检查:
docker network inspect <network>
docker inspect <container>
并从容器内部测试目标服务,而不是只测试目标 IP 是否存在。
七、iptables 与 nftables 的冲突机制
1. “两边都写规则”可能造成重复或顺序不确定
以下组件都可能操作防火墙:
- Docker Engine;
- firewalld;
- ufw;
- Kubernetes 或其他容器运行时;
- 云主机安全代理;
- 手工执行的 iptables/nftables 脚本;
- 网络管理服务在启动时恢复的规则。
冲突不一定表现为命令报错。更常见的是规则都成功写入,但因为优先级或链跳转顺序不同,最终数据包被提前接受、提前丢弃或进入错误的 NAT 规则。
在 nftables 中,链具有 hook 和 priority。两个规则可能挂在同一个 hook,但 priority 不同;iptables 兼容规则还可能出现在兼容表中。不能只看某条规则“存在”,必须确认它在目标数据包的实际路径上被执行。
2. ufw 的常见误判
许多系统中,管理员以为:
sudo ufw deny 8080/tcp
就一定阻止了 Docker 发布的 8080 端口。
但发布端口的数据包可能先在 nat PREROUTING 中被 DNAT 到容器,再进入 FORWARD,而不是进入宿主机的 INPUT。如果 ufw 只管理 INPUT,它可能看不到预期的目标。
验证方法不是查看 ufw 状态,而是:
sudo iptables -t nat -vnL --line-numbers
sudo iptables -vnL FORWARD --line-numbers
sudo iptables -S DOCKER-USER
并从另一台主机测试:
nc -vz 192.0.2.10 8080
生产环境中,应明确由哪一个组件负责 Docker 流量的入口控制,并把规则放在正确的转发路径,而不是只修改 INPUT。
3. firewalld、zone 与 Docker bridge
firewalld 可能把 Docker bridge 接口加入某个 zone,也可能通过 direct rules 或 nftables backend 管理过滤路径。接口归属、zone 的 target、转发策略和 masquerade 设置会共同影响结果。
可检查:
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --get-zone-of-interface=docker0
sudo firewall-cmd --list-all --zone=<zone>
命令是否存在取决于系统是否使用 firewalld。不要在已经由 firewalld 管理的系统中长期运行一套互不知情的规则恢复脚本,否则重载 firewalld 可能覆盖或重排规则。
4. nftables 的“默认 drop”会影响 Docker
原生 nftables 规则可能有:
chain forward {
type filter hook forward priority filter; policy drop;
}
即使 Docker 的 DNAT 正确,数据包仍可能在 forward hook 被丢弃。此时现象通常是:
- 宿主机端口监听或发布状态看起来正常;
- NAT 计数器可能增加;
- 容器应用没有收到连接;
- 客户端超时或收到连接被拒绝之外的错误。
应使用计数器和抓包确认丢包点,而不是重复执行 docker restart。
八、网络地址冲突:最隐蔽的 Bridge 问题
1. Docker 子网与物理网络重叠
假设 Docker bridge 使用:
172.18.0.0/16
而企业 VPN 也使用:
172.18.0.0/16
容器访问企业服务 172.18.10.20 时,容器路由表可能认为该地址就在本地 bridge 上:
172.18.0.0/16 dev eth0
于是数据包被送到 Docker bridge,而不是 VPN。此时 NAT 规则可能完全正确,但路由选择已经错误。
检查宿主机和容器路由:
ip route
docker exec <container> ip route
ip route get 172.18.10.20
docker network inspect <network>
解决方式是为 Docker 网络规划不与企业网、云 VPC、VPN 和宿主机路由重叠的地址池,例如在 daemon 配置中统一规划 default-address-pools。修改地址池通常只影响新建网络;已有网络可能需要重建,重建前应确认服务依赖和数据卷不受影响。
2. 端口冲突不是 NAT 冲突
两个容器都执行:
-p 8080:80
通常不能同时成功,因为同一宿主机地址、同一协议和同一端口不能被两个发布端口占用。Docker 会在创建或启动阶段报告端口分配失败。
这与“两个 NAT 规则都存在、匹配顺序冲突”不同。端口发布通常还涉及宿主机监听 socket 或端口分配检查。可以检查:
docker ps
docker port <container>
sudo ss -ltnp
以下两种发布可以共存:
127.0.0.1:8080 -> 容器 A:80
192.0.2.10:8080 -> 容器 B:80
因为宿主机绑定地址不同。但是否能从预期网络访问,还要考虑路由和过滤规则。
3. 多个 NAT 规则的匹配顺序
如果管理员手工添加一条比 Docker 规则更早匹配的 DNAT 规则:
192.0.2.10:8080 -> 其他目标
那么数据包可能根本不会到达 Docker 的 DOCKER 链。NAT 的第一包处理具有顺序性;“Docker 的规则存在”不能证明它获得了数据包。
九、一个可运行的端到端实验
以下实验在 Linux 宿主机上运行,要求:
- Docker Engine 已安装并运行;
- 当前用户有 Docker 权限;
- 主机允许创建 bridge 和配置防火墙;
- 端口
18080未被占用。
创建一个监听 HTTP 端口的容器:
docker run -d \
--name bridge-demo \
-p 127.0.0.1:18080:80 \
nginx:alpine
检查发布关系:
docker port bridge-demo
预期类似:
80/tcp -> 127.0.0.1:18080
检查容器 IP:
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' bridge-demo
测试宿主机访问:
curl -v http://127.0.0.1:18080/
如果成功,说明至少满足:
- Nginx 在容器内监听 80;
- 容器 bridge 网络正常;
- 宿主机本地发布路径的 DNAT 或代理路径正常;
- 返回流量能够回到 curl 进程。
查看对应规则和计数器:
sudo iptables -t nat -vnL --line-numbers
sudo iptables -vnL FORWARD --line-numbers
sudo nft list ruleset
再次执行 curl,再观察计数器是否增长。计数器不增长通常说明:
- 请求没有走到所查看的规则;
- 实际使用了另一套防火墙后端;
- 请求被本地 socket、代理或其他路径处理;
- 查看的是错误的地址族,例如 IPv4 规则而请求使用 IPv6。
查看连接套接字:
sudo ss -ltnp | grep 18080
Docker 的具体实现可能使用宿主机端口监听辅助机制、内核 DNAT,或二者结合;因此不能把 ss 中是否存在某个 Docker 进程当作所有版本的固定判断标准。
最后清理:
docker rm -f bridge-demo
十、按数据包路径排障,而不是按组件猜测
1. 先确认应用是否真的监听
docker exec bridge-demo ss -ltn
如果容器内没有 ss,可以:
docker exec bridge-demo cat /proc/net/tcp
更直接的方式是从另一个同网络容器测试:
docker run --rm --network container:bridge-demo \
curlimages/curl:latest -v http://127.0.0.1:80/
这个测试绕过了 bridge 到宿主机发布端口的路径,只验证应用是否在容器 namespace 内监听。
2. 确认 Docker 网络和路由
docker inspect bridge-demo
docker network ls
docker network inspect bridge
ip addr show docker0
ip route
重点观察:
- 容器是否加入预期网络;
- 容器 IP 是否变化;
- 默认网关是否是对应 bridge 地址;
- Docker 子网是否与 VPN 或主机路由重叠。
3. 在正确接口抓包
宿主机上可以同时抓 bridge、veth 和外部网卡:
sudo tcpdump -ni any 'tcp port 18080 or tcp port 80'
更细致地:
sudo tcpdump -ni docker0 tcp
sudo tcpdump -ni <外部网卡> tcp port 18080
从容器访问外部服务时:
sudo tcpdump -ni docker0 host 172.18.0.2
sudo tcpdump -ni <外部网卡> host <外部服务器IP>
常见判断方式:
- 外部网卡能看到 SYN,docker0 看不到转换后的 SYN:DNAT 或路由前规则有问题;
- docker0 能看到请求,容器 veth 看不到:bridge、veth 或过滤路径有问题;
- 容器收到 SYN 并发出 SYN-ACK,但外部收不到:回程路径、SNAT 或过滤规则有问题;
- 请求和响应都到达,但应用无响应:应用监听地址、协议或容器内部策略有问题。
4. 检查 ip_forward 和过滤策略
sysctl net.ipv4.ip_forward
sudo iptables -L FORWARD -vn --line-numbers
sudo nft list chain inet filter forward
最后一条命令中的表名和链名不一定存在;nftables 规则可能使用不同命名,应先执行:
sudo nft list ruleset
5. 检查发布端口是否仅绑定回环地址
如果执行:
docker run -d -p 127.0.0.1:18080:80 nginx:alpine
那么远程主机访问:
curl http://192.0.2.10:18080/
失败是预期行为,不是 Docker 网络故障。若目标是对外提供服务,应使用明确的外部绑定地址:
docker run -d -p 192.0.2.10:18080:80 nginx:alpine
或者根据实际需求绑定所有 IPv4 地址:
docker run -d -p 0.0.0.0:18080:80 nginx:alpine
绑定所有地址会扩大暴露面,必须同时考虑主机防火墙和云安全组。
十一、Docker 规则被覆盖或不应直接修改时怎么办
1. 不要把 Docker 生成链当作永久配置入口
容器启动、停止、网络创建和 daemon 重启都会使 Docker 重新计算规则。直接修改:
sudo iptables -t nat -I DOCKER ...
可能短期有效,但不一定能在容器重建后保留,也可能与 Docker 后续添加的规则顺序冲突。
管理自定义过滤策略时,优先考虑 Docker 提供的管理链,例如:
sudo iptables -I DOCKER-USER 1 ...
但仍必须验证当前 Docker 版本和防火墙后端是否提供该链,以及数据包在该链时是否已经 DNAT。
2. 规则更改应配合回滚
添加规则前保存当前状态:
sudo iptables-save > /root/iptables.before-docker-test
sudo nft list ruleset > /root/nft.before-docker-test
如果使用原生 nftables,应通过发行版支持的配置文件或服务管理规则,而不是只执行一次临时命令。iptables 与 nftables 混用时,回滚必须恢复对应后端,不要用 iptables-restore 试图恢复一份原生 nftables 配置。
3. 先验证,再重载防火墙
firewalld、ufw 或系统网络服务重载可能清理临时规则、改变链顺序或重新设置默认策略。修改前后都应检查:
docker ps
docker port <container>
sudo nft list ruleset
sudo iptables-save
curl ...
如果重载后容器发布端口立即失效,优先比较重载前后的规则差异和计数器,而不是直接重建所有容器。
十二、Bridge 模式的边界
本文讨论的是 Linux 容器使用的 bridge 网络。以下场景不能直接套用同一结论:
- host 网络模式:容器直接使用宿主机网络 namespace,通常没有独立容器 IP,也不需要同样的 bridge DNAT 路径;
- none 网络模式:容器几乎没有自动网络配置;
- macvlan/ipvlan:数据路径和宿主机与容器之间的通信限制不同;
- overlay 网络:通常还涉及 VXLAN、节点间封装和分布式控制面;
- rootless Docker:常使用用户态网络方案,可能没有 rootful Docker 的内核 bridge、iptables 和端口发布路径;
- Docker Desktop:Linux 容器可能运行在 Desktop 管理的 Linux VM 中,宿主机看到的端口转发还可能经过 Desktop 自身的虚拟化网络层。
此外,IPv4 的 MASQUERADE 经验不能直接推导 IPv6。IPv6 通常强调全局可路由地址和过滤策略,Docker 是否启用 IPv6、是否使用 NAT66 以及外部路由如何配置,都需要单独验证。
十三、几个容易混淆的结论
“端口发布成功,所以外部一定能访问”
不成立。端口发布成功只表示 Docker 接受了配置,并创建或准备了相应映射。外部访问还受以下因素影响:
绑定地址
∧ 地址族
∧ 宿主机路由
∧ DNAT
∧ FORWARD 过滤
∧ 云安全组
∧ 应用监听地址
∧ 返回路径
“容器能访问宿主机,所以容器能访问互联网”
不成立。访问 bridge 网关是本地路径,不需要外部路由和出站 MASQUERADE。互联网访问还需要宿主机转发、出口路由、源地址转换和外部回程。
“iptables 里有规则,所以 nftables 不会影响它”
不成立。iptables 可能使用 nftables 兼容后端,也可能与原生 nftables 规则共同参与 netfilter。最终结果取决于实际 hook、priority、链跳转和策略。
“把 FORWARD 默认策略设为 ACCEPT 就能解决所有问题”
也不成立。它可能绕过某个过滤问题,却不能修复:
- Docker 子网与 VPN 重叠;
- DNAT 目标错误;
- 应用没有监听;
- 宿主机端口绑定错误;
- conntrack 状态异常;
- 外部网络没有回程路径。
更严重的是,全局放开转发可能扩大容器和其他网络的暴露范围。
Docker Bridge 的核心可以用一条数据流概括:
容器出站:
容器地址 --bridge--> 宿主机转发 --MASQUERADE--> 外部网络
端口发布入站:
外部地址:宿主机端口 --DNAT--> 容器地址:容器端口 --FORWARD--> bridge --> 容器
返回流量:
依靠 conntrack 反向还原 NAT 映射
其中,Bridge 提供二层连接,路由决定下一跳,NAT 改写地址和端口,filter 规则决定是否允许通过,conntrack 保存连接状态;iptables 或 nftables 只是这些内核机制的规则管理入口。排障时沿着这条路径逐段验证,通常比单独检查某个 Docker 命令或某条防火墙规则更可靠。
系列导航与关联阅读
- 系列入口:Docker 完整学习路线:从镜像与容器到安全、可观测和生产交付
- 上一篇:Docker Bind Mount 深入:传播、递归、只读、符号链接和平台差异
- 下一篇:Docker 容器 DNS:嵌入式解析、Service Name、Search Domain 和故障
- 延伸:Docker 网络完整指南:Bridge、端口映射、DNS、Overlay 和排障
- 延伸:Docker 网络故障诊断:Namespace、Bridge、DNS、NAT、MTU 和抓包
官方资料
本文依据 Docker、OCI 与 CNCF 官方文档重新梳理;正文与生产检查清单由 WR BLOG 编写。

评论
0 条讨论