一、先谈谈进程
在正式介绍Namespace之前,先介绍下进程,因为容器本质上是进程,但是在介绍进程之前,先理清下“程序”和“进程”的关系,这是IT从业人员在日常工作中经常碰到的两个词汇,举个通俗点的例子帮助大家理解,“程序”可以看成是一张机械图,图上的内容都是手工画上去的,相当于是计算机的输入,在机械图未正式设计出产品的时候,它是静态的,而当工程师按照机械图正式设计各个零部件、组合、啮合到最后产品成型的整个动态过程,可看成是一个进程,因此进程可认为是程序运行起来后的计算机执行环境的总和,是静态程序的具体实现。
二、容器的namespace
在上节中介绍了什么是进程,可通过ps命名查看当前宿主机正在运行的进程,如下图所示
[root@k8s-master James]# ps PID TTY TIME CMD 101614 pts/1 00:00:00 su 101657 pts/1 00:00:00 bash 103878 pts/1 00:00:00 ps
容器本质上对于Linux操作系统来说,和上述进程一样,但是会在进程上加入了很多namespace来实现进程、挂载点、网络、用户信息之间的隔离,这样容器看上去就像一个沙箱,在沙箱内部只能看到和操作限定namespace下的系统资源,以PID namespace为例,我们先创建1个容器
[root@k8s-master James]# docker run -it -d busybox /bin/sh
01a0fd62d2110e54b0c3635b2897e7c18e6b78f026fa57b4214d7662dd3b38ba
[root@k8s-master James]# docker exec -it 01a0fd62d2110e54b0c3635b2897e7c18e6b78f026fa57b4214d7662dd3b38ba /bin/sh
/ # ps
PID USER TIME COMMAND
1 root 0:00 /bin/sh
6 root 0:00 /bin/sh
11 root 0:00 ps
这条命名的意思是我想启动一个容器执行/bin/bash命令,并分配一个伪终端的交互窗口和容器进行交互,docker run是创建和启动容器的意思,-it表示就是分配一个伪终端的输入和输出交互窗口,busybox是镜像,/bin/bash是操作程序。当创建成功后,输入ps可查看容器中正在运行的进程,可以看到有2个进程,PID代表进程的唯一编号,可以看到1号PID的操作程序为/bin/bash,之前谈到容器也是一种进程,回到宿主机查看下此容器的进程
[root@k8s-master James]# docker container top 01a0fd62d2110e54b0c3635b2897e7c18e6b78f026fa57b4214d7662dd3b38ba UID PID PPID C STIME TTY TIME CMD root 24691 24668 0 18:34 pts/0 00:00:00 /bin/sh
其PID为24691了,此PID在容器中不存在,也就是说容器中只能看到自己程序执行后的进程,而看不到其他容器和宿主机上的进程,且容器里面的进程编号PID也做了障眼法般的处理,与宿主机上的PID不一致,而实现这个技术的就是PID namespace,而容器类似的还有NET、IPC、MNT、UTS、USER的namespace,其对应隔离的内容如下表所示:
namespace | 隔离的内容 |
---|---|
PID | 进程 |
IPC | 信号量、消息队列和共享内容 |
UTS | 主机名、域名 |
NET | 网络设备、网络栈、端口 |
MNT | 文件系统 |
User | 用户和用户组 |
网络相对比较复杂,我们再详细深入看下,从上表可以看出不同的网络命名空间可以隔离网络设备、网络栈、端口等,为了达到这个目标,LINUX系统就需要支持虚拟化网络协议栈的多个实例,且这些独立的协议栈可处于不同的网络命名空间来进行隔离,达到彼此无法进行通信隔离的效果,如果想要不同的网络命名空间设备进行通信,应如何操作呢?答案是:veth设备对,什么是veth设备对,你可以认为是一根管道,一端连接一个网络命名空间的协议栈,另外一端连接另外一个网络命名空间的协议栈来实现通信,那如何建立veth设备对呢?接下来一起操作下
在root用户下,先创建一个网络命名空间
[root@k8s-master zhanglei]# ip netns add net_test1 [root@k8s-master zhanglei]# ip netns list net_test1
再创建一个设备对veth:
[root@k8s-master zhanglei]# ip link add veth0 type veth peer name veth1 [root@k8s-master zhanglei]# ip link show 60: veth0@veth1: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
可以看到veth0和veth1的设备对已经生成,veth的设备支持在不同的网络命名空间进行转移,将veth0设备住转移到net_test1命名空间
[root@k8s-master zhanglei]# ip link set veth0 netns net_test1
此时在宿主机上ip link show,因不同的网络命名空间设备是隔离的,且veth0设备已经被移动到net_test1网络命名空间,因此将无法在宿主机网络命名空间再次找到veth0
[root@k8s-master zhanglei]# ip netns exec net_test1 ip link show 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ipip 0.0.0.0 brd 0.0.0.0 60: veth0@if59: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 26:c0:29:4d:77:28 brd ff:ff:ff:ff:ff:ff link-netnsid 0
同时我们将另veth1设置到命名空间net_test2里面
[root@k8s-master zhanglei]# ip netns add net_test2 [root@k8s-master zhanglei]# ip netns show net_test2 net_test1 (id: 24)
[root@k8s-master zhanglei]# ip link set veth1 netns net_test2 [root@k8s-master zhanglei]# ip netns exec net_test2 ip link show 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ipip 0.0.0.0 brd 0.0.0.0 59: veth1@if60: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000 link/ether 7e:06:da:3a:09:44 brd ff:ff:ff:ff:ff:ff link-netns net_test1
到这里,veth0和veth1这个设备对已经配置在不同的网络命名空间了,但是此时还不能通信,就像电线已经搭好,但是还未通电,谁来扮演通电的角色呢?答案就是:IP
[root@k8s-master zhanglei]# ip netns exec net_test1 ip addr add 10.1.1.1/24 dev veth0 [root@k8s-master zhanglei]# ip netns exec net_test2 ip addr add 10.1.1.2/24 dev veth1 [root@k8s-master zhanglei]# ip netns exec net_test1 ip link set dev veth0 up [root@k8s-master zhanglei]# ip netns exec net_test2 ip link set dev veth1 up [root@k8s-master zhanglei]# ip netns exec net_test1 ping 10.1.1.2 PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data. 64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.097 ms 64 bytes from 10.1.1.2: icmp_seq=2 ttl=64 time=0.061 ms 64 bytes from 10.1.1.2: icmp_seq=3 ttl=64 time=0.032 ms 64 bytes from 10.1.1.2: icmp_seq=4 ttl=64 time=0.024 ms
[root@k8s-master zhanglei]# ip netns exec net_test2 ping 10.1.1.1
PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data.
64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=0.030 ms
64 bytes from 10.1.1.1: icmp_seq=2 ttl=64 time=0.090 ms
64 bytes from 10.1.1.1: icmp_seq=3 ttl=64 time=0.032 ms
64 bytes from 10.1.1.1: icmp_seq=4 ttl=64 time=0.033 ms
[root@k8s-master zhanglei]# ip netns exec net_test2 ping 10.1.1.1 PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data. 64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=0.030 ms 64 bytes from 10.1.1.1: icmp_seq=2 ttl=64 time=0.090 ms 64 bytes from 10.1.1.1: icmp_seq=3 ttl=64 time=0.032 ms 64 bytes from 10.1.1.1: icmp_seq=4 ttl=64 time=0.033 ms
如上,给每个设备配备1个ip地址,配备成功后再启动设备,然后就可以进行相互的通信了,前面提到每个容器拥有自己单独的网络命名空间,而网络命名空间之间的通信是通过设备对,而上面演示的就是命名空间通过设备对进行通信的全过程,
事实上容器之间、容器与宿主机之间都是通过设备对的方式进行通信的,当然实际上容器网络命名空间的创建、设备对的创建、IP的分配并不是手动进行的,都是容器在创建的时候会自动完成,对用户来说是无感知的,这里是方便展示内部原理,采用的手动的形式。
三、总结
本文主要重点介绍了容器PID和NET 命名空间(namespace)的隔离原理,其他namespace的隔离原理类似,容器本质上一种特殊的进程,它虽然提供了隔离技术,但与虚拟机的隔离技术要区别开来,虚拟机是在宿主机上通过Hypervisor虚拟了一个独立的GuestOS,它的隔离是彻底的,虚拟机上的进程在宿主机上无法查看;而容器本质上是宿主机上的进程,它的隔离机制是通过在进程上加入不同的namespace参数来实现资源、文件、设备、状态,或者配置的隔离,两者隔离的本质是有差异的,你或许有个疑问,既然容器是宿主机上的一个进程,而不同的进程可以相互共享宿主机的内核,一旦一个容器的应用逃逸入侵宿主机,是不是会有可能会影响宿主机或者其他容器应用呢,事实上,Docker容器的确存在这样的安全隐患,由此看来,Docker容器的隔离性并不像虚拟机般隔离的彻底,细心的读者,可能发现在这里为什么特意加入Docker,这是因为有其他类型的容器既可以提供虚拟机级般的隔离能力同时又拥有容器的高性能,这就是Kata容器,什么是Kata容器?这里先卖个关子,后续专文介绍下,感谢您的阅读!
作者简介:云计算容器DockerK8sServerless方向产品经理,学点技术,为更好地设计产品。