容器访问外网 NAT

容器访问外网 NAT

面试题

Docker 容器是如何访问外网的?NAT(网络地址转换)在其中起了什么作用?

标准答案

Docker 容器(默认 bridge 网络)通过 SNAT(Source Network Address Translation,源地址转换)实现对外网的访问。简单来说,容器的私有 IP 在出站时,源地址被替换为宿主机的公网 IP。

核心机制:SNAT

# 容器的 IP 是私有地址(172.17.0.0/16)
# 外网路由器不识别私有地址
# 所以需要把容器的源 IP 转换为宿主机的外网 IP

# 容器访问外网的完整流程:
# 容器 (172.17.0.2) → 8.8.8.8

# 1. 容器发出数据包
#    源: 172.17.0.2:54321
#    目标: 8.8.8.8:53

# 2. 经过 docker0 网桥到达宿主机

# 3. 宿主机 iptables 执行 SNAT(MASQUERADE)
#    源改为: 10.0.0.100:随机端口
#    目标不变: 8.8.8.8:53

# 4. 数据包通过宿主机 eth0 发出到互联网

# 5. 8.8.8.8 返回数据包
#    源: 8.8.8.8:53
#    目标: 10.0.0.100:随机端口

# 6. 宿主机收到回复,查连接跟踪表
#    找到反方向的映射关系
#    目标改回: 172.17.0.2:54321

# 7. 数据包通过 docker0 → veth → 容器

Docker 添加的 NAT 规则

# 查看 Docker 的 SNAT 规则
iptables -t nat -L POSTROUTING -n -v

# Chain POSTROUTING (policy ACCEPT)
# target     prot opt  source          destination
# MASQUERADE  all  --  172.17.0.0/16  0.0.0.0/0
#                     ↑ 所有来自容器网段的流量    ↑ 到任何目的地

# 这条规则的意思是:
# 所有来自 172.17.0.0/16 网段的出站数据包
# 源地址都被转换为宿主机出站网卡的 IP

MASQUERADE vs SNAT

# Docker 默认使用的是 MASQUERADE(伪装)
# 不是传统的 SNAT(源地址转换)

# MASQUERADE 和 SNAT 的区别:
# - SNAT:指定固定的转换后地址
#   iptables -t nat -A POSTROUTING -s 172.17.0.0/16 -j SNAT --to-source 10.0.0.100

# - MASQUERADE:自动选择出站接口的 IP
#   iptables -t nat -A POSTROUTING -s 172.17.0.0/16 -j MASQUERADE

# MASQUERADE 适合 IP 不固定的场景(如 DHCP 获取 IP)
# SNAT 性能更好,适合固定 IP 的场景

连接跟踪(conntrack)

# NAT 能工作的关键:连接跟踪
# 内核维护一个连接跟踪表,记录所有 NAT 的映射关系

# 查看连接跟踪表
conntrack -L | grep 172.17.0
# tcp 6 431999 ESTABLISHED src=172.17.0.2 dst=8.8.8.8 sport=54321 dport=53
#   src=8.8.8.8 dst=10.0.0.100 sport=53 dport=54321 [ASSURED] mark=0 use=1

# 连接跟踪表的结构
# 原始方向:容器 IP → 外网目标
# 回复方向:外网返回 → 宿主机 IP
# 这个映射关系让返回的数据包能正确找到对应的容器

# 查看连接跟踪统计
conntrack -S

验证 NAT 是否工作

# 1. 容器是否能访问外网
docker run --rm alpine ping -c 2 8.8.8.8
# 64 bytes from 8.8.8.8: seq=0 ttl=117 time=1.234 ms ✅

# 2. 查看连接跟踪
conntrack -L | grep 172.17.0

# 3. 在宿主机上抓包
tcpdump -i eth0 -n icmp &
docker run --rm alpine ping -c 2 8.8.8.8
# 可以看到源 IP 是宿主机的外网 IP,而不是容器的 IP

容器访问外网的完整例子

# 启动一个容器
docker run -d --name web nginx

# 在容器内更新包管理器(需要外网)
docker exec web apt update

# 网络路径分解
# 1. 容器发出 apt update 请求
#    源: 172.17.0.2:随机端口
#    目标: deb.debian.org:80

