深入剖析Kubernetes系列连载(五)镜像



Kubernetes的学习往往让人摸不着头脑,很难理解其中的原理。

深入剖析Kubernetes系列连载是学习《深入剖析Kubernetes》课程的笔记和总结,记录学习的过程,并且传递知识。

对 Docker 项目来说,它最核心的原理实际上就是为待创建的用户进程:

1. 启用 Linux Namespace 配置;

2. 设置指定的 Cgroups 参数;

3. 切换进程的根目录(优先pivot_root再chroot)

Docker 在镜像的设计中,引入了层(layer)的概念。也就是说,用户制作镜像的每一步操作,都会生成一个层,也就是一个增量 rootfs。

Union File System 也叫 UnionFS,最主要的功能是将多个不同位置的目录联合挂载(union mount)到同一个目录下

Ubuntu 16.04 和 Docker CE 18.05,这对组合默认使用的是 AuFS 这个联合文件系统的实现。你可以通过 docker info 命令,查看到这个信息

AuFS 的全称是 Another UnionFS,后改名为 Alternative UnionFS,再后来干脆改名叫作Advance UnionFS,从这些名字中你应该能看出这样两个事实:

1. 它是对 Linux 原生 UnionFS 的重写和改进;

2. 它的作者怨气好像很大。我猜是 Linus Torvalds(Linux 之父)一直不让 AuFS 进入Linux 内核主干的缘故,所以我们只能在 Ubuntu 和 Debian 这些发行版上使用

docker run -d ubuntu:latest sleep 3600

这个所谓的“镜像”,实际上就是一个 Ubuntu 操作系统的 rootfs,它的内容是 Ubuntu操作系统的所有文件和目录。不过,与之前我们讲述的 rootfs 稍微不同的是,Docker 镜像使用的 rootfs,往往由多个“层”组成

docker image inspect ubuntu:latest
...
"RootFS": {
    "Type": "layers",
    "Layers": [
        "sha256:f49017d4d5ce9c0f544c...",
        "sha256:8f2b771487e9d6354080...",
        "sha256:ccd4d61916aaa2159429...",
        "sha256:c01d74f99de40e097c73...",
        "sha256:268a067217b5fe78e000..."
    ]
}

这五个层就是五个增量 rootfs,每一层都是 Ubuntu 操作系统文件与目录的一部分;而在使用镜像时,Docker 会把这些增量联合挂载在一个统一的挂载点上

这个挂载点就是比如: /var/lib/docker/aufs/mnt/6e3be5d2ecccae7cc0fcfa2a2f5c89dc21ee30e166be823ceaeba15dce

从这些信息里,我们可以看到,镜像的层都放置在/var/lib/docker/aufs/diff 目录下,然后被联合挂载在/var/lib/docker/aufs/mnt 里面

这个容器的 rootfs 由如下图所示的三部分组成

第一部分,只读层

ro+wh,即 readonly+whiteout(白障)

$ ls /var/lib/docker/aufs/diff/72b0744e06247c7d0...
etc sbin usr var

$ ls /var/lib/docker/aufs/diff/32e8e20064858c0f2...
run

$ ls /var/lib/docker/aufs/diff/a524a729adadedb900...
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

第二部分,可读写层

挂载方式为:rw,即 read write。在没有写入文件之前,这个目录是空的。而一旦在容器里做了写操作,你修改产生的内容就会以增量的方式出现在这个层中

如果要删除只读层里的一个文件呢?

为了实现这样的删除操作,AuFS 会在可读写层创建一个 whiteout 文件,把只读层里的文件“遮挡”起来。

比如,你要删除只读层里一个名叫 foo 的文件,那么这个删除操作实际上是在可读写层创建了一个名叫.wh.foo 的文件。这样,当这两个层被联合挂载之后,foo 文件就会被.wh.foo 文件“遮挡”起来,“消失”了。这个功能,就是“ro+wh”的挂载方式,即只

读 +whiteout 的含义。我喜欢把 whiteout 形象地翻译为:“白障”

第三部分,Init 层

它是一个以“-init”结尾的层,夹在只读层和读写层之间。Init 层是 Docker 项目单独生成的一个内部层,专门用来存放 /etc/hosts、/etc/resolv.conf 等信息。

需要这样一层的原因是,这些文件本来属于只读的 Ubuntu 镜像的一部分,但是用户往往需要在启动容器时写入一些指定的值比如 hostname,所以就需要在可读写层对它们进行修改。

可是,这些修改往往只对当前的容器有效,我们并不希望执行 docker commit 时,把这些信息连同可读写层一起提交掉。所以,Docker 做法是,在修改了这些文件之后,以一个单独的层挂载了出来。而用户执行docker commit 只会提交可读写层,所以是不包含这些内容的。

最终,这 7 个层都被联合挂载到 /var/lib/docker/aufs/mnt 目录下,表现为一个完整的Ubuntu 操作系统供容器使用

copy-on-write

上面的读写层通常也称为容器层,下面的只读层称为镜像层,所有的增删查改操作都只会作用在容器层,相同的文件上层会覆盖掉下层。知道这一点,就不难理解镜像文件的修改,比如修改一个文件的时候,首先会从上到下查找有没有这个文件,找到,就复制到容器层中,修改,修改的结果就会作用到下层的文件,这种方式也被称为copy-on-write

(完)