一文读懂容器三大核心技术——Namespace,Cgroup和UnionFS

本文字数:8814 字

精读时间:18 分钟

也可在 8 分钟内完成速读

容器到底是什么?容器是怎么工作的?容器如何隔离资源?为啥容器启动那么快?...如果你是个好奇宝宝,平时在使用容器的时候内心定会泛起类似疑问。本文将通过讲解其三大核心技术:Linux Namespace,Control Groups(cgroups)和UnionFS (联合文件系统)来解答你心中对容器原理的种种疑问。

Linux Namespace

Linux Namespaces是Linux内核提供的一种资源隔离方案。Namespaces之间的资源相互独立。目前Linux中提供七种namespace。 

参考:http://man7.org/linux/man-pages/man7/namespaces.7.html

Namespace Flag 说明

Cgroup

CLONE_NEWCGROUP 隔离cgroup 
IPC
CLONE_NEWIPC 隔离进程间通信
Network
CLONE_NEWN
隔离网络资源
Mount
CLONE_NEWNS 隔离挂载点
PID CLONE_NEWPID 隔离进程的ID
User
CLONE_NEWUSER 隔离用户和用户组的ID
UTS
CLONE_NEWUTS 隔离主机名和域名信息


clone系统调用传入上述表格中对应的Flag参数,可以为新建的进程创建相应的namespace。也可以使用setns系统调用将进程加入到一个已经存在的namespace中。容器通过namespace技术来实现资源隔离。

namespaces限制容器能看到哪些资源。

示例:linux下通过shell创建一个容器

Talk is cheap, show me the code。

我们直接用一个示例来演示一下namespace隔离资源的效果。在命令行下,我们可以通过unshare命令来启动一个新进程,并为其新建相应的命名空间。在这个示例中,我们将通过unshare为我们的容器创建除cgroupuser之外的所有命名空间,这也是docker run something默认为容器创建的命名空间。本示例依赖docker环境来为我们提供一些配置上的便利。完整的示例script放在这里,方便大家scriptreplay回看过程。

git clone https://github.com/DrmagicE/build-container-in-shell
cd ./build-container-in-shell
scriptreplay build_container.time build_container.his
step1: 准备一个rootfs

首先,我们要为我们的容器准备自己的rootfs,用来为容器进程提供隔离后执行环境的文件系统。这里我们直接导出alpine镜像作为我们的rootfs,选择/root/container目录作为镜像rootfs:

[root@drmagic container]# pwd 
/root/container
[root@drmagic container]# # 修改mount类型为private,确保后续的mount/umount不会在namespace之间传播
[root@drmagic container]# mount --make-rprivate / 
[root@drmagic container]# CID=$(docker run -d alpine true)
[root@drmagic container]# docker export $CID | tar  -xf-
[root@drmagic container]# ls # rootfs建立好啦
bin  dev  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
step2: 命名空间隔离
[root@drmagic container]# # 使用unshare为新的shell创建命名空间
[root@drmagic container]# unshare --mount --uts --ipc --net --pid --fork /bin/bash
[root@drmagic container]# echo $$ # 看看新进程的pid
1
[root@drmagic container]# hostname unshare-bash # 修改一下hostname
[root@drmagic container]# exec bash #替换bash,显现hostname修改后的效果
[root@unshare-bash container]# # hostname变化了

通过上面的过程,我们可以看到UTSPID这两个命名空间的隔离效果。

如果你在这一步使用ps来查看所有的进程,结果可能会令你失望——你仍然会看到系统中的所有进程,就像没有隔离成功一样。但这是正常的,因为ps读取/proc下的信息,此时的/proc还是host的/proc,所以ps还是能看到所有的进程。

step3:隔离挂载信息

[root@unshare-bash container]# mount # 还是能看到host上的mount
/dev/vda2 on / type xfs (rw,relatime,attr2,inode64,noquota)
devtmpfs on /dev type devtmpfs (rw,nosuid,size=1929332k,nr_inodes=482333,mode=755)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000)
mqueue on /dev/mqueue type mqueue (rw,relatime)
hugetlbfs on /dev/hugepages type hugetlbfs (rw,relatime)
.....

我们发现mount依然能够获取全局挂载信息,难道是mount命名空间隔离没生效?非也,mount命名空间已经生效了。当新建一个mount命名空间时,他会拷贝父进程的挂载点,但对该命名空间挂载点的后续修改将不会影响到其他命名空间。

参考:

http://man7.org/linux/man-pages/man7/mount_namespaces.7.html#DESCRIPTION

命名空间内挂载点的修改不影响其他命名空间有一个前提条件——mount的propagation type要设置为MS_PRIVATE,这也是为什么一开始我们要执行 mount --make-rprivate / 的原因

因此我们看到的mount信息是父进程的一份拷贝,我们重新mount一下/proc,好让ps能正常显示。

[root@unshare-bash ~]# # 重新mount一下/proc
[root@unshare-bash ~]# mount -t proc none /proc
[root@unshare-bash ~]# ps -ef
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 21:29 pts/0    00:00:00 bash
root        77     1  0 21:47 pts/0    00:00:00 ps -ef
[root@unshare-bash ~]# # 啊哈,现在我们的ps正常了!

处理完了/proc的挂载,我们还需要清理旧的挂载点,将他们umount掉,这一步我们需要借助pivot_root(new_root,put_old)来完成。pivot_root将当前mount namespace下的所有进程(线程)的根目录挂载点切换至new_root,并将旧的根目录挂载点放到put_old目录下。使用pivot_root的主要目的是用来umount一些从父进程copy过来的挂载点。

http://man7.org/linux/man-pages/man2/pivot_root.2.html

为了满足pivot_root的一些参数要求,需要额外做一次bind mount:

  • 10
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值