# 2. 数据包经过 docker0 → 宿主主机栈

# 3. MASQUERADE 生效
#    源: 172.17.0.2 → 宿主主机 eth0 的 IP(如 10.0.0.100)

# 4. 经过宿主机默认路由和网关,到达互联网

# 5. deb.debian.org 返回数据
#    源: deb.debian.org IP:80
#    目标: 10.0.0.100:随机端口

# 6. 宿主机根据 conntrack 表还原
#    目标: 172.17.0.2:随机端口

# 7. 数据包到达容器,apt 收到响应

无 NAT 的访问方式

# 如果不想要 NAT,可以:
# 1. 使用 host 网络模式
docker run -d --network host nginx
# 容器直接使用宿主机 IP,不需要 NAT

# 2. 使用 macvlan/ipvlan
docker network create -d macvlan -o parent=eth0 \
  --subnet=192.168.1.0/24 --gateway=192.168.1.1 my-net
docker run -d --network my-net --ip=192.168.1.100 nginx
# 容器有自己的局域网 IP,不需要 NAT

# 3. 宿主机配置路由(复杂,不推荐)
# 需要配置容器网段的路由到宿主机

常见问题

Q:容器能 ping 外网 IP,但 DNS 解析失败?

# DNS 问题,不是 NAT 问题
# 检查容器 DNS 配置
docker exec my-container cat /etc/resolv.conf
# nameserver 8.8.8.8 或 114.114.114.114

# 也可能是 DNS 端口被封
docker exec my-container nslookup google.com 8.8.8.8

Q:容器出网慢?

# 可能原因:
# 1. 连接跟踪表满了
# 查看 conntrack 表大小
sysctl net.netfilter.nf_conntrack_max
sysctl net.netfilter.nf_conntrack_count

# 2. 大量短连接导致 conntrack 表项激增
# 调整超时
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_time_wait=10

# 3. 使用 SNAT 替代 MASQUERADE(性能更好)
iptables -t nat -D POSTROUTING -s 172.17.0.0/16 -j MASQUERADE
iptables -t nat -A POSTROUTING -s 172.17.0.0/16 -j SNAT --to-source 10.0.0.100

Q:容器能出网,但外部不能访问容器服务?

# 这是正常的!NAT 是单向的
# 容器 → 外网:通过 SNAT ✅
# 外网 → 容器:需要 DNAT(端口映射)❌

# 解决:使用 -p 端口映射或 host 网络
docker run -d -p 8080:80 nginx

面试官追问

问:为什么 Docker 默认使用 MASQUERADE 而不是 SNAT?

答:MASQUERADE 会自动检测出站接口的 IP 地址。当宿主机 IP 变化时(如 DHCP 续期、拔插网线),MASQUERADE 能自动适应,而 SNAT 需要手动更新规则。对于使用 DHCP 获取 IP 的桌面环境和云服务器,MASQUERADE 更灵活。在固定 IP 的生产环境,可以改为 SNAT 获得更好的性能。

问:容器访问外网时,源端口怎么选?

答:容器的源端口是容器内应用自己选择的(通常是随机端口)。经过 MASQUERADE 后,宿主机也会选一个随机端口作为新的源端口。这两个端口可能不同,连接跟踪表会记录这个映射关系。宿主机端口的范围由内核参数 ip_local_port_range 控制。

问:如果禁用了 Docker 的 iptables 管理,容器还能访问外网吗?

答:不能。默认情况下容器访问外网完全依赖 iptables 的 MASQUERADE 规则。如果设置 "iptables": false,Docker 不会创建任何 iptables 规则,容器也无法访问外网。除非手动添加 MASQUERADE 规则。

总结

容器访问外网的背后是 Linux 内核的 SNAT/MASQUERADE 机制。核心流程是:容器出包 → iptables 改源 IP 为宿主机 IP → 外网返回 → conntrack 还原。理解这个流程就能明白”容器能访问外网,但外网不能绕过 -p 直接访问容器”的原因。如果对性能有要求,可以用 SNAT 代替 MASQUERADE,或者使用 host 网络模式。

© 版权声明
THE END
喜欢就支持一下吧
点赞10 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容