本文演示 CentOS 7.6:Linux testerfans 3.10.0-1160.45.1.el7.x86_64
前言
Linux network namespace在Linux 2.6.24加入到内核中,用来隔离网络设备,协议栈,端口等。每个network namespace在逻辑上是网络堆栈的另一个副本,具有自己的路由、防火墙规则和网络设备。默认情况下,进程从其父进程继承其网络命名空间。在没有新建网络命名空间情况下,所有进程都与 init 进程共享相同的默认网络命名空间。本章我们将重点通过演示来帮助大家理解什么是network namespace,如何实现不同network namespace之间的网络通信。
Linux ip netns介绍
在实际演示network namespace之前我们首先介绍一下Linux ip netns工具。ip netns工具可以实现给network namespace进行命名,然后通过名字对namespace进行操作。
按照惯例,named network namespace是 /var/run/netns/NAME 中可以打开的对象。打开 /var/run/netns/NAME 产生的文件描述符引用指定的网络命名空间。保持该文件描述符打开可以使网络命名空间保持活动状态。
ip netns命令
我们通过本机使用 man ip-netns或者ip-netns可以查看ip netns的使用说明。
CMD | 描述 |
---|---|
ip netns list | 显示所有在 /var/run/netns的netns |
ip netns add NAME | 创建一个新的命名netns。如果 NAME 在 /var/run/netns 这个命令中可用则创建一个新的netns并分配 NAME。 |
ip netns attach NAME PID | 创建一个新的命名netns。如果 NAME 在 /var/run/netns 这个命令中可用,将进程 PID 的netns附加到 NAME 就像它是用 ip netns 创建的一样。 |
ip [-all] netns delete [ NAME ] | 删除命名netns。如果 NAME 存在于 /var/run/netns 中,它将被卸载并且挂载点被删除。 如果这是netns的最后一个用户 ,netns将被释放,所有物理设备都将移至默认设备。 否则网络命名空间会一直存在,直到它没有更多的用户。 如果挂载点被其他mnt namespace使用, ip netns delete 执行可能会失败。 如果指定了 -all 选项,则所有netns名称将被删除。 |
ip netns set NAME NETNSID | 这个命令将一个ID分配给一个netns。 此 id 仅在当前网络命名空间中有效。如果指定了关键字“auto”,则内核会自动分配一个有效的nsid。 这个 id 将在某些netlink消息中被内核使用。如果内核在使用netns id的时候不存在,它将由内核自动分配。 一经分配,这个ID就无法更改。 |
ip netns pids NAME | 查看命名netns内的进程。这个命令遍历proc并且找到所有拥有named netns作为他们的主netns的进程。 |
ip [-all] netns exec [ NAME ] cmd … | 在指定的命名netns中执行命令。 |
ip netns monitor | 监控对命名netns的操作。 |
ip netns list-id [target-nsid POSITIVE-INT] [nsid POSITIVE-INT] | 以id形式显示所有的命名netns。 |
创建network namespace
- 通过
readlink /proc/$$/ns/net
查看当前进程所在的netns。
[root@testerfans ~]# readlink /proc/$$/ns/net
net:[4026531956]
- 通过
ip netns add mynet
创建一个mynet的网络命名空间。 - 通过
ls /var/run/netns
查看在netns路径下多了一个mynet的文件对象。
[root@testerfans ~]# ip netns add mynet
[root@testerfans ~]# ls /var/run/netns
mynet
- 通过
ip netns exec mynet /bin/bash
在mynet命名空间下启动一个bash进程。 - 通过
readlink /proc/$$/ns/net
查看命名空间为net:[4026532292],说明bash运行在一个新的netns内。
[root@testerfans ~]# ip netns exec mynet /bin/bash
[root@testerfans ~]# readlink /proc/$$/ns/net
net:[4026532292]
我们在新的netns内启动了一个bash进程,那这个新的命名空间有什么不同呢?
- 每个新创建的 network namespace 默认有一个本地环回接口 lo,并且这个接口是DOWN状态。
[root@testerfans ~]# ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
- 我们启动这个lo接口,并查看被分配了IP:127.0.0.1。
[root@testerfans ~]# ip link set lo up
[root@testerfans ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
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
- 接下来我们ping这个IP可以正常ping通。
[root@testerfans ~]# ping 127.0.0.1 -c 3
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.037 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.020 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.032 ms
--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.020/0.029/0.037/0.009 ms
- 回收我们创建的mynet,确认被删除。
[root@testerfans ~]# ip netns delete mynet
[root@testerfans ~]# ls /var/run/netns
两个netns间通信
network namespace 之间是相互隔离的,我们可以使用 veth 设备把两个 network namespace 连接起来进行通信。veth 设备是虚拟的以太网设备。它们可以充当 network namespace 之间的通道,也可以作为独立的网络设备使用。veth 设备总是被成对的创建,并且这一对设备总是连接在一起的,所以一般把称之为 veth pair。需要注意的是,veth pair 无法单独存在,删除其中一个,另一个也会自动消失。接下来的示例我们就演示如何使用 veth pair 在两个 network namespace 直接通信。示例中我们使用 ip link 命令来创建和管理 veth pair。
- 创建两个命名netns,分别为netns1和netns2。
[root@testerfans ~]# readlink /proc/$$/ns/net
net:[4026531956]
[root@testerfans ~]# ip netns add netns1 && ip netns add netns2
[root@testerfans ~]# ip netns exec netns1 bash
[root@testerfans ~]# readlink /proc/$$/ns/net
net:[4026532349]
[root@testerfans ~]# ip netns exec netns2 bash
[root@testerfans ~]# readlink /proc/$$/ns/net
net:[4026532406]
- 创建一对命名的 veth 设备,分别为veth1和veth2。通过
ip link ls
查看生成了veth1和veth2两块网卡(返回结果中编号16、17)。
[root@testerfans ~]# ip link add veth1 type veth peer name veth2 && ip link ls
16: veth2@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 66:6d:31:22:94:de brd ff:ff:ff:ff:ff:ff
17: veth1@veth2: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
link/ether 76:64:e7:59:6c:d3 brd ff:ff:ff:ff:ff:ff
- 将veth1和veth2分别加入到netns1和netns2中,我们看到netns1和netns2分别多了veth1和veth2设备。
[root@testerfans ~]# ip link set veth1 netns netns1 && ip link set veth2 netns netns2
[root@testerfans ~]# ip netns exec netns1 ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
17: veth1@if16: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 76:64:e7:59:6c:d3 brd ff:ff:ff:ff:ff:ff link-netnsid 1
[root@testerfans netns]# ip netns exec netns2 ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
16: veth2@if17: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 66:6d:31:22:94:de brd ff:ff:ff:ff:ff:ff link-netnsid 0
- 将veth1和veth2加入到netns1和netns2中后,我们在当前主机不会再看到这两块网络设备。
[root@testerfans ~]# ip link ls
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 mq state UP mode DEFAULT group default qlen 1000
link/ether 52:54:00:b5:c9:bc brd ff:ff:ff:ff:ff:ff
- 我们分别给veth1和veth2增IP,IP地址为192.168.1.1/24和192.168.1.2/24。
[root@testerfans ~]# ip netns exec netns1 ip link set veth1 up
[root@testerfans ~]# ip netns exec netns1 ip addr add 192.168.1.1/24 dev veth1
[root@testerfans ~]# ip netns exec netns1 ip route
192.168.1.0/24 dev veth1 proto kernel scope link src 192.168.1.1
[root@testerfans ~]# ip netns exec netns2 ip link set veth2 up
[root@testerfans ~]# ip netns exec netns2 ip addr add 192.168.1.2/24 dev veth2
[root@testerfans ~]# ip netns exec netns2 ip route
192.168.1.0/24 dev veth2 proto kernel scope link src 192.168.1.2
- 进入到netns1网络命名空间内ping netns2可以ping通,说明两个网络命名空间可以进行通信了。
[root@testerfans ~]# ip netns exec netns1 ping -c 3 192.168.1.2
PING 192.168.1.2 (192.168.1.2) 56(84) bytes of data.
64 bytes from 192.168.1.2: icmp_seq=1 ttl=64 time=0.055 ms
64 bytes from 192.168.1.2: icmp_seq=2 ttl=64 time=0.035 ms
64 bytes from 192.168.1.2: icmp_seq=3 ttl=64 time=0.038 ms
--- 192.168.1.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.035/0.042/0.055/0.011 ms
- 回收我们创建的两个netns和veth pair,确认都被删除。
[root@testerfans ~]# ip netns delete netns1 && ip netns delete netns2
[root@testerfans ~]# ls /var/run/netns
[root@testerfans ~]# ip link delete veth1 && ip link delete veth2
netns通过bridge通信
虽然 veth pair 可以实现两个 network namespace 之间的通信,但是当需要在多个 network namespace 之间通信的时候,光靠 veth pair 就不行了。我们可以使用 Linux 提供的虚拟交换机,来完成这样的功能。下面的示例演示如何通过虚拟交换机(这里就是一个虚拟网桥)连接多个 network namespace。
- 创建一个名称为mybridge的网桥,查看网桥为up状态。
[root@testerfans ~]# ip link add mybridge type bridge
[root@testerfans ~]# ip link set mybridge up
[root@testerfans ~]# ip addr
18: mybridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
link/ether ae:f0:10:89:bd:8f brd ff:ff:ff:ff:ff:ff
inet6 fe80::acf0:10ff:fe89:bd8f/64 scope link
valid_lft forever preferred_lft forever
- 创建一个叫netns1的命名空间。
[root@testerfans ~]# ip netns add netns1
- 创建一对veth分别为veth1和veth1p。
[root@testerfans ~]# ip link add veth1 type veth peer name veth1p
- 将veth1p加入到netns1中,并将名称修改成eth0。
[root@testerfans ~]# ip link set dev veth1p netns netns1
[root@testerfans ~]# ip netns exec netns1 ip link set dev veth1p name eth0
- 分配IP并启动eth0,查看设备状态。
[root@testerfans ~]# ip netns exec netns1 ip addr add 192.168.1.1/24 dev eth0
[root@testerfans ~]# ip netns exec netns1 ip link set dev eth0 up
[root@testerfans ~]# ip netns exec netns1 ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
19: eth0@if20: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state LOWERLAYERDOWN group default qlen 1000
link/ether b2:28:d2:70:ec:e2 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.1.1/24 scope global eth0
valid_lft forever preferred_lft forever
- 将veth1绑定到我们创建的mybridge网桥。
[root@testerfans ~]# ip link set dev veth1 master mybridge
[root@testerfans ~]# ip link set dev veth1 up
- 重复执行上述步骤,分别创建netns2、netns3和对应的网络设备并绑定到网桥。
[root@testerfans ~]# ip netns add netns2
[root@testerfans ~]# ip link add veth2 type veth peer name veth2p
[root@testerfans ~]# ip link set dev veth2p netns netns2
[root@testerfans ~]# ip netns exec netns2 ip link set dev veth2p name eth0
[root@testerfans ~]# ip netns exec netns2 ip addr add 192.168.1.2/24 dev eth0
[root@testerfans ~]# ip netns exec netns2 ip link set dev eth0 up
[root@testerfans ~]# ip link set dev veth2 master mybridge
[root@testerfans ~]# ip link set dev veth2 up
[root@testerfans ~]# ip netns add netns3
[root@testerfans ~]# ip link add veth3 type veth peer name veth3p
[root@testerfans ~]# ip link set dev veth3p netns netns3
[root@testerfans ~]# ip netns exec netns3 ip link set dev veth3p name eth0
[root@testerfans ~]# ip netns exec netns3 ip addr add 192.168.1.3/24 dev eth0
[root@testerfans ~]# ip netns exec netns3 ip link set dev eth0 up
[root@testerfans ~]# ip link set dev veth3 master mybridge
[root@testerfans ~]# ip link set dev veth3 up
- 停用网桥并给网桥分配IP,然后再启动。
[root@testerfans ~]# ip link set dev mybridge down
[root@testerfans ~]# ip addr add 192.168.1.0/24 dev mybridge
[root@testerfans ~]# ip link set dev mybridge up
[root@testerfans ~]# ip addr
18: mybridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 26:69:b9:5e:84:6d brd ff:ff:ff:ff:ff:ff
inet 192.168.1.4/24 scope global mybridge
valid_lft forever preferred_lft forever
inet6 fe80::2469:b9ff:fe5e:846d/64 scope link
valid_lft forever preferred_lft forever
- 通过bridge link查看网桥信息。
[root@testerfans ~]# bridge link
20: veth1 state UP @(null): <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master mybridge state forwarding priority 32 cost 2
22: veth2 state UP @(null): <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master mybridge state forwarding priority 32 cost 2
24: veth3 state UP @(null): <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master mybridge state forwarding priority 32 cost 2
- 在三个网络命名空间内互ping,检查网络是否联通。
[root@testerfans ~]# ip netns exec netns1 ping -c 3 192.168.1.3
PING 192.168.1.3 (192.168.1.3) 56(84) bytes of data.
--- 192.168.1.3 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 1999ms
从上面结果上看我们发现在netns1中ping netns3,网络并未联通这是为什么呢?
iptables设置
产生无法ping通的原因是演示的CentOS系统为bridge开启了iptables功能,所有经过br0的数据包都要受iptables里面规则的限制。并且之前安装了docker修改了iptable的FORWARD策略导致。
docker为了安全性,将iptables里面filter表的FORWARD链的默认策略设置成了drop,于是所有不符合docker规则的数据包都不会被forward,导我们不同netns之间无法ping通。
我们通过如下命令在iptable内增加一条mybridge的转发规则,然后再ping一下,此时我们发现可以ping通了。
[root@testerfans ~]# iptables -A FORWARD -i mybridge -j ACCEPT
[root@testerfans ~]# iptables -nvL
Chain INPUT (policy ACCEPT 260 packets, 987K bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy DROP 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all -- mybridge * 0.0.0.0/0 0.0.0.0/0
[root@testerfans ~]# ip netns exec netns1 ping -c 3 192.168.1.3
PING 192.168.1.3 (192.168.1.3) 56(84) bytes of data.
64 bytes from 192.168.1.3: icmp_seq=1 ttl=64 time=0.047 ms
64 bytes from 192.168.1.3: icmp_seq=2 ttl=64 time=0.045 ms
64 bytes from 192.168.1.3: icmp_seq=3 ttl=64 time=0.045 ms
--- 192.168.1.3 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.045/0.045/0.047/0.007 ms
之后我又在一台裸机(没有安装docker)上进行了同样的测试,发现不需要进行iptable设置即可互通。
- 回收演示数据。
[root@testerfans ~]# ip netns delete netns1
[root@testerfans ~]# ip netns delete netns2
[root@testerfans ~]# ip netns delete netns3
[root@testerfans ~]# ip link delete mybridge
总结
本文通过ip netns命令演示了network namespace的创建、通过veth pair实现两个netns通信和通过bridge实现多netns间的网络通信,这些试验均是为了加深对netns理解。并且容器间通过bridge进行通信也是docker容器间通信的实现原理。
评论区