这篇文章不知道要写多少,我本来只打算一篇写完docker的原理,但是想想还是写成一个系列,后面好做补充内容。这篇主要是讲讲docker的命名空间隔离,CGroups资源控制组,以及rootfs根文件系统,还有UnoinFS文件系统之类的知识点。
至于docker的命令部分,会稍微带一点,但不是重点,如果有必要的话会在本系列第二篇仔细讲一讲的,以及RH134中没有提及到的podman容器,因为podman容器天然支持systemd,并且能够运行rootless容器,所以还是需要看看的。
在开始之前,先理清楚容器、容器镜像、容器运行时在这几个概念。容器利用的是linux内核提供的的namespace和CGroup技术,将一系列进程进行隔离在独立的环境中运行。
而容器镜像就是包含容器运行的所需的静态文件和元数据,本质是一个分层存储的归档文件。至于容器运行时则是管理容器生命周期的工具。
UnionFS文件系统
上面提到,容器镜像是通过是分层构建的,每个镜像层都是一个只读模板,当执行docker命令时,则会基于现有的镜像层创建一个新的临时可写层,用于构建容器。因此,这种技术通过联合文件系统可以实现。UnionFS的最上层是可写的,其他层则是只读的,并且上层会掩盖下层的文件。
[1]AUFS
这是UnionFS的升级版,原理是一样的。我们可以通过mount -t aufs来挂在一个aufs文件系统,需要指定必要选项,如-o br:upperlay:layer1:layer2,最上层放在第一个,指定的设备文件为空none(因为选项已经包含了要挂载的目录),但是需要指定挂载点。
挂载点其实就是最终的可视层,在设置 AUFS 文件系统的分支(即层)的时候 ,layer1 在前,AUFS 的优先级是从左到右的,这就意味着,如果一个文件存在于两个层中,则 AUFS文件系统中显示的版本(可视层)将是最左侧层的版本。
可视层也是我们可以操作的层,我们在里面可以删除和创建文件,但是如果删除的文件属于只读层的,那么会在可写层创建一个.wh*文件,用于掩盖只读层的文件而不是真正删除。如果创建一个同名文件,则是在可写层中创建,并删除.wh*文件。
[2]OverlayFS
可以使用mount -t overlay挂载overrlay文件系统,挂载选项和AUFS差不多,但是多了一个workdir临时目录,overlay使用该临时目录来对文件系统内部进行修改,如文件的重命名和删除。并且在overlay中挂载点被称为merged,也就是最终合并的视图。
在docker中,每个容器都有其ID,在/var/lib/docker/image/overlay2/layerdb/mounts/目录下,有着该容器ID命名的文件夹。该文件夹存放着init-id、mount-id、parent三个文件,其中 mount-id 文件包含一个 ID,该 ID 与 /var/lib/docker/overlay2/ 中的一个目录相对应。
在/var/lib/docker/overlay2中与mount-id中ID命名的文件夹下,有着可以看到的overlay2文件系统,包括diff、merged、lower、work这几个目录,其实merged是最终视图,里面包含一个根文件系统,有着完整的系统文件目录,在里面进行任何操作都会修改真实的容器。
namespace命名空间
在上一节我们可以看到每个容器都有一个根文件系统,那么究竟是怎么实现的,为什么不会和宿主机的根文件系统产生冲突呢?那么实际上就是通过联合文件系统+命名空间构建独立的视图,从而让这两者互不干扰,无论修改哪个根文件系统都不会影响另一方。
[1]根文件系统
我们可以通过docker export dockername -o name.tar导出容器的文件系统,然后通过tar xf name.tar -C /path/to/restore来解压,里面包含完整的根文件系统。我们可以使用chroot /path/to/restore/rootfs来切换根文件系统,可以指定shell,不指定的话默认是当前shell。
[2]命名空间
系统使用到的命名空间有,cgroup ns:隔离 CGroup 视图。ipc ns: 隔离进程间通信资源。 mnt ns: 隔离文件系统挂载点。net ns: 隔离网络资源。pid ns: 隔离进程 ID。user ns: 隔离用户 和用户组 ID。uts ns: 隔离主机名和域名。pid_for_children: 当前进程的子进程将继承的 PID ns。
上一小节切换根文件系统之后使用ip link和mount -t proc proc /proc && ps aux(依赖/proc目录,进程本质上是/proc 挂载点中的文件描述符)来查看,发现网络资源和进程资源并没有隔离。我们可以手动创建一个隔离的命名空间,使用unshare --mount --uts --net --ipc --pid --fork --user --map-root-user /bin/bash来创建。
在该命名空间中,我们通过mount -t proc proc /proc && ps aux就可以看到隔离的具有不同PID的进程了,这就是PID 命名空间的神奇之处,它使得一个进程在不同的命名空间具有不同的 PID 号,在该命名空间之外查看其实就是创建命名空间的用户的进程。
CGroup资源控制组
资源控制组用来限制组内进程消耗的资源,通过将进程分组,并为每个组分配特定的资源限制,能够对不同优先级的进程进行调度,高优先级的进程会优先满足资源的需求。
同时资源控制组会监视各类资源的使用情况,能够对进程实现的挂起、恢复和终止等操作,防止某个进程或者组占用过多的资源,从而影响系统的稳定性。CGroup通过子系统来管理不同类型的资源,在里面通过声明式的文件来管控进程组对资源的分配情况。