首先我们通过 docker run -p 1234:80 -d nginx,启动一个nginx容器,宿主机的1234端口映射到nginx容器的80端口,然后我们打印一下iptables的 nat 表里面的规则 :
root@ubuntutest1:~# iptables -t nat -nL
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:1234 to:172.17.0.3:80
它完整地展示了 Docker 如何利用 iptables 的 NAT (网络地址转换) 功能来实现其核心网络特性:端口映射 和 容器访问外网。
接下来我们来逐链、逐行地深度解析这份“网络蓝图”。
整体概览
-t nat:我们查看的是nat表。这个表专门用于修改数据包的源/目标 IP 地址或端口,是实现端口转发、共享上网的关键。-n:以数字形式显示 IP 地址和端口,不进行 DNS 解析,输出更快、更清晰。- **
-L:列出 (List) 当前表中的所有链和规则。
各链详解
1. Chain PREROUTING (数据包到达时,路由决策前)
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
policy ACCEPT:这是这条链的默认策略。如果数据包没有匹配到任何规则,默认就接受它。target DOCKER:这是规则的动作。如果数据包匹配了这条规则,它就会被跳转到名为DOCKER的自定义链去继续处理。prot opt all --:匹配所有协议。source 0.0.0.0/0:匹配任何源 IP 地址。destination 0.0.0.0/0:匹配任何目标 IP 地址。ADDRTYPE match dst-type LOCAL:这是这条规则最关键的匹配条件。dst-type LOCAL表示数据包的目标地址是本机上的一个 IP 地址(比如你主机的eth0网卡的 IP,或者127.0.0.1)。
这条规则的整体含义:
“任何发往本机 IP 地址的数据包,都不要按默认流程处理了,请将它送到
DOCKER自定义链里去进行特殊处理。”
为什么这么做?
因为外部访问 http://<你的主机IP>:1234 的请求,其目标 IP 就是你主机的 IP。Docker 需要拦截这些请求,判断它们是不是要发给容器的。这是实现端口映射的第一步。
2. Chain INPUT (发往本机进程的包)
Chain INPUT (policy ACCEPT)
target prot opt source destination
这条链是空的。这意味着发往本机进程的数据包(比如你用 curl 127.0.0.1)将直接由内核的默认策略 ACCEPT 处理,不受 Docker 的 NAT 规则影响。这很合理,因为发往本机进程的流量通常不需要被转发到容器。
3. Chain OUTPUT (从本机发出的包)
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
这条规则和 PREROUTING 非常相似,但它在 OUTPUT 链上。
!127.0.0.0/8:注意这里的!,代表“非”。这条规则匹配的目标地址是本机 IP,但排除了127.0.0.0/8网段(也就是127.0.0.1到127.255.255.255)。
这条规则的整体含义:
“任何从本机发出、且目标地址是本机 IP(但不是回环地址
127.x.x.x)的数据包,也请送到DOCKER自定义链去处理。”
为什么这么做?
这主要是为了处理“主机访问自己的容器”的场景。比如,你在主机上执行 curl <你的主机IP>:1234。这个请求从本机发出,目标地址也是本机 IP,它需要被 Docker 拦截并转发到容器。而 curl 127.0.0.1:1234 则不受此规则影响,它通常有其他处理方式(比如通过 docker0 网桥直接路由)。
4. Chain POSTROUTING (数据包离开时)
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
target MASQUERADE:这是一个特殊的 SNAT (Source NAT, 源地址转换) 动作。它会将数据包的源 IP 地址修改为数据包离开时所用网络接口的 IP 地址。这就像一个伪装,让外部世界觉得数据包是从这台主机发出的,而不是从后面的容器发出的。source 172.17.0.0/16:这是匹配条件。172.17.0.0/16是 Docker 默认的网桥docker0的网段。所有容器都会从这个网段获得 IP 地址(比如你的172.17.0.3)。
这条规则的整体含义:
“任何从
172.17.0.0/16网段(即所有容器)发出的数据包,在离开主机时,都将其源 IP 地址伪装成主机的 IP 地址。”
为什么这么做?
这是为了让容器能够访问外部网络(比如 ping google.com)。
- 容器
172.17.0.3发送一个数据包给8.8.8.8。 - 数据包到达主机的
POSTROUTING链。 MASQUERADE规则匹配成功,数据包的源 IP 从172.17.0.3被修改成主机的公网 IP(比如203.0.113.10)。- Google 的服务器收到包,并回复给
203.0.113.10。 - 主机收到回复后,内核会记得这个连接是之前伪装过的,于是会自动把目标 IP 再改回
172.17.0.3,然后转发给容器。
没有这条规则,容器发出的包源 IP 是私有 IP,互联网上的路由器不知道如何回复,容器就无法与外界通信。这是实现容器访问外网的关键。
5. Chain DOCKER (Docker 的自定义规则)
Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:1234 to:172.17.0.3:80
这个 DOCKER 链就是 PREROUTING 和 OUTPUT 链将数据包送来的地方。(2 references) 表明有两条规则(即 PREROUTING 和 OUTPUT 中的规则)跳转到了这个链。
-
target RETURN:- 这是一个非常重要的动作。
RETURN意味着“立即返回”,即跳出当前链(DOCKER链),回到原来调用它的链(PREROUTING或OUTPUT)的下一条规则继续处理。 - 它匹配所有数据包 (
all -- 0.0.0.0/0 0.0.0.0/0)。 - 这实际上是一个**“默认通过”的兜底策略**。它的意思是:“如果一个数据包进入了
DOCKER链,但它不匹配下面任何一条具体的 DNAT 规则,那就把它放回去,按正常流程处理。”
- 这是一个非常重要的动作。
-
target DNAT:- 这是实现端口映射的核心。
prot opt tcp --:只匹配 TCP 协议。tcp dpt:1234:匹配目标端口为1234的 TCP 包。to:172.17.0.3:80:这是 DNAT 的核心。将匹配到的数据包的目标 IP 和端口,修改为172.17.0.3的80端口。
这条规则的整体含义:
“对于进入
DOCKER链的数据包:
- 首先,有一条
RETURN规则。它匹配所有数据包。这意味着,如果数据包不匹配下面的任何规则,它就会直接返回。- 接着,检查数据包是不是 TCP 协议,并且目标端口是不是
1234。如果是,就执行 DNAT,将目标地址:1234改为172.17.0.3:80,然后处理结束。- 如果数据包的目标端口不是
1234(比如是5678),它就不会匹配DNAT规则,但会匹配第一条RETURN规则,然后被返回给PREROUTING链,继续它的正常旅程。”
完整流程:一个外部请求的生命周期
现在,我们把所有规则串联起来,模拟一个外部用户访问 http://<你的主机IP>:1234 的完整过程。假设你的容器 IP 是 172.17.0.3,并且它内部运行着一个 Nginx 服务在监听 80 端口。
- 请求到达:一个来自互联网的 TCP 数据包,目标 IP 是你主机的 IP,目标端口是
1234。 - 进入
PREROUTING链:- 数据包匹配
ADDRTYPE match dst-type LOCAL规则。 target是DOCKER,所以数据包被跳转到DOCKER链。
- 数据包匹配
- 进入
DOCKER链:- 数据包首先匹配
RETURN规则吗?是的,但iptables会继续检查下面的规则。 - 数据包匹配下一条规则:它是 TCP 协议,目标端口是
1234。 target是DNAT,数据包的目标 IP 和端口被修改为172.17.0.3:80。- DNAT 执行完毕,
DOCKER链的处理结束。
- 数据包首先匹配
- 路由决策:内核现在看到这个数据包的目标 IP 是
172.17.0.3。它查询路由表,发现这个 IP 属于docker0网桥。 - 进入
FORWARD链:- 因为数据包的目标 IP 不是本机,所以它进入
FORWARD链(filter表)。 - Docker 在
FORWARD链中也设置了规则,通常会允许172.17.0.0/16网段内的流量通过。数据包被ACCEPT。
- 因为数据包的目标 IP 不是本机,所以它进入
- 转发到容器:内核将数据包通过
docker0网桥发送给 IP 为172.17.0.3的容器。 - 容器响应:容器里的 Nginx 进程处理请求,并返回一个响应数据包。这个包的源 IP 是
172.17.0.3:80,目标 IP 是外部用户的 IP。 - 进入
POSTROUTING链:- 响应包离开主机时,经过
POSTROUTING链。 - 它匹配
source 172.17.0.0/16规则。 target是MASQUERADE,响应包的源 IP 被伪装成主机的公网 IP。
- 响应包离开主机时,经过
- 返回用户:响应包被发送回互联网上的用户。用户完全不知道背后有容器的存在,他一直以为是在和你的主机通信。
总结
这份 iptables 规则清单是 Docker 网络实现的精髓。它通过一个清晰、分阶段的流程,巧妙地利用 Linux 内核的 iptables/netfilter 框架,实现了:
- 端口暴露:通过
PREROUTING+DOCKER链中的DNAT规则,将主机端口的访问精准地转发到容器内部端口。 - 外部访问:通过
POSTROUTING链中的MASQUERADE规则,让容器能够“伪装”成主机访问外部网络。 - 主机访问:通过
OUTPUT链的规则,确保从主机自身发出的、访问主机 IP 的请求也能被正确地转发到容器。
理解了这些规则,你就掌握了 Docker 网络最核心的原理,对于排查网络问题(如端口不通、容器无法上网等)将有极大的帮助。
1万+

被折叠的 条评论
为什么被折叠?



