引言
Namespaces(命名空间)和cgroups是两种主要的内核技术,他们是容器化技术的基石。简而言之,cgroup是一种计量和限制机制,它能控制你可以使用多少系统资源(CPU、内存)。另一方面,Namespaces限制了你所看到的内容。得益于Namespaces,进程有其独立的系统资源视图。
Linux内核提供了6种类型的Namespaces:pid、net、mnt、uts、ipc、user。例如,pid命名空间的进程只能看到同一个命名空间的进程。使用mnt命名空间,可以将进程附加到其自己的文件系统(就像chroot)。在本文中我们仅关注网络命名空间。
网络命名空间为命名空间内的所有进程提供了全新的网络堆栈。其中包括网络接口,路由表和iptables规则。
网络命名空间
从系统的角度来看,当通过clone()系统调用创建新进程时,传递标志CLONE_NEWNET将在新进程中创建一个全新的网络命名空间。从用户的角度来看,我们仅使用工具ip(软件包为iproute2)来创建新的持久网络命名空间:
$ ip netns add ns1
此命令将创建一个名为ns1的新网络命名空间。创建命名空间后,ip命令会在/var/run/netns下为其添加绑定挂载点。这样,即使没有附加任何进程,命名空间也可以保留。列出系统中可用的命名空间:
$ ls /var/run/netns
ns1
或通过ip
:
$ ip netns
ns1
如前所述,网络命名空间包含其自己的网络资源:接口,路由表等。让我们向ns1添加回环接口:
1 $ ip netns exec ns1 ip link set dev lo up
2 $ ip netns exec ns1 ping 127.0.0.1
3 PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
4 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.115 ms
- 第1行在网络命名空间ns1内部启用回环接口。
- 第2行在网络命名空间内部执行命令
ping 127.0.0.1
。
启用回环接口的另一种语法可以是:
$ ip netns exec ns1 ifconfig lo up
废弃旧的但更熟悉ifconfig
,route
等命令,我倾向于使用命令ip
,因为它已经成为Linux的首选网络工具。请注意,ip需要root权限,因此请以root身份或前置sudo运行。
网络命名空间也有自己的路由表:
$ ip netns exec ns1 ip route show
由于我们尚未添加任何路由表规则,因此该命令什么都不返回。一般而言,在网络命名空间中运行的任何命令都由以下内容开头:
$ ip netns exec <network-namespace>
示例
网络命名空间的后果之一是一次只能将一个接口分配给命名空间。如果root命名空间拥有eth0,它提供对外部世界的访问,则只有root命名空间内的程序才能访问Internet。解决方案是通过veth对与root命名空间进行通信。一对veth像跳线一样工作,连接两侧。它由两个虚拟接口组成,其中一个虚拟接口分配给root网络命名空间,另一个虚拟接口位于网络命名空间内。相应地设置其IP地址和路由规则,再在主机端启用NAT,就在网络命名空间内访问Internet。
另外,我觉得我现在需要澄清。我读过几篇有关网络命名空间的文章,其中物理设备接口只能存在于root命名空间中。至少我当前的内核(Linux 3.13)不是这种情况。我可以将eth0分配给除根目录以外的命名空间,并且在正确设置它时,可以从命名空间访问Internet。但是,由于一个网络接口一次只能存在于一个命名空间的限制,很多时候,仍然需要通过veth对连接网络命名空间。
首先,让我们创建一个名为ns1的网络命名空间。
# 移除命名空间.
ip netns del ns1 &>/dev/null
# 创建命名空间
ip netns add ns1
接下来,创建一个veth-pair。接口v-eth1保留在root网络命名空间中,而其对等方v-peer1将移至ns1命名空间。
# 创建veth-pair,v-eth1加入root命名空间
ip link add v-eth1 type veth peer name v-peer1
# 将v-peer1加入ns1
ip link set v-peer1 netns ns1
接下来,为两个接口设置IPv4地址并启动它们。
# 设置v-eth1的ip
ip addr add 10.200.1.1/24 dev v-eth1
ip link set v-eth1 up
# 设置v-peer1的ip
ip netns exec ns1 ip addr add 10.200.1.2/24 dev v-peer1
ip netns exec ns1 ip link set v-peer1 up
ip netns exec ns1 ip link set lo up
另外,在ns1内部启用回环接口。
现在必须使离开ns1的所有外部流量都通过v-eth1。
ip netns exec ns1 ip route add default via 10.200.1.1
但是,这还不够。与共享其Internet连接的任何主机一样,有必要在主机中启用IPv4转发并启用伪装。
# 共享主机与NS间的internet访问
# 开启IP转发
echo 1 > /proc/sys/net/ipv4/ip_forward
# 清空转发规则,默认策略policy DROP
iptables -P FORWARD DROP
iptables -F FORWARD
# 清空nat表
iptables -t nat -F
# 开启网段10.200.1.0/24的IP伪装(masquerading)
iptables -t nat -A POSTROUTING -s 10.200.1.0/24 -o eth0 -j MASQUERADE
# 允许eth0与v-eth1之前的转发
iptables -A FORWARD -i eth0 -o v-eth1 -j ACCEPT
iptables -A FORWARD -o eth0 -i v-eth1 -j ACCEPT
如果一切顺利,则可以从ns1 ping外部主机。
$ ip netns exec ns1 ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=50 time=48.5 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=50 time=50.8 ms
设置后,ns1内的路由表如下所示:
$ ip netns exec ns1 ip route sh
default via 10.200.1.1 dev v-peer1
10.200.1.0/24 dev v-peer1 proto kernel scope link src 10.200.1.2
除了加上ip netns exec
这样的前缀,来指定命令执行的命名空间。还可以直接将运行的bash shell附加到网络命名空间:
$ ip netns exec ns1 /bin/bash --rcfile <(echo "PS1=\"namespace ns1> \"")
namespace ns1> ping www.google.com
PING www.google.com (178.60.128.38) 56(84) bytes of data.
64 bytes from cache.google.com (178.60.128.38): icmp_seq=1 ttl=58 time=17.6 ms
输入exit退出bash进程并离开网络命名空间。
最后,可以执行如下命令,清理现场
# 恢复iptable规则,清空filter/nat表
iptables -P FORWARD ACCEPT
iptables -t nat -F
iptables -F
# 删除命名空间,关联的veth pair随之删除
ip netns del ns1
总结
网络命名空间以及Linux内核提供的其他容器化技术是一种用于资源隔离的轻量级机制。附加到网络命名空间的进程可以看到自己的网络堆栈,而不会干扰系统的其余网络堆栈。
网络命名空间也易于使用。可以使用虚拟机建立类似的网络级别隔离。但是,就系统资源和建立此类环境的时间投入而言,这似乎是一种昂贵得多的解决方案。如果只需要网络级别的进程隔离,则绝对要考虑网络命名空间。