版本比较

标识

  • 该行被添加。
  • 该行被删除。
  • 格式已经改变。

桥接网络

没有指定网络下,默认的网络模式 bridge,每个容器在 network namespace 隔离下有单独的协议栈,然后通过 veth peer 链接到宿主机上的 docker0 网桥。

  • 容器内的 lo 没有画,不影响理解
  • 宿主机需要 modprobe br_netfilter 后开启 ip_forward
  • docker info 最下面几行出现 warning 代表没开
  • docker0 的网段不要配置和内网的网段一样,否则会冲突

容器内访问外部的流量

例如使用前面说的新版本 使用 tcpdump 抓包后,我们起个下面容器:


代码块
languagebash
$ 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


抓包输出为如下:

代码块
languagebash
# 出去的报文


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

流量会根据图中箭头走:

Image Added

代码块
languagebash
$ 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 


   
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
 cache

$ 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 查看:

代码块
languagebash
$ 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

外部访问容器

代码块
languagebash
$ docker run --rm -d --name t2 -p 80:80 nginx:alpine


# 外部机器 192.168.2.5 


$ curl 192.168.2.112
流量会根据图中箭头走:

流量会根据图中箭头走

Image Added

代码块
languagebash
$ 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 信息:

代码块
languagebash
conntrack -L |& grep -P =80
tcp      6 117 TIME_WAIT 
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 的进程存在:

代码块
languagebash
$ 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,这些请求不会被正确地重定向到容器。

代码块
languagebash
$ curl -I localhost:80


HTTP/1.1 200 OK


...

然后让进程完全停止后再 curl 看看 http 状态码:

代码块
languagebash
$ kill -STOP 3709090


$ curl -I localhost:80


^C


# 外部机器上访问这个容器宿主机 80 端口依然可以访问


# 是因为外部进来是走的 PRE_ROUTING  DNAT 


$ curl -I 192.168.2.112:80


HTTP/1.1 200 OK


...

然后恢复进程,能访问

代码块
languagebash
$ 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 请求。

目录