在 Linux 中很多的资源都是全局的。比如进程有全局的进程 ID,网络也有全局的路由表。当一台 Linux 上跑多个进程的时候,如果我们要使用不同的路由策略,这些进程可能会冲突,那就需要将这个进程放在一个独立的 namespace 里面,这样就可以独立配置网络了。
Linux 中 namespace 的作用就是用来隔离内核资源,共有 6 种不同类型的 namespace:
- user namespace 隔离用户权限
- mount namespace 修改进程的文件系统视图(chroot 重新挂载根节点)
- pid namespace 保证了容器的 init 进程是以 1 号进程来启动的
- network namespace 网络虚拟化
- uts namespace 隔离了 hostname 和 domain
- ipc namespace 进程间通信
network namespace 技术是实现网络虚拟化的重要功能,它可以创建多个隔离的网络空间,这些网络空间都有各自独立的私有的网络栈信息,包括:网卡(Network Interface)、回环设备(Loopback Device)、路由表(Routing Table)和 iptables 规则。
关于 network namespace 的增删改查功能已经集成到 Linux 工具的 netns 命令中,下面通过一个 demo 来演示一下 Linux Network Namespace 的功能。
[root@ ~]# ip netns add namespace-1
[root@ ~]# ip netns add namespace-2
[root@ ~]# ip netns exec namespace-1 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
创建了两个网络空间,默认情况下都只会有一个回环接口,处于 DOWN 状态。现在要解决一下四个问题:
仅有一个本地回环设备是无法与宿主机或外界通信的,如果想与外界通信,就需要有网卡,这里扮演网卡角色的就是 Linux 中的虚拟设备 veth pair 对。
Veth Pair 设备的特点是:它被创建出来后,总是以两张虚拟网卡(Veth Peer)的形式成对出现的。并且,从其中一个网卡发出的数据包,可以直接出现在与它对应的另一张“网卡”上,哪怕这两个“网卡”在不同的 Network Namespace 里。
当每个 veth 设备在不同的 Network Namespace 的时候,Namespace 之间就可以用这对 veth 设备来进行网络通讯了,这就使得 Veth Pair 常常被用作连接不同 Network Namespace 的“网线”。
下面通过命令创建 veth pair 对,创建成功后可以看到,他们在宿主机上就表现为两张网卡,然后分别把两端移动到两个 network namespace 中,并为虚拟网卡配置 IP。此时在两个 network namespace 中就可以互相 ping 通。
[root@ ~]# ip link add veth0 type veth peer name veth1
[root@ ~]# ip addr
44: veth1@veth0: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 22:f7:2f:62:e6:5a brd ff:ff:ff:ff:ff:ff
45: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN group default qlen 1000
link/ether 5e:e4:9f:67:f0:d2 brd ff:ff:ff:ff:ff:ff
[root@ ~]# ip link set veth0 netns namespace-1
[root@ ~]# ip link set veth1 netns namespace-2
[root@iZ2zece2l8yr2f8qhrnr3lZ ~]# ip netns exec namespace-1 ifconfig veth0 172.14.0.2/24 up
[root@iZ2zece2l8yr2f8qhrnr3lZ ~]# ip netns exec namespace-2 ifconfig veth1 172.14.0.3/24 up
虽然现在这两块虚拟网卡可以互相通信了,但是仍然是不能连接外部网络的,并且和宿主机物理网卡也是不能通信的。要想通信有一下几种解决方式:
- 虚拟网桥(交换机)
- NAT 网络地址转换
下面介绍第一种,这也就是容器的主机内组网模型:veth pair + bridge 的模式。
Linux Bridge 网桥是一种软件配置,用于连结两个或更多个不同网段。它的行为就像是一台虚拟的二层网络交换机,工作于透明模式(即其他机器不必关注网桥的存在与否)。任意的真实物理设备(例如 eth0)和虚拟设备(例如 veth tap0)都可以连接到网桥。
下面演示下 bridge 网桥的作用,命令用的是 bridge-utils 软件包里的 brctl 工具来管理网桥。首先创建两个 veth pair 对,并分别将各自的一段移动到 network namespace 中,另一端插入到网桥上。
当成功创建一个网桥 test-bridge 并为其配置好 IP 的时候(ip 要在同一个网段),会默认在宿主机上维护一条此网段的路由规则(直连规则,二层网络通信,ARP 广播)。可以看到任何目的地到这个网段 (192.168.1.0/24) 的请求都会经过网桥设备转发,也就是说在宿主机上可以 ping 通。
[root@ ~]# route
Destination Gateway Genmask Flags Metric Ref Use Iface
172.14.0.0 0.0.0.0 255.255.255.0 U 0 0 0 test-bridge
由于网络空间中的路由表没有默认网关,因此无法从 172.14 范围之外到达其他计算机。要解决这个问题,需要给网络名称空间一个默认的网关路由。
[root@]# ip netns exec namespace-1 route add default gw 172.14.0.1
[root@]# ip netns exec namespace-2 route add default gw 172.14.0.1
[root@ ~]# ip netns exec namespace-1 route
Destination Gateway Genmask Flags Metric Ref Use Iface
default gateway 0.0.0.0 UG 0 0 0 veth1
172.14.0.0 0.0.0.0 255.255.255.0 U 0 0 0 veth1
梳理下上面所形成的一个网络栈,将两个 network namespace 和 bridge 组成了一个子网,bridge 上的 IP 就是这个子网的网关 IP。network namespace 中的数据包通过 veth 设备到达 bridge,bridge 中的数据包要把数据包转发到 eth0 上,这里需要做两个网络设备接口之间的数据包转发,用到了 Linux 协议栈里的一个常用参数 ip_forward。
sysctl -w net.ipv4.ip_forward=1
# filter(显式地允许 test-bridge 和 eth0 之间的包转发)
:FORWARD DROP [0:0]
-A FORWARD -i test-bridge -o eth0 -j ACCEPT
-A FORWARD -i eth0 -o test-bridge -j ACCEPT
此时,通过一些虚拟网络设备:Veth Pair 虚拟网卡、Bridge 网桥、Routing Table 路由规则,解决了 network namespace 之间的通信,以及 network namespace 与 宿主机之间的通信。但是现在 network namespace 还无法与局域网、外部网络通信。
与外部网络通信,就需要用到 NAT 网络地址转换技术,由于网络空间的 IP 地址都是私有的,经过物理网络时是不能被识别的,这个时候就需要对出去的包做地址伪装,对回来的包再做目标地址转换。
所有从 namespace 内部发出来的包,都要做地址伪装,将源 IP 地址,转换为物理网卡的 IP 地址。如果有多个 namespace,所有的 namespace 共享一个宿主机的 IP 地址,但是在 conntrack 表中,记录下这个出去的连接。
当服务器返回结果到达宿主机时,会根据 conntrack 表中的规则,取出原来的私网 IP,通过 DNAT 将地址转换为私网 IP 地址,通过网桥 bridge 实现对 network namespace 的访问。
iptables 是最常用的一种配置工具。其在 Docker、Kubernetes、Istio 网络中应用甚广,像 Docker 容器的端口映射,Kubernetes Service 的工作模式,Istio 的流量接管等都是通过 iptables 来实现的。
网友评论