Docker 中网络命名空间
Docker 中容器的网络都是采用成熟的技术来实现的,例如 network namespace、iptables、Linux bridge、Linux veth 等等,要理解起来其实难度不大。这篇文章看看 Docker 网络中网络命名空间在其中的应用,网络命名空间是 Linux kernel 提供的一种轻量级网络隔离方案,隔离了包括网络设备、IPv4和IPv6协议栈、IP路由表、防火墙、/proc/net目录、/sys/class/net目录、端口(socket)等等。
Docker 中的 bridge 网络
Docker 安装启动之后,会在宿主机上创建一个 docker0 这样的网络设备,并且分配了一个 IP 地址。这里的 docker0 有多重身份,它是一个 Linux 的虚拟网桥,类似于一个物理交换机的功能,可以创建端口,报文在其中能二层转发;同时它也是一个 Linux 的网络设备,在内核中与普通的网卡无异,报文可以被网络协议栈处理。你可以参考这篇文章来获取更多内容。
如下是用 ip
命令查看到 docker0 的信息:
[root@localhost ~]# ip add sh docker0
6: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN
link/ether 02:42:3c:e6:72:b4 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 scope global docker0
valid_lft forever preferred_lft forever
[root@localhost ~]# ip -d link sh docker0
6: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT
link/ether 02:42:3c:e6:72:b4 brd ff:ff:ff:ff:ff:ff promiscuity 0
bridge
如果你安装了 bridge-utils
这样的工具包,那样你可以使用命令 brctl
查看到 Linux 的网桥:
[root@localhost ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02423ce672b4 no
当使用命令创建一个容器之后,这里以 ubuntu:14.04
镜像为例,发现宿主机上多出一个网络设备 veth71c3a08
,这是 Linux 中的 veth 设备。
[root@localhost ~]# docker run -idt ubuntu:14.04
93e29ba347817635cb62aacdf295fd0aa8045884459c1f6deffd025b3dd94425
[root@localhost ~]# ip -d link sh
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 promiscuity 0
...
6: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT
link/ether 02:42:3c:e6:72:b4 brd ff:ff:ff:ff:ff:ff promiscuity 0
bridge
22: veth71c3a08: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT
link/ether c6:62:45:4a:20:28 brd ff:ff:ff:ff:ff:ff promiscuity 1
veth
veth 设备是 Linux 中一种虚拟网络设备,它总是成对出现,从一个设备发送的数据总是在另一个设备接收到,类似于一根网线的两端,用 veth 这个原理就很容易实现网络不同命名空间之间的通信问题。例如上例中 Docker 容器实现就是在宿主机的 root 命名空间创建一个 veth71c3a08
,再创建了一个 veth peer 到该容器的网络命名空间中,也就是容器中的 eth0
。进入到容器中查看网络信息:
[root@localhost ~]# docker attach 93e29ba34781
root@93e29ba34781:/# ip add
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
...
21: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 scope global eth0
valid_lft forever preferred_lft forever
root@93e29ba34781:/# ip -d link sh eth0
21: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff promiscuity 0
veth
回到宿主机,使用 brctl
命令查看网桥信息,发现在网桥上多了一个接口,正是创建容器中网卡的另一个 veth peer。这样容器中访问外部网络,从 eth0
发出来的报文就等于送到 veth71c3a08
,再在 docker0 这个网桥上进行转发出去。
[root@localhost ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.02423ce672b4 no veth71c3a08
总结一下,Docker 中 bridge 网络其实非常简单,如下图所示,每个容器的网络在一个独立的网络命名空间内,他们之间是隔离的,通过 veth peer 对将其与 root 命名空间(也即宿主机所在的网络命名空间)相连。
怎么查看容器的网络命名空间
命令 ip netns
可以用来查看网络的命名空间,但是在宿主机上执行时却发现并没有,这是为什么呢?其实 ip netns
只能查看到使用该命令创建的命名空间,但 Docker 创建网络命名空间使用的是 Raw Socket 的接口来调用的。但我们还是有办法来使用 ip netns
来查看到 Docker 创建的命名空间的:获取到容器运行的进程 PID,取得其网络命名空间的虚拟文件,在 /var/run/netns/
目录下创建一个软链接即可。命令如下:
[root@localhost ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
93e29ba34781 ubuntu:14.04 "/bin/bash" 32 minutes ago Up 32 minutes sad_northcutt
[root@localhost ~]# container_id=
[root@localhost ~]# pid=$(docker inspect -f '' ${container_id})
[root@localhost ~]# mkdir -p /var/run/netns/
[root@localhost ~]# ln -sfT /proc/$pid/ns/net /var/run/netns/$container_id
[root@localhost ~]# ip netns
93e29ba34781
接下来就可以使用 ip
命令来查看容器里的网络信息了,得到的结果和进到容器里查看的结果是一样的,这也证明了容器里的网络就是用网络命名空间来实现的。
[root@localhost ~]# ip netns exec 93e29ba34781 ip -d link sh
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
...
21: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.2/16 scope global eth0
valid_lft forever preferred_lft forever
还有一种方法不需要创建软连接,使用 nsenter
命令,有兴趣可以参考SO上这个答案。
为容器再创建一个网卡
下面用前面讲到的知识,不通过调用 docker 的命令为容器再添加一张网卡。
-
创建一个 Linux Bridge,取名为 docker1,并为 docker1 分配一个 IP:
[root@localhost ~]# brctl addbr docker1 [root@localhost ~]# brctl show bridge name bridge id STP enabled interfaces docker0 8000.02423ce672b4 no vethfff8bb8 docker1 8000.000000000000 no [root@localhost ~]# ip add add 172.18.0.1/16 dev docker1 [root@localhost ~]# ip add show ... 6: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP link/ether 02:42:3c:e6:72:b4 brd ff:ff:ff:ff:ff:ff inet 172.17.0.1/16 scope global docker0 valid_lft forever preferred_lft forever 27: docker1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN link/ether 42:d1:17:2a:99:d5 brd ff:ff:ff:ff:ff:ff inet 172.18.0.1/16 scope global docker1 valid_lft forever preferred_lft forever
-
创建一对 veth 设备,分别取名为
veth-test
和veth-test-peer
:[root@localhost ~]# ip link add veth-test type veth peer name veth-test-peer [root@localhost ~]# ip link set veth-test up [root@localhost ~]# ip link show ... 36: veth-test-peer: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT qlen 1000 link/ether 46:14:50:f9:18:49 brd ff:ff:ff:ff:ff:ff 37: veth-test: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT qlen 1000 link/ether 0e:36:00:d9:58:fd brd ff:ff:ff:ff:ff:ff
-
将其中的
veth-test
加到第一步创建的 Linux bridge 上,另一个veth-test-peer
加入到容器的命名空间中,并重命名为 eth1、设置 IP 地址:[root@localhost ~]# brctl addif docker1 veth-test [root@localhost ~]# brctl show bridge name bridge id STP enabled interfaces docker0 8000.02423ce672b4 no vethfff8bb8 docker1 8000.baefdf2c27c9 no veth-test [root@localhost ~]# ip link set veth-test-peer netns f376fa75f9f7 [root@localhost ~]# ip netns exec ab3243b54282 ip link set veth-test-peer name eth1 [root@localhost ~]# ip netns exec ab3243b54282 ip add add 172.18.0.2/16 dev eth1 [root@localhost ~]# ip netns exec ab3243b54282 ip link set eth1 up [root@localhost ~]# ip netns exec ab3243b54282 ip add 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever ... 30: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff inet 172.17.0.2/16 scope global eth0 valid_lft forever preferred_lft forever 36: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 46:14:50:f9:18:49 brd ff:ff:ff:ff:ff:ff inet 172.18.0.2/16 scope global eth1 valid_lft forever preferred_lft forever
-
至此我们没用 docker 原生的网络命令就为容器添加了一张网卡,接下来进入容器中测试下创建的这张网卡网络连接是否正常。测试方法是通过新加得网卡 ping 宿主机的 IP 地址,由于默认路由被添加为走 eth0,现在手动添加一条路由:
[root@localhost ~]# ip netns exec ab3243b54282 ip route add 192.168.11.0/24 via 172.18.0.1 [root@localhost ~]# ip netns exec ab3243b54282 ip route default via 172.17.0.1 dev eth0 172.17.0.0/16 dev eth0 proto kernel scope link src 172.17.0.2 172.18.0.0/16 dev eth1 proto kernel scope link src 172.18.0.2 192.168.11.0/24 via 172.18.0.1 dev eth1 [root@localhost ~]# docker attach ab3243b54282 root@ab3243b54282:/# root@ab3243b54282:/# ping 192.168.11.120 PING 192.168.11.120 (192.168.11.120) 56(84) bytes of data. 64 bytes from 192.168.11.120: icmp_seq=1 ttl=64 time=0.062 ms 64 bytes from 192.168.11.120: icmp_seq=2 ttl=64 time=0.068 ms ^C --- 192.168.11.120 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 999ms rtt min/avg/max/mdev = 0.062/0.065/0.068/0.003 ms
参考资料
- http://www.infoq.com/cn/articles/docker-kernel-knowledge-namespace-resource-isolation
- http://cizixs.com/2017/02/10/network-virtualization-network-namespace
- https://coolshell.cn/articles/17029.html
- https://stackoverflow.com/questions/31265993/docker-networking-namespace-not-visible-in-ip-netns-list
- https://www.ibm.com/developerworks/cn/linux/1310_xiawc_networkdevice/index.html
comments powered by Disqus