05|容器技术基本原理之容器镜像

容器镜像是Docker公司的创新发明,它解决了应用程序的分发问题,在了解了Namespace和Cgroups的基本原理后,我们再来谈谈容器镜像的知识。

我们说容器可以理解成一个被封闭起来的特殊进程,Cgroups限制了进程的最大资源使用量(相当于天花板),Namespace对资源进行了隔离(相当于围墙),下面还要对进程视角的文件系统进行一下限制(相当于地板),这样就全方位多角度的把进程封闭起来了。

说起容器文件系统的限制,可以想到Namespace中包含一个Mount namespace,但是Mount namespace相比于Pid namespace有一些不同,来回顾一下Pid namespace实现Pid隔离的流程,当操作系统执行clone操作并传入CLONE_NEWPID参数时,所生成的新进程Pid为1,新的进程视图已经生效,它以为自己是老大了。

int pid = clone(main_function, stack_size, CLONE_NEWPID | SIGCHLD, NULL); 
复制代码

Mount namespace的使用,也是通过操作系统clone操作并传入CLONE_NEWNS来实现:

int pid = clone(main_function, stack_size, CLONE_NEWNS | SIGCHLD , NULL);
复制代码

但是当新的进程生成后,它能看到的文件系统信息和宿主机还是一样的,这是因为Mount namespace要生效,需要重新执行挂载操作才可以。

所以Docker在调用clone操作并传入CLONE_NEWNS参数新起进程后,还是要执行重新mount相关的操作的,要重新mount的目录自然是所有目录之祖: /目录了。

Docker是如何执行重新Mount相关操作的呢?答案是切换进程的根目录,在进程的根目录下挂载一个完整的操作系统的文件系统,这样在ls /时就可以看到一个完整操作系统的目录文件了。而这个挂载在容器根目录上、用来为容器进程提供隔离后执行环境的文件系统就是容器镜像,也称为:rootfs(根文件系统)。对于这个挂载操作Docker会执行pivot_root系统调用,如果系统不支持,则使用chroot,二者完成的功能没有大太不同,下面以chroot为例手动演示目录切换操作:

在任意目录/home/ops/目录下创建rootfs目录:

$ mkdir rootfs
复制代码

从现成的busybox镜像中导出其系统文件,并放到rootfs目录下:

$ cd rootfs 
$ docker export $(docker create busybox) -o busybox.tar
$ tar -xf busybox.tar
$ rm -rf busybox.tar
复制代码

可以看到rootfs目录下有了基本的操作系统目录文件:

$ ls
bin  dev  etc  home  proc  root  sys  tmp  usr  var
复制代码

以/home/ops/chroot为根目录运行/bin/sh进程:

$ chroot /home/ops/rootfs /bin/sh
复制代码

运行ls命令,可以看到文件系统已经与宿主机隔离开了:

/ # bin/ls /
bin   dev   etc   home  proc  root  sys   tmp   usr   var
复制代码

有了这些铺垫,我们可以讲容器创建的核心就是为一个进程做如下操作:

  1. 为容器进程启用Namespace资源隔离
  2. 创建进程的Cgroup配置进行资源限制
  1. 切换容器进程根目录,挂载rootfs

容器镜像的方便之处在于可以自由的被分发,复用,例如想运行一个Node.js程序,那直接从DockerHub上pull一个Node.js的基础镜像就可以运行我们的代码了,我们也可以基于别人的镜像增加自己的改动,新生成一个镜像。镜像这种可以复用的前提是,Docker在镜像的设计中,引入了层(layer)的概念。用户制作镜像时的每一步操作(Dockerfile文件中的每一行命令),都会生成一个层。这种分层的能力是由联合文件系统( Union File System 提供的,它的功能是将不同位置的目录,联合挂载到同一目录下。

联合文件系统有很多驱动实现,比如常听说的overlayFS,AUFS等。就目前来讲这个也不用特别的选择了,基本用overlayFS就是可以的,现在它已经出到第二版了。

下面来做个练习看下联合文件系统是如何把多个文件组合到一起的,以overlayFS为例。

Overlay 只有两层:upper 层和 lower 层,Lower 层代表镜像层,upper 层代表容器可写层。

不同文件会合并到一起,相同文件上层覆盖下层:

$ mkdir test && cd test
$ mkdir upper lower merged work
$ echo "from lower" > lower/in_lower.txt
$ echo "from upper" > upper/in_upper.txt
$ echo "from lower" > lower/in_both.txt
$ echo "from upper" > upper/in_both.txt
$ sudo mount -t overlay overlay -o lowerdir=`pwd`/lower,upperdir=`pwd`/upper,workdir=`pwd`/work `pwd`/merged
$ cat merged/in_both.txt
复制代码

当我们想删除test目录会报以下错误:

rm: 无法删除"test/merged": 设备或资源忙
复制代码

这时我们查看overlayFS的挂载信息并卸载后删除就可以了:

$ cat /proc/mounts | grep overlay

overlay /var/lib/docker/overlay2/57ba934a7db982dd8cc04c438a965e7faaaebe4e0d86ea73174c38079df1cd5b/merged overlay rw,relatime,lowerdir=/var/lib/docker/overlay2/l/DJ6J7JO7NFQAC6VOS43UZ45EA2:/var/lib/docker/overlay2/l/EDRBRT6SLIRHP3JAI4A4R5ZW4A,upperdir=/var/lib/docker/overlay2/57ba934a7db982dd8cc04c438a965e7faaaebe4e0d86ea73174c38079df1cd5b/diff,workdir=/var/lib/docker/overlay2/57ba934a7db982dd8cc04c438a965e7faaaebe4e0d86ea73174c38079df1cd5b/work 0 0

$ umount /var/lib/docker/overlay2/57ba934a7db982dd8cc04c438a965e7faaaebe4e0d86ea73174c38079df1cd5b/merged
复制代码

通过命令可以查看指定容器的文件系统驱动及挂载信息:

$ docker inspect <container_id>

        "GraphDriver": {
            "Data": {
                "LowerDir": "/var/lib/docker/overlay2/57ba934a7db982dd8cc04c438a965e7faaaebe4e0d86ea73174c38079df1cd5b-init/diff:/var/lib/docker/overlay2/cd0d5a014407c86f45025d2b70c1b175ec92158836f24581193beb8e3a65d118/diff",
                "MergedDir": "/var/lib/docker/overlay2/57ba934a7db982dd8cc04c438a965e7faaaebe4e0d86ea73174c38079df1cd5b/merged",
                "UpperDir": "/var/lib/docker/overlay2/57ba934a7db982dd8cc04c438a965e7faaaebe4e0d86ea73174c38079df1cd5b/diff",
                "WorkDir": "/var/lib/docker/overlay2/57ba934a7db982dd8cc04c438a965e7faaaebe4e0d86ea73174c38079df1cd5b/work"
            },
            "Name": "overlay2"
        },
复制代码

Docker启动时镜像是如何工作的?

  1. 初始化将rootfs以readonly的方式加载并检查,利用union mount的方式将一个readwrite文件系统挂载在readonly的rootfs之上。
  2. 允许将下层的文件系统设定为readonly并且向上叠加。
  1. 这样一组readonly和一个writeable的结构构成一个container的运行时态,每一个FS被称为一个FS层。

镜像的写操作:

镜像拥有共享特性,对容器可写层的操作需要依赖于存储驱动提供的写时复制和用时分配机制,以此来支持对容器可写层的修改,提高对存储和内存资源的利用率。

  • 写时复制(copy-on-write)
    • 一个镜像可以被多个容器使用,但是不需要在内存和磁盘上做多个拷贝。
    • 在需要对镜像提供的文件进行修改时,该文件会从镜像的文件系统被复制到容器的可写层进行修改,而镜像里的文件不会改变。
    • 不同容器对文件的修改都相互独立、互不影响。
  • 用时分配
    • 按需分配空间,而非提前分配,即当一个文件被创建出来后,才会分配存储空间。

猜你喜欢

转载自juejin.im/post/7036338949853282311
今日推荐