Linux内核提供namespace隔离功能用于实现虚拟化技术。在同一个namespace下的进程组它们可以相互感知,但是对namespace之外的事情一无所知。就好比一个监狱的犯人按组被囚禁在不同的牢房,一个牢房就是一个namespace,一个牢房的囚犯可以相互沟通,但是他们无法得知其他牢房发生的事情,无法与其他囚牢的犯人直接沟通。 linux操作系统将所有与namespace相关的接口都通过文件系统路径 /proc/$pid/ns 暴露给用户。客户端可以通过读取该路径下的文件内容获取与namespace相关的数据。
linux提供了多种namespace隔离技术,例如:
- pid namespace隔离
- network namespace
- mount namespace
- IPC namespace
本文尝试解释docker容器基础原理之一的网络隔离技术: network namespace,它是linux实现网络虚拟化的重要手段。我们假设读者对linux操作系统有基本的理解,能理解一些最基本的linux的命令行,并且假设读者对linux的ip命令有一定了解,如果读者不熟悉或者没使用过ip命令,可通过其他途径去了解,本文不对ip命令做单独的讲解。 本文内容分为三部分:
- 第一部分讲解 ip命令管理linux的network namespace;
- 第二部分讲解在同一台宿主机上不同的network namespace之间如何进行通信,并演示一个 3个network namespace + 1个bridge 构建的通信模型;
- 通过第二部分的模型,来对同宿主机docker容器通信的基本模型做一个总结。
在一个linux操作系统中,当系统开机启动后,其起码会有一个默认的network namespace。 用户可以新建一个network namespace和宿主机默认的network namespace隔离。每个network namespace都有自己独立的网络栈,其内部网络资源外部不可见,不同的netns之间内部ip可以重复。类似于我们使用的局域网,不同的内网集群ip是可以重复的。如果启动一个进程的过程不指定自己的network namespace,那么子进程会继承父进程的network-namespace,因此一般来说我们的进程都是继承init进程所在的network-namespace,所以我们常规手段启动的进程之间是可以直接网络通信的。
可通过命令 readlink /proc/self/ns/net 来查询当前进程所在的net ns:
[root@jump1 ~]# readlink /proc/self/ns/net
net:[4026531956]
1. netns 管理
可以通过linux强大的网络命令工具 ip管理netns:
ip netns add $net_ns_name 创建一个新的namespace并通过 ip netns ls 查询当前进程创建的netns 集合:
[root@k8s-worker4 ~]# ip netns add net1
[root@k8s-worker4 ~]# ip netns ls
net1
查看宿主机上的netns,并进入到nstns net1 查看netns 文件,可以知道两个netns分别指向不同的文件,它们分别表示不同的两个namespace:
[root@k8s-worker4 ~]# readlink /proc/self/ns/net
net:[4026531956]
[root@k8s-worker4 ~]# ip netns exec net1 readlink /proc/self/ns/net
net:[4026532569]
ip netns exec $netns_name command_line 表示进入到一个指定的network namespace,然后执行一段shell命令。 也可以先进入到指定的network namespace,然后再执行其他任务:
- ip netns exec net1 /bin/bash
- 查看当前的network namespace id:readlink /proc/self/ns/net 或者查看当前的ip route规则: ip route
2. netns 网络通讯
不同的netns之间的网络是隔离的,通常,netns之间进行网络通讯可以通过虚拟网络设备veth来完成。veth-pair设备总是会成对出现,如果想了解linux的vath pair设备是什么,可自行google或者 参考这里 ,本文中我们只需要知道veth pair设备有以下特点:
-
veth-pair设备一边相互之间连接,当其中一个设备收到数据,会将数据总是会被导流到另外一端的veth设备上;
-
另外一端连接着网络协议栈(图片来源于网络,侵删);
从上面对network namespace的介绍,我们可以知道network namespace是有自己独立的网络协议栈的,因此,veth设备两端就可以分别接在不同的network namespace上,从而为两个不用的network namespace提供了网络沟通的桥梁。
- 两个netns之间的通讯,可以直接用一对veth设备,分别将veth的一端放入到两个不同的netns
其中:
- veth0 设备的一端和net1的网络协议栈相连,另一端则和veth1相连;
- veth1 设备的一端和net2的网络协议栈相连,另一端则和veth0相连;
- N个netns之间的通讯,有以下方式
-
- 理论上只要在不同的netns集合两两之间创建veth设备,这种方式繁琐,因为需要创建 n*(n-1)/2 对veth设备,因此现实中一般都不会这么操作。
-
- 借助于网桥这样的设备。创建一个虚拟网桥,和N对veth设备,将veth的一边放入到netns中,另外一端桥接到网桥上,就可以实现N个netns之间的相互通讯。这是常用的方法,docker在同宿主机不同容器之间的通信就是通过这种方式,同一个宿主机上的容器都通过veth设备桥接到docker0网桥上。
-
3. 案例演示
下面演示一个案例,在linux上实现三个不同netns之间进行网络通讯。 基本的网络结构如下:
- 设置宿主机支持数据包转发功能
echo 1 > /proc/sys/net/ipv4/ip_forward
所谓的数据包转发即当主机拥有多于一块的网卡时,其中一块收到数据包,根据数据包的目的ip地址将数据包发往本机另一块网卡,该网卡根据路由表继续发送数据包。这通常是路由器所要实现的功能。要让Linux系统具有路由转发功能,需要配置一个Linux的内核参数net.ipv4.ip_forward。这个参数指定了Linux系统当前对路由转发功能的支持情况;其值为0时表示禁止进行IP转发;如果是1,则说明IP转发功能已经打开。
本节中会在一个宿主机上创建多个linux虚拟网络设备,并且为其配置ip地址,因此需要开启linux的数据包转发功能。
- 创建三个network namespace
[root@master ~]# ip netns add net1
[root@master ~]# ip netns add net2
[root@master ~]# ip netns add net3
[root@master ~]# ip netns ls
net3
net2
net1
- 创建网桥 bri0,并添加 ip 10.1.1.0, 启动网桥bri0
# 创建网桥 bri0
ip link add bri0 type bridge
# 添加ip ,这个命令表示,给网桥 bri0配置ip地址 10.1.1.0 ,并且给宿主机配置一个 ip route规则:凡是目的地ip地址为 10.1.1.0/24 网段的数据包,都应该被转发到网桥 bri0上
ip addr add 10.1.1.0/24 dev bri0
# 查看ip route可以发现下面的一条规则:
ip route ls
10.1.1.0/24 dev bri0 proto kernel scope link src 10.1.1.0
# 启动网桥
ip link set dev bri0 up
# 查看 bri0 网桥设备
[root@master ~]# ifconfig
bri0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.1.1.0 netmask 255.255.255.0 broadcast 0.0.0.0
inet6 fe80::28f6:e2ff:fee5:f553 prefixlen 64 scopeid 0x20<link>
ether 2a:f6:e2:e5:f5:53 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 12 bytes 816 (816.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
- 创建3对veth pair设备
# 创建 veth设备
ip link add veth0 type veth peer name br-veth0
ip link add veth1 type veth peer name br-veth1
ip link add veth2 type veth peer name br-veth2
- 分别为 ( veth0,br-veth0 ) | ( veth1 , br-veth1 ) | ( veth2 , br-veth2 ) , 3对veth 设备的一头放入到分别放进到三个创建好的network namespace中,然后分别进入到3个network namespace中配置ip地址, veth-pair的另一头则接到网桥 bri0 上,最后启动3对veth-pair设备
# 将veth的一头分别放入到3个network namespace中
ip link set veth0 netns net1
ip link set veth1 netns net2
ip link set veth2 netns net3
# 另一头的veth设备接入到网桥 bri0上
ip link set br-veth0 master bri0
ip link set br-veth1 master bri0
ip link set br-veth2 master bri0
#创建ip地址, 同时分别给每个namespace上配置了ip route规则:凡是目的ip地址为 10.1.1.0/24 网段的数据都应该路由到 veth0/veth1/veth2 设备上。
ip netns exec net1 ip addr add 10.1.1.2/24 dev veth0
ip netns exec net2 ip addr add 10.1.1.3/24 dev veth1
ip netns exec net3 ip addr add 10.1.1.4/24 dev veth2
# 跳进到新建的network namespace,启动veth设备
ip netns exec net1 ip link set dev veth0 up
ip netns exec net2 ip link set dev veth1 up
ip netns exec net3 ip link set dev veth2 up
# 分别启动三个network namespace下的网络回环设备
ip netns exec net1 ip link set lo up
ip netns exec net2 ip link set lo up
ip netns exec net3 ip link set lo up
# 查看三个namespace下的 route规则
ip netns exec net1 ip route ls
//结果: 10.1.1.2/24 dev veth0 proto kernel scope link src 10.1.1.2
ip netns exec net2 ip route ls
//结果:10.1.1.3/24 dev veth1 proto kernel scope link src 10.1.1.3
ip netns exec net3 ip route ls
//结果:10.1.1.4/24 dev veth2 proto kernel scope link src 10.1.1.4
# 启动veth设备
ip link set dev br-veth0 up
ip link set dev br-veth1 up
ip link set dev br-veth2 up
## 查看宿主机 route规则
ip route ls
- 测试验证三个network namespace的联通性
# 在net1 下尝试ping net2和net3里面的ip地址
ip netns exec net1 ping 10.1.1.3
ip netns exec net1 ping 10.1.1.4
- 测试宿主机到network namespace的连通性
ping 10.1.1.2
ping 10.1.1.3
ping 10.1.1.4
4. 同宿主机下docker容器网络通信模型
通过上面的演示的案例,我们将这个模型映射到一个宿主机上的多个docker容器之间的通信,并描述同一宿主机上的多个docker容器通信模型。
-
- 安装docker引擎的时候,会创建一个docker0 网桥,其默认的ip为:172.17.0.1。(可在宿主机上通过
ifconfig命令查看docker0网桥设备,通过ip route ls获取宿主机的ip route列表)
- 安装docker引擎的时候,会创建一个docker0 网桥,其默认的ip为:172.17.0.1。(可在宿主机上通过
-
- 每创建docker容器都有自己的network namespace(不考虑使用宿主机网络的情况);
-
- 每创建一个docker容器都会创建一对veth设备,其中一端被接入到docker 容器内,另外一端则被接到docker0网桥上了。
-
- 容器之间的通信都通过veth转发到docker0网桥上,最后被转发到目的ip地址的veth设备上。
以上,就是宿主机下docker容器通信模型,通过对上面的案例一步一步进行操作的话,整个通信过程都是比较容易理解的。