docker容器学习(2)

微信扫一扫,分享到朋友圈

docker容器学习(2)

为什么强调Linux容器?是因为比如Docker on Mac,以及Windows Docker(Hyper-V 实现),实际上是基于虚拟化技术实现的。

容器里面有完整的操作系统文件,对于容器中的进程来说,它看到的文件系统又是什么样子的呢?这和Mount Namespace(Linux操作系统里的第一个Namespace)有关,Mount Namespace修改的是容器进程对文件系统“挂载点”的认知。这也就意味着,只有在“挂载”这个操作发生之后,进程的视图才会被改变(这是它和其他Namespace不同的地方,挂载操作生效后,才会看到新视图)。

对于容器来说,通过chroot linux命令进行mount,这样让容器中的进程看到完全隔离的环境,而不是继承自宿主机的文件系统。所谓chroot,就是change root file system,即改变进程的根目录到指定的位置。

对于容器来说,它会在容器根目录下挂载一个完整操作系统文件(比如Ubuntu ISO),容器中的进程会看到完整操作系统文件,而这个挂载在容器根目录上、用来为容器进程提供隔离后执行环境的文件系统,就是所谓的“容器镜像”。它还有一个更为专业的名字,rootfs(根文件系统)。

对于docker引擎来说,它做三件事:启用Linux Namespace配置;设置指定的Cgroups参数;切换进程的根目录(Change Root)。

需要明确的是,rootfs只是一个操作系统所包含的文件、配置和目录,并不包括操作系统内核,一台机器上的所有容器共享宿主机操作系统的内核。这说明如果容器中的进程依赖操作系统内核参数或模块,一旦宿主机发送变化,就可能影响容器。

正是由于rootfs的存在,容器才有了一个重要特性:一致性。因为rootfs里打包的不只是应用,而是整个操作系统的文件和目录,容器所有的依赖,包括操作系统都封装在一起,这样开发和线上环境就能完全一致了。

对于镜像来说,如果稍微修改下rootfs就要重新制作镜像,那么一方面非常麻烦,同时新老镜像之间就非常碎片化了。通过镜像分层的概念,只需要维护相对于base rootfs修改的增量内容,而不是每次修改都制造一个“fork”。

Docker镜像层就是说,用户制作镜像的每一步操作,都会生成一个层,也就是一个增量rootfs。对于不同的容器来说,可以共享某些层。

那么各个层之间是如何联合在一起的呢?使用一种联合文件系统(Union File System)技术UnionFS。在不同操作系统中,UnionFS有不同的实现,Ubuntu(rootfs),CentOS(overlayFS)。

对于UnionFS来说,它可以将镜像中的增量层联合挂载在一个统一的挂载点上,这样docker就能找到一个容器完整的增量层了。

对于一个容器来说,它由三部分组成:

1:只读层

rootfs最下面的层,它们的挂载方式都是只读的(ro+wh,即 readonly+whiteout),比如/var/lib/docker/overlay2/[id]能看到操作系统的文件(/etc,/var),这些层都以增量的方式分别包含了操作系统的一部分。

2:可读写层

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

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

这种技术也被称为copy-on-write,所有的增删查改操作都只会作用在容器层(读写层),对于相同的文件。上层会覆盖掉下层(只读层,镜像层),当修改一个文件的时候,从上到下寻找,找到就复制到读写层,修改的结果就会作用到下层的文件。

可读写层如果做了修改,使用docker commit和push命令保存这个被修改过的可读写层,然后上传到docker hub中,这样这个镜像又能被其他人使用了,这就是增量rootfs的好处。

3:init层

Init层是Docker项目单独生成的一个内部层(在可读写层和只读层中间),专门用来存放 /etc/hosts、/etc/resolv.conf 等信息。

为什么需要这一层呢?它们原来属于只读层镜像的一部分,但用户启动容器后会写入一些指定值(比如hostname),这些修改只对当前容器有效,并不希望执行docker commit时提交这些修改,所以容器就单独创建这一层,在docker commit提交的时候只会提交可读写层,所以是不包含这些内容的。

最终这些层都被联合挂载到UnionFS对应的容器挂载点,表现为一个完整的操作系统供容器使用。

使用docker image inspect命令可以详细知晓容器不同层之间的关系。对于centos镜像来说:

"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/[id]/diff:/var/lib/docker/overlay2/[id]/diff:/var/lib/docker/overlay2/[id]/diff:/var/lib/docker/overlay2/[id]/diff:/var/lib/docker/overlay2/[id]/diff",
"MergedDir": "/var/lib/docker/overlay2/[id]/merged",
"UpperDir": "/var/lib/docker/overlay2/[id]/diff",
"WorkDir": "/var/lib/docker/overlay2/[id]/work"
},
"Name": "overlay2"
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:",
"sha256:",
"sha256:",
"sha256:",
"sha256:",
"sha256:"
]
},

LowerDir指向镜像层;UpperDir指向容器层;MergedDir:容器挂载点 ;lowerdir和upperdir整合起来提供统一的视图给容器,作为根文件系统;WorkDir:用于实现copy_up操作。

微信扫一扫,分享到朋友圈

docker容器学习(2)

两项开创性的研究,改变了我们对鸟类的认知

上一篇

你也可能喜欢

docker容器学习(2)

长按储存图像,分享给朋友