复制 $ sudo ip link add veth0 type veth peer name ceth0 1. 用这条简单的命令,我们就可以创建一对互联的虚拟Ethernet设备。默认选择了veth0和ceth0这两个名称。
复制 $ ip link 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000 link/ether 52:54:00:e3:27:77 brd ff:ff:ff:ff:ff:ff 5: ceth0@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 66:2d:24:e3:49:3f brd ff:ff:ff:ff:ff:ff 6: veth0@ceth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 96:e8:de:1d:22:e0 brd ff:ff:ff:ff:ff:ff 创建的veth0和ceth0都在主机的网络栈(也称为root网络命名空间)上。将netns0命名空间连接到root命名空间,需要将一个设备留在root命名空间,另一个挪到netns0里:
复制 $ sudo ip link set ceth0 netns netns0 # 列出所有设备,可以看到ceth0已经从root栈里消失了 $ ip link 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: eth0: <BROADCAST,MULTICAST,UP,LOWER/_UP> mtu 1500 qdisc fq/_codel state UP mode DEFAULT group default qlen 1000 link/ether 52:54:00:e3:27:77 brd ff:ff:ff:ff:ff:ff 6: veth0@if5: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 96:e8:de:1d:22:e0 brd ff:ff:ff:ff:ff:ff link-netns netns0 一旦启用设备并且分配了合适的IP地址,其中一个设备上产生的包会立刻出现在其配对设备里,从而连接起两个命名空间。从root命名空间开始:
复制 $ sudo ip link set veth0 up $ sudo ip addr add 172.18.0.11/16 dev veth0 然后是netns0:
复制 $ sudo nsenter --net=/var/run/netns/netns0 $ ip link set lo up $ ip link set ceth0 up $ ip addr add 172.18.0.10/16 dev ceth0 $ ip link 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 5: ceth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000 link/ether 66:2d:24:e3:49:3f brd ff:ff:ff:ff:ff:ff link-netnsid 0
检查连通性:
复制 # 在netns0里ping root的 veth0 $ ping -c 2 172.18.0.11 PING 172.18.0.11 (172.18.0.11) 56(84) bytes of data. 64 bytes from 172.18.0.11: icmp_seq=1 ttl=64 time=0.038 ms 64 bytes from 172.18.0.11: icmp_seq=2 ttl=64 time=0.040 ms --- 172.18.0.11 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 58ms rtt min/avg/max/mdev = 0.038/0.039/0.040/0.001 ms # 离开 netns0 $ exit # 在root命名空间里ping ceth0 $ ping -c 2 172.18.0.10 PING 172.18.0.10 (172.18.0.10) 56(84) bytes of data. 64 bytes from 172.18.0.10: icmp_seq=1 ttl=64 time=0.073 ms 64 bytes from 172.18.0.10: icmp_seq=2 ttl=64 time=0.046 ms --- 172.18.0.10 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 3ms rtt min/avg/max/mdev = 0.046/0.059/0.073/0.015 ms 同时,如果尝试从netns0命名空间访问其他地址,也同样可以成功:
复制 # 在 root 命名空间 $ ip addr show dev eth0 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000 link/ether 52:54:00:e3:27:77 brd ff:ff:ff:ff:ff:ff inet 10.0.2.15/24 brd 10.0.2.255 scope global dynamic noprefixroute eth0 valid_lft 84057sec preferred_lft 84057sec inet6 fe80::5054:ff:fee3:2777/64 scope link valid_lft forever preferred_lft forever # 记住这里IP是10.0.2.15 $ sudo nsenter --net=/var/run/netns/netns0 # 尝试ping主机的eth0 $ ping 10.0.2.15 connect: Network is unreachable # 尝试连接外网 $ ping 8.8.8.8 connect: Network is unreachable 这也很好理解。在netns0路由表里没有这类包的路由。唯一的entry是如何到达172.18.0.0/16网络:
复制 # 在netns0命名空间: $ ip route 172.18.0.0/16 dev ceth0 proto kernel scope link src 172.18.0.10 Linux有好几种方式建立路由表。其中一种是直接从网络接口上提取路由。记住,命名空间创建后, netns0里的路由表是空的。但是随后我们添加了ceth0设备并且分配了IP地址172.18.0.0/16。因为我们使用的不是简单的IP地址,而是地址和子网掩码的组合,网络栈可以从其中提取出路由信息。目的地是172.18.0.0/16的每个网络包都会通过ceth0设备。但是其他包会被丢弃。类似的,root命名空间也有了个新的路由:
复制 # 在root命名空间: $ ip route # ... 忽略无关行 ... 172.18.0.0/16 dev veth0 proto kernel scope link src 172.18.0.11 这里,就可以回答第一个问题了。我们了解了如何隔离,虚拟化并且连接Linux网络栈。
复制 # 从 root 命名空间 $ sudo ip netns add netns1 $ sudo ip link add veth1 type veth peer name ceth1 $ sudo ip link set ceth1 netns netns1 $ sudo ip link set veth1 up $ sudo ip addr add 172.18.0.21/16 dev veth1 $ sudo nsenter --net=/var/run/netns/netns1 $ ip link set lo up $ ip link set ceth1 up $ ip addr add 172.18.0.20/16 dev ceth1 检查连通性:
复制 # 从netns1无法连通root 命名空间! $ ping -c 2 172.18.0.21 PING 172.18.0.21 (172.18.0.21) 56(84) bytes of data. From 172.18.0.20 icmp_seq=1 Destination Host Unreachable From 172.18.0.20 icmp_seq=2 Destination Host Unreachable --- 172.18.0.21 ping statistics --- 2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 55ms pipe 2 # 但是路由是存在的! $ ip route 172.18.0.0/16 dev ceth1 proto kernel scope link src 172.18.0.20 # 离开 `netns1` $ exit # 从 root 命名空间无法连通`netns1` $ ping -c 2 172.18.0.20 PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data. From 172.18.0.11 icmp_seq=1 Destination Host Unreachable From 172.18.0.11 icmp_seq=2 Destination Host Unreachable --- 172.18.0.20 ping statistics --- 2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 23ms pipe 2 # 从netns0可以连通 `veth1` $ sudo nsenter --net=/var/run/netns/netns0 $ ping -c 2 172.18.0.21 PING 172.18.0.21 (172.18.0.21) 56(84) bytes of data. 64 bytes from 172.18.0.21: icmp_seq=1 ttl=64 time=0.037 ms 64 bytes from 172.18.0.21: icmp_seq=2 ttl=64 time=0.046 ms --- 172.18.0.21 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 33ms rtt min/avg/max/mdev = 0.037/0.041/0.046/0.007 ms # 但是仍然无法连通netns1 $ ping -c 2 172.18.0.20 PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data. From 172.18.0.10 icmp_seq=1 Destination Host Unreachable From 172.18.0.10 icmp_seq=2 Destination Host Unreachable --- 172.18.0.20 ping statistics --- 2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 63ms pipe 2 晕!有地方出错了……netns1有问题。它无法连接到root,并且从root命名空间里也无法访问到它。但是,因为两个容器都在相同的IP网段172.18.0.0/16里,从netns0容器可以访问到主机的veth1。
这里花了些时间来找到原因,不过很明显遇到的是路由问题。先查一下root命名空间的路由表:
复制 $ ip route # ... 忽略无关行... # 172.18.0.0/16 dev veth0 proto kernel scope link src 172.18.0.11 172.18.0.0/16 dev veth1 proto kernel scope link src 172.18.0.21 在添加了第二个veth对之后,root的网络栈知道了新路由172.18.0.0/16 dev veth1 proto kernel scope link src 172.18.0.21,但是之前已经存在该网络的路由了。当第二个容器尝试ping veth1时,选中的是第一个路由规则,这导致网络无法连通。如果我们删除第一个路由sudo ip route delete 172.18.0.0/16 dev veth0 proto kernel scope link src 172.18.0.11,然后重新检查连通性,应该就没有问题了。netns1可以连通,但是netns0就不行了。
复制 $ sudo ip netns delete netns0 $ sudo ip netns delete netns1 $ sudo ip link delete veth0 $ sudo ip link delete ceth0 $ sudo ip link delete veth1 $ sudo ip link delete ceth1 快速重建两个容器。注意,我们没有给新的veth0和veth1设备分配任何IP地址:
复制 $ sudo ip netns add netns0 $ sudo ip link add veth0 type veth peer name ceth0 $ sudo ip link set veth0 up $ sudo ip link set ceth0 netns netns0 $ sudo nsenter --net=/var/run/netns/netns0 $ ip link set lo up $ ip link set ceth0 up $ ip addr add 172.18.0.10/16 dev ceth0 $ exit $ sudo ip netns add netns1 $ sudo ip link add veth1 type veth peer name ceth1 $ sudo ip link set veth1 up $ sudo ip link set ceth1 netns netns1 $ sudo nsenter --net=/var/run/netns/netns1 $ ip link set lo up $ ip link set ceth1 up $ ip addr add 172.18.0.20/16 dev ceth1 $ exit 确保主机上没有新的路由:
复制 $ ip route default via 10.0.2.2 dev eth0 proto dhcp metric 100 10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100 最后创建网桥接口:
复制 $ sudo ip link add br0 type bridge $ sudo ip link set br0 up 将veth0和veth1接到网桥上:
复制 $ sudo ip link set veth0 master br0 $ sudo ip link set veth1 master br0
检查容器间的连通性:
复制 $ sudo nsenter --net=/var/run/netns/netns0 $ ping -c 2 172.18.0.20 PING 172.18.0.20 (172.18.0.20) 56(84) bytes of data. 64 bytes from 172.18.0.20: icmp_seq=1 ttl=64 time=0.259 ms 64 bytes from 172.18.0.20: icmp_seq=2 ttl=64 time=0.051 ms --- 172.18.0.20 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 2ms rtt min/avg/max/mdev = 0.051/0.155/0.259/0.104 ms 复制 $ sudo nsenter --net=/var/run/netns/netns1 $ ping -c 2 172.18.0.10 PING 172.18.0.10 (172.18.0.10) 56(84) bytes of data. 64 bytes from 172.18.0.10: icmp_seq=1 ttl=64 time=0.037 ms 64 bytes from 172.18.0.10: icmp_seq=2 ttl=64 time=0.089 ms --- 172.18.0.10 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 36ms rtt min/avg/max/mdev = 0.037/0.063/0.089/0.026 ms 太好了!工作得很好。用这种新方案,我们根本不需要配置veth0和veth1。只需要在ceth0和ceth1端点分配两个IP地址。但是因为它们都连接在相同的Ethernet上(记住,它们连接到虚拟switch上),之间在L2层是连通的:
复制 $ sudo nsenter --net=/var/run/netns/netns0 $ ip neigh 172.18.0.20 dev ceth0 lladdr 6e:9c:ae:02:60:de STALE $ exit $ sudo nsenter --net=/var/run/netns/netns1 $ ip neigh 172.18.0.10 dev ceth1 lladdr 66:f3:8c:75:09:29 STALE $ exit 太好了,我们学习了如何将容器变成友邻,让它们互不干扰,但是又可以连通。