- 由 虚拟的现实创建于9月 05, 2024 需要 4 分钟阅读时间
桥接网络
没有指定网络下,默认的网络模式 bridge,每个容器在 network namespace 隔离下有单独的协议栈,然后通过 veth peer 链接到宿主机上的 docker0 网桥。
- 容器内的 lo 没有画,不影响理解
- 宿主机需要 modprobe br_netfilter 后开启 ip_forward
- docker info 最下面几行出现 warning 代表没开
- docker0 的网段不要配置和内网的网段一样,否则会冲突
容器内访问外部的流量
例如使用前面说的新版本 tcpdump 抓包后,我们起个下面容器:
$ tcpdump -nn -i any icmp and host 223.5.5.5
# 另一个窗口上开启 ping 一个包
$ docker run --rm -d --name t1 nginx:alpine ping -c1 223.5.5.5
抓包输出为如下:
# 出去的报文
11:12:44.213854 veth91af98c P ifindex 45 02:42:0a:b9:00:02 ethertype IPv4 (0x0800), length 104: 172.17.0.2 > 223.5.5.5: ICMP echo request, id 2, seq 0, length 64
11:12:44.213854 docker0 In ifindex 3 02:42:0a:b9:00:02 ethertype IPv4 (0x0800), length 104: 172.17.0.2 > 223.5.5.5: ICMP echo request, id 2, seq 0, length 64
11:12:44.213895 ens160 Out ifindex 2 00:50:56:ad:6f:fa ethertype IPv4 (0x0800), length 104: 192.168.2.112 > 223.5.5.5: ICMP echo request, id 2, seq 0, length 64
# 回来的报文
11:12:44.233494 ens160 In ifindex 2 78:2c:29:c4:00:01 ethertype IPv4 (0x0800), length 104: 223.5.5.5 > 192.168.2.112: ICMP echo reply, id 2, seq 0, length 64
11:12:44.233522 docker0 Out ifindex 3 02:42:50:71:f0:99 ethertype IPv4 (0x0800), length 104: 223.5.5.5 > 172.17.0.2: ICMP echo reply, id 2, seq 0, length 64
11:12:44.233527 veth91af98c Out ifindex 45 02:42:50:71:f0:99 ethertype IPv4 (0x0800), length 104: 223.5.5.5 > 172.17.0.2: ICMP echo reply, id 2, seq 0, length 64
流量会根据图中箭头走:
$ ip route show
default via 192.168.2.1 dev eth0 proto static
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
192.168.2.0/24 dev eth0 proto kernel scope link src 192.168.2.112
$ ip route get 223.5.5.5
223.5.5.5 via 192.168.2.1 dev eth0 src 192.168.2.112 uid 0
cache
+-----------------------------------+--------------------------+
| Host | Container 1 |
| 路由后走 forward 默认路由 eth0 发出 | |
| +----------------------------+ | +----------------------+ |
| | Network Protocol Stack | | |Network Protocol Stack| |
| +----------------------------+ | +----------------------+ |
| | ↑ | | |
|.......|...........|...............|.............|............|
| | | | | |
| POST_ROUTING | | | |
| SNAT 2.112 | | | |
| | | | | |
| ↓ | | ↓ |
| +------+ +----------+ | +-------+ |
| |.2.112| |172.17.0.1| | | .0.2 | |
| +------+ +----------+ | +-------+ |
| | eth0 | | docker0 | | | eth0 | |
| +------+ +----------+ | +-------+ |
| ↓ ↑ | | |
| | | | | |
| | +-------+ | | |
| | | veth |<------------------+ |
| | +-------+ | |
| | | |
+-------↓---------------------------+--------------------------+
$ iptables -w -t nat -S POSTROUTING
-P POSTROUTING ACCEPT
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
# 要添加下面内核参数,iptables 对 bridge 的数据进行处理
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-arptables = 1
iptables 做 NAT 时候,是有记录状态的,可以使用 conntrack 查看:
$ conntrack -L -p icmp
icmp 1 29 src=172.17.0.2 dst=223.5.5.5 type=8 code=0 id=1 src=223.5.5.5 dst=192.168.2.112 type=0 code=0 id=1 mark=0 use=1
外部访问容器
$ docker run --rm -d --name t2 -p 80:80 nginx:alpine
# 外部机器 192.168.2.5 上
$ curl 192.168.2.112
流量会根据图中箭头走:
$ iptables -t nat -S
...
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 172.17.0.3:80
+-----------------------------------+--------------------------+
| Host | Container 1 |
| 路由后走 forward docker0 | |
| +----------------------------+ | +----------------------+ |
| | Network Protocol Stack | | |Network Protocol Stack| |
| +----------------------------+ | +----------------------+ |
| ↑ ↓ | ↑ |
|.......|................|..........|.............|............|
| | | | | |
| PRE_ROUTING | | | |
| DNAT 172.17.0.3:80 | | | |
| | | | | |
| ↑ ↓ | ↑ |
| +------+ +----------+ | +-------+ |
| |.2.112| |172.17.0.1| | | .0.3 | |
| +------+ +----------+ | +-------+ |
| | eth0 | | docker0 | | | eth0 | |
| +------+ +----------+ | +-------+ |
| ↑ ↓ | ↑ |
| | | | | |
| | | | | |
| | +-------+ | | |
| | | veth |>-------------------+ |
| | +-------+ | |
| | | |
| | | |
+-------↑---------------------------+--------------------------+
相关 conntrack 信息:
conntrack -L |& grep -P =80
tcp 6 117 TIME_WAIT src=192.168.2.5 dst=192.168.2.112 sport=51894 dport=80 src=172.17.0.3 dst=192.168.2.5 sport=80 dport=51894 [ASSURED] mark=0 use=1
外部访问容器有些人没注意有个坑,有些应用默认监听 127.0.0.1,这种情况 -p 映射后,发往容器的协议栈里,由于没有 bind 0.0.0.0:x 会导致外面无法访问。
docker-proxy 进程
如果你细心观察的话,你会发现有 docker-proxy 的进程存在:
$ ss -nlpt sport 80 | awk '{print $4,$5,$6}' | column -t
Local Address:Port Peer
0.0.0.0:80 0.0.0.0:* users:(("docker-proxy",pid=3709090,fd=4))
[::]:80 [::]:* users:(("docker-proxy",pid=3709098,fd=4))
本机回环接口(localhost)的流量不会经过 iptables 的 PREROUTING 链。因此,如果没有 docker-proxy,这些请求不会被正确地重定向到容器。
$ curl -I localhost:80
HTTP/1.1 200 OK
...
然后让进程完全停止后再 curl 看看 http 状态码:
$ kill -STOP 3709090
$ curl -I localhost:80
^C
# 外部机器上访问这个容器宿主机 80 端口依然可以访问
# 是因为外部进来是走的 PRE_ROUTING 被 DNAT 了
$ curl -I 192.168.2.112:80
HTTP/1.1 200 OK
...
然后恢复进程,能访问
$ kill -CONT 3709090
$ curl -I localhost:80
HTTP/1.1 200 OK
...
docker-proxy 存在的另一个原因也是因为 iptables 和内核可以处理 IPv4 的 DNAT 和 SNAT,但对于 IPv6 的支持,尤其是在早期版本的 Docker 中并不完善(新的 Linux 发行版系统里有 ip6tables 命令)。docker-proxy 可以在用户空间处理 IPv6 请求。
- 无标签