Linux容器在实际使用中非常常见,docker、LXC都是使用了容器技术,本系列目的就是探究Linux容器的实现。
一般情况下,Linux容器使用到了以下几个内核的功能:
- namespace
- cgroup
- seccomp
各个功能用处如下
namespace
它是Linux内核的对资源进行分区的技术,使得一组进程只能看到相应组的资源,现有以下几个namespace:
Mount(mnt)
mnt namespace可以实现挂载点的隔离,在创建mnt namespace后新的名字空间会复制先前名字空间的挂载点,之后便可以
挂载一些挂载点使其在原来的名字空间中不可见。
这里说的是可以,因为Linux上的挂载点具有不同的传播类型,现有如下几个:
- MS_SHARED
共享挂载,通过这种方式挂载会影响其他名字空间。 - MS_PRIVATE
私有挂载,通过这种方式挂载不会对其他名字空间有任何影响。 - MS_SLAVE
重属挂载,父名字空间内改变会影响到子名字空间,但子名字空间无法直接修改 - MS_UNBINDABLE
不可解除挂载,通过这种方式,即使父名字空间解除了挂载,子名字空间中仍然存在
我们可以使用cat /proc/self/mountinfo
来查看当前进程(猜猜当前进程指的是那个进程)所在名字空间的挂载点信息,可以看到,基本每行都能看到sharer:[n]
(其中[n]为数字),因此在我们新创建名字空间的时候,这些挂载点便会共享到新的名字空间中。
如果我们在docker容器中运行这条命令呢?这样你会发现,没有了刚刚说的那种特征,这以为着这些挂载点是私有的,因此在上级的名字空间中你看不到这些挂载点,相反,如果这个挂载点是共享的,那么我们在父名字空间中看到这个挂载点,在之后代码我们可以试试。
Process ID(pid)
pid namespace,可以让进程的pid在新的名字空间中重新编号,但实际上这就是一种重新映射,我们可以开一个debian的docker容器,通过以下指令来查看这一映射:
1.在容器中执行sleep 10000
2.利用docker exec来为此容器创建一个新的shell,输入ps -ef | grep "sleep 10000"
3.在主机上,执行同样的指令
在容器中执行,可以看到类似的结果:
root 216 1 0 06:54 pts/0 00:00:00 sleep 10000
root 218 8 0 06:54 pts/1 00:00:00 grep sleep 10000
但在主机上运行,结果是这样的:
root 60683 59675 0 14:54 pts/0 00:00:00 sleep 10000
lc 60689 59430 0 14:54 pts/2 00:00:00 grep sleep 10000
可以看出,容器中的进程上层名字空间内不仅可见,而且pid也和上级不同。
我们在主机上可以继续执行以下指令,来看出这个进程在另一个名字空间中:
sudo ls -la /proc/60683/ns/
输出结果如下:
dr-x--x--x 2 root root 0 2月20日 14:57 .
dr-xr-xr-x 9 root root 0 2月20日 14:54 ..
lrwxrwxrwx 1 root root 0 2月20日 14:57 cgroup -> 'cgroup:[4026533635]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 ipc -> 'ipc:[4026533573]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 mnt -> 'mnt:[4026533188]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 net -> 'net:[4026533576]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 pid -> 'pid:[4026533575]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 pid_for_children -> 'pid:[4026533575]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 uts -> 'uts:[4026533570]'
我们再查看当前进程所处的名字空间:
sudo ls -la /proc/self/ns/
总计 0
dr-x--x--x 2 root root 0 2月20日 14:57 .
dr-xr-xr-x 9 root root 0 2月20日 14:57 ..
lrwxrwxrwx 1 root root 0 2月20日 14:57 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 mnt -> 'mnt:[4026531841]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 net -> 'net:[4026531840]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 uts -> 'uts:[4026531838]'
也可以继续查看其他进程:
sudo ls -la /proc/1/ns/
总计 0
dr-x--x--x 2 root root 0 2月20日 10:36 .
dr-xr-xr-x 9 root root 0 2月20日 10:36 ..
lrwxrwxrwx 1 root root 0 2月20日 10:36 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 mnt -> 'mnt:[4026531841]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 net -> 'net:[4026531840]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 2月20日 14:57 uts -> 'uts:[4026531838]'
可以看到,处于统一名字空间的self(当前进程)和1目录下的链接目标相同,但在容器中的却不同,你也可以拿其他的进程做测试。
除此之外,pid namespace还有一特征,进入容器的第一个进程pid为1(当然是在子名字空间中),这一点可以在docker进入容器最初始的shell上看到:
root@7cd6bdd0d303:/# echo $$
1
这一特征也能帮我们在容器中运行systemd等(systemd要求自身pid必须为1,即对应实体机开机后的第一个用户态进程或由其exec产生的进程)服务管理工具。
fish · 2024-05-12 22:36
二呢?