容器访问外网 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 网络模式。


暂无评论内容