容器网络原理分析:veth 和 network namespace
1. Liunx veth-pair 和 network namespace
Docker 中容器的访问需要依赖 veth-pair 和 network namespace 等技术。network namespace(网络命名空间)代表的是独立的网络协议栈,不同的网络命令空间相互隔离,无法访问。而 veth-pair 可以打破这种限制,实现不同网络命令空间的相互访问。
构建包含 veth-pair 和 network namespace 的示意图如下:
创建网络命名空间 ns1 和 ns2:
[root@lianhua netns]$ ip netns add ns1
[root@lianhua netns]$ ip netns add ns2
[root@lianhua netns]$ ip netns list
ns2
ns1
默认情况下创建的网络命名空间只有一个 loopback 接口,将它 up 起来:
[root@lianhua netns]$ ip netns exec ns1 ip a 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 [root@lianhua netns]$ ip netns exec ns1 ip link set dev lo up [root@lianhua netns]$ ip netns exec ns1 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1 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 inet6 ::1/128 scope host valid_lft forever preferred_lft forever
创建 veth-pair 设备 veth-ns1-a 和 veth-ns2-b,并将 veth-pair 分别加到对应的 network namespace 中:
[root@lianhua netns]$ ip link add veth-ns1-a type veth peer name veth-ns2-b [root@lianhua netns]$ ip link set veth-ns1-a netns ns1 [root@lianhua netns]$ ip link set veth-ns2-b netns ns2 [root@lianhua netns]$ ip netns exec ns1 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1 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 inet6 ::1/128 scope host valid_lft forever preferred_lft forever 307: veth-ns1-a@if306: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether 8a:a2:45:23:aa:28 brd ff:ff:ff:ff:ff:ff link-netnsid 1 [root@lianhua netns]$ ip netns exec ns2 ip a 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 306: veth-ns2-b@if307: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000 link/ether 5e:a9:4e:d9:e2:5d brd ff:ff:ff:ff:ff:ff link-netnsid 0
veth 设备在网络命名空间中的表现是一个网络接口,可以把它们想象成一根“网线”,网线一头连在 ns1,一头连在 ns2。将接口 up 起来:
[root@lianhua netns]$ ip netns exec ns1 ip link set dev veth-ns1-a up [root@lianhua netns]$ ip netns exec ns2 ip link set dev veth-ns2-b up [root@lianhua netns]$ ip netns exec ns1 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1 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 inet6 ::1/128 scope host valid_lft forever preferred_lft forever 307: veth-ns1-a@if306: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 8a:a2:45:23:aa:28 brd ff:ff:ff:ff:ff:ff link-netnsid 1 inet6 fe80::88a2:45ff:fe23:aa28/64 scope link valid_lft forever preferred_lft forever
为接口配置 ip:
[root@lianhua netns]$ ip netns exec ns1 ip addr add 162.0.0.1/24 dev veth-ns1-a [root@lianhua netns]$ ip netns exec ns2 ip addr add 162.0.0.2/24 dev veth-ns2-b [root@lianhua netns]$ ip netns exec ns1 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1 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 inet6 ::1/128 scope host valid_lft forever preferred_lft forever 307: veth-ns1-a@if306: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 8a:a2:45:23:aa:28 brd ff:ff:ff:ff:ff:ff link-netnsid 1 inet 162.0.0.1/24 scope global veth-ns1-a valid_lft forever preferred_lft forever inet6 fe80::88a2:45ff:fe23:aa28/64 scope link valid_lft forever preferred_lft forever
进入到网络命名空间 ns1 中访问 ns2:
[root@lianhua netns]$ ip netns exec ns1 ping 162.0.0.2 PING 162.0.0.2 (162.0.0.2) 56(84) bytes of data. 64 bytes from 162.0.0.2: icmp_seq=1 ttl=64 time=0.066 ms 64 bytes from 162.0.0.2: icmp_seq=2 ttl=64 time=0.027 ms --- 162.0.0.2 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 999ms rtt min/avg/max/mdev = 0.027/0.046/0.066/0.020 ms [root@lianhua netns]$ ip netns exec ns1 ip route 162.0.0.0/24 dev veth-ns1-a proto kernel scope link src 162.0.0.1
可以看到,veth-pair 实现了从网络命令空间 ns1 到 ns2 的访问。
值得注意的是,在真实的 docker 网络模式下 veth-pair 打通的是容器 network namespace 和宿主机 root namespace 之间互相访问的限制,veth-pair 的一头在宿主机,一头在容器中(在容器中的一头会被 docker 更改接口名为 eth0)。
2. 容器网络原理
Docker 容器通过 namespace 做资源隔离,其中通过 network namespace 做网络资源隔离,docker 容器实质上可看作一个 network namespace。基于此,构建容器网络示意图如下:
创建网络命名空间 ns5,veth-pair 和网桥 lxcbr1:
[root@lianhua ~]$ ip netns add ns5 [root@lianhua ~]$ ip link add veth5.1 type veth peer name veth5.2 [root@lianhua ~]$ brctl addbr lxcbr1 [root@lianhua ~]$ brctl stp lxcbr1 off
将 veth5.2 添加到 ns5 内,veth5.1 连到网桥 lxcbr1:
[root@lianhua ~]$ ip link set veth5.2 netns ns5 [root@lianhua ~]$ ip netns exec ns5 ip link set dev veth5.2 up [root@lianhua ~]$ ip link set dev veth5.1 up [root@lianhua ~]$ brctl addif lxcbr1 veth5.1
在网桥 lxcbr1 上添加 ip,该 ip 将作为 ns5 网络接口的网关:
[root@lianhua ~]$ ifconfig lxcbr1 172.11.0.1/24 up # 类似 docker 机制,将 veth5.2 改名为 eth0 [root@lianhua ~]$ ip netns exec ns5 ip link set dev veth5.2 down [root@lianhua ~]$ ip netns exec ns5 ip link set dev veth5.2 name eth0 [root@lianhua ~]$ ip netns exec ns5 ip link set dev eth0 up [root@lianhua ~]$ ip netns exec ns5 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1 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 inet6 ::1/128 scope host valid_lft forever preferred_lft forever 312: eth0@if313: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether ea:23:3f:8f:9d:07 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet6 fe80::e823:3fff:fe8f:9d07/64 scope link valid_lft forever preferred_lft forever [root@lianhua ~]$ ip netns exec ns5 ifconfig eth0 172.11.0.2/24 up
为 network namespace 配置默认路由:
[root@lianhua ~]$ ip netns exec ns5 route add default gw 172.11.0.1 [root@lianhua ~]$ ip netns exec ns5 ip route default via 172.11.0.1 dev eth0 172.11.0.0/24 dev eth0 proto kernel scope link src 172.11.0.2
进入 ns5 内访问宿主机:
[root@lianhua ~]$ ip netns exec ns5 ping 192.168.0.69 PING 192.168.0.69 (192.168.0.69) 56(84) bytes of data. 64 bytes from 192.168.0.69: icmp_seq=1 ttl=64 time=0.041 ms 64 bytes from 192.168.0.69: icmp_seq=2 ttl=64 time=0.026 ms --- 192.168.0.69 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 999ms rtt min/avg/max/mdev = 0.026/0.033/0.041/0.009 ms
3. 容器到 network namespace 的映射
前面介绍了,容器访问实际上是 network namespace 中 veth 设备的访问。那么我们在真实创建的容器中查看对应的 network namesapce:
[root@lianhua ~]$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES 459df1132c4b caps1371 "/bin/bash" 6 days ago Up 6 days test2 94a3abcf7e54 caps1371 "/bin/bash" 6 days ago Up 6 days test1 [root@lianhua ~]$ ip netns list
宿主机上有两个容器,但是为什么看不到容器对应的 network namespace?
这是因为 ip nets 无法查看 docker 创建的 network namespace,进行如下适配即可看到容器对应的 network namespace。
查找容器对应的进程号:
[root@lianhua ~]$ docker inspect --format '{{ .State.Pid }}' test1 43163 [root@lianhua ~]$ docker inspect --format '{{ .State.Pid }}' test2 43265
建立容器到 network namespace 的映射:
# 创建 /var/run/netns 目录,ip netns 会查找该目录下的 network namespace
[root@lianhua ~]$ mkdir -p /var/run/netns [root@lianhua ~]$ ln -s /proc/43163/ns/net /var/run/netns/test1 [root@lianhua ~]$ ln -s /proc/43265/ns/net /var/run/netns/test2
show network namespace:
[root@lianhua ~]$ ip netns list test2 (id: 3) test1 (id: 2) [root@lianhua ~]$ ip netns exec test1 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1 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 298: eth0@if299: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:19:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.25.0.2/16 brd 172.25.255.255 scope global eth0 valid_lft forever preferred_lft forever 302: eth1@if303: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:1a:00:03 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.26.0.3/16 brd 172.26.255.255 scope global eth1 valid_lft forever preferred_lft forever [root@lianhua ~]$ ip netns exec test2 ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1 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 300: eth0@if301: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default link/ether 02:42:ac:1a:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 172.26.0.2/16 brd 172.26.255.255 scope global eth0 valid_lft forever preferred_lft forever
在 network namespace 中 show 出了容器的网络接口。掌握了 veth-pair,network namespace 和容器的实现机制,那么我们就可以给正在运行的容器添加接口(veth-pair)了。
进一步的,查看 test1 和 test2 的网络 id 是否一致:
[root@lianhua ~]$ ip netns exec test1 ls -la /proc/self/ns/ total 0 ... lrwxrwxrwx 1 root root 0 Jan 12 00:42 net -> net:[4026532403] [root@lianhua ~]$ ip netns exec test2 ls -la /proc/self/ns/ total 0 ... lrwxrwxrwx 1 root root 0 Jan 12 00:42 net -> net:[4026532502]
net:[] 中括号内是 network namespace 的 id,相同的 network namespace 具有相同的 id,可以看到 test1 和 test2 的 id 是不一样的,验证了它们处于不同的 network namespace 中。
芝兰生于空谷,不以无人而不芳。