本博客为个人学习极客时间中张磊课程时所作的笔记,仅作交流,不得作为商用
上一篇深入剖析kubernetes系列学习之何为容器(二)
目录
容器的脚下是什么
更改根目录的chroot
容器镜像rootfs
容器镜像的层(layer)
联合文件系统
aufs
只读层/镜像层
可读写层/容器层
init层
OverlayFS
只读层/镜像层
init层
可读写层/容器层
Namespace的“隔离”让容器进程看到伪装过的世界,而Cgroups则给这个世界打造了一堵无形的墙——emm,怎么有点楚门的世界既视感,哈哈哈,如果容器进程(楚门)低头看看自己的脚下(文件系统),他会看到什么景象呢?之前提到Namespace的时候有介绍六种技术,其中有个mount:
Mount |
CLONE_NEWNS |
挂载点(文件系统) |
Mount Namespace修改的是容器进程对文件系统“挂载点”的认知。Mount Namespace跟其他Namespace的使用略有不同的地方在于它对容器进程视图的修改一定是伴随着挂载操作才能生效,C语言中有专门的mount函数执行挂载命令:
#include
int mount(const char *source, const char *target, const char *filesystemtype, unsigned long mountflags, const void *data);
参数:
source:将要挂上的文件系统,通常是一个设备名。
target:文件系统所要挂在的目标目录。
filesystemtype:文件系统的类型,可以是"ext2","msdos","proc","nfs","iso9660" 。。。
mountflags:指定文件系统的读写访问标志
我们都希望在容器进程启动后看到整个根目录“/”,而chroot命令就是shell中专门完成该功能的,它能够改变进程的根目录到你指定的位置,用法简单:
# chroot $HOME/test /bin/bash
以上命令告诉操作系统使用$HOME/test目录为/bin/bash进程的根目录。实际上,Mount Namespace正是基于对chroot的不断改良才被发明出来的,他也是Linux操作系统里面的第一个Namespace。
可以认为,Docker容器启动后主要执行了三个操作:
启用 Linux Namespace 配置;
设置指定的 Cgroups 参数;
切换进程的根目录(Change Root)
关于第三个操作,Docker容器会优先调用pivot_root进行切换,系统不允许时才会使用chroot。其区别主要为:
pivot_root和chroot的主要区别是,pivot_root主要是把整个系统切换到一个新的root目录,而移除对之前root文件系统的依赖,这样你就能够umount原先的root文件系统。而chroot是针对某个进程,而系统的其它部分依旧运行于老的root目录。
在Linux中,这种挂载在容器根目录上、用来为容器进程提供隔离后执行环境的文件系统即“容器镜像”,更专业的名字叫rootfs(根文件系统)。
rootfs只是一个操作系统所包含的文件、配置和目录,并不包括操作系统内核,所以说,rootfs只包括了操作系统的躯壳,没有包括操作系统的灵魂——哈哈哈哈!容器共享的还是宿主机的操作系统内核。
rootfs最重要的作用,是为容器提供了一致性的特性。
很多应用打包发布的时候,程序员只看到其编程语言层面的依赖,导致在新的环境打开应用时面临各种异常。殊不知,整个操作系统的文件和目录本身才是应用最需要的、最完整的依赖,而rootfs就提供了这种深入到操作系统级别的运行环境一致性,优雅的解决了应用的环境依赖问题。
我们知道一个项目的代码经常使用git等代码仓库进行管理,每一次修改和commit都会在仓库中生成一个镜像记录。
容器镜像借鉴了git的思想,用户制作镜像的每一个操作都会生成一个层,也就是一个增量rootfs。而实现这一想法的技术是联合文件系统UnionFS。
联合文件系统有很多种,包括但不限于以下这几种:aufs, device mapper, btrfs, overlayfs, vfs, zfs:
aufs:ubuntu 常用
device mapper :centOS常用
btrfs:SUSE
overlayfs:ubuntu 和 centos 都会使用,现在最新的 docker 版本中默认两个系统都是使用的 overlayfs
vfs 和 zfs:常用在 solaris 系统
aufs(advanced UnionFS),其主要文件结构如下图所示:
左上角是dockerfile,该文件专门用来创建镜像,可以看到每一句命令都对应最终镜像中的一个层(layer)。
dockerfile中的这些命令的意义在后面会介绍。
可以看出,容器的rootfs主要由三部分组成
只读层:read-only layer
读写层:read-write layer
init层:init layer
镜像的层(只读层)都放置在/var/lib/docker/aufs/diff目录下,容器运行起来后备联合挂载在/var/lib/docker/mnt里。
只读层以只读的方式挂载,ro+wh,ro即只读,wh即whiteout,什么是whiteout,下面会提到。只读层以增量的方式分别包含了最终容器操作系统的一部分。
可读写层位于容器rootfs的顶部,挂载方式为read write。可读写层通常也称为容器层,下面的只读层称为镜像层,所有的增删查改操作都只会作用在容器层,相同的文件上层会覆盖掉下层。在没有写入文件之前,这个目录是空的。
用户执行写操作时,如果是新建,则新建内容直接以增量的方式出现在这个层;如果是修改,修改一个文件的时候,首先会从上到下查找有没有这个文件,找到,就复制到容器层中进行修改,修改的结果就会作用到下层的文件,这种方式也被称为copy-on-write。
用户执行删除操作时,特别是删除只读层的问件事,aufs会在可读写层创建whiteout文件,把只读层的文件“遮挡”起来。
容器镜像修改好后,执行docker commit和push指令可以保存这个修改后的可读写层,并上传到Docker Hub中供他人使用,原先的只读层里面的内容不会发生任何变化。
init层是Docker单独生成的内部层,用来存放/etc/hosts、/etc/resolv.conf等文件,这些文件本来属于系统的一部分,但是用户往往需要在启动容器时写入一些指定的值,如hostname,就需要在可读写层对他们进行修改。
但是,这些修改的信息其实只对当前容器有效,不需要在docker commit时提交修改、与别人分享,init层就是为了解决这个问题设计的,保存用户的本地配置信息。
OverlayFS的原理是将一层目录重叠于另一层目录之上,在上层的文件系统中记录更改,下层的文件系统保持不变。OverlayFS被视为aufs的接班人,因为其设计简单、性能更好。
主要有4类目录:
被联合挂载的两个目录lower和upper,同一路径的两个文件,upper会屏蔽lower中的文件,如果是同一目录,则会合并后在上层显示;
作为统一视图联合挂载点的merged目录;
辅助功能的work目录;
CentOS环境下查看docker的overlay目录:
/var/lib/docker/overlay
查看目录结构:
[root@izwz9194nuv8g0cwqfqsh3z overlay]# tree -L 2
.
├── 48c689ee8a0738e3f13c4abd2b821355d38c4fdd4365295ed990fb075b855911 ##可读写层/容器层
│ ├── lower-id
│ ├── merged
│ ├── upper
│ └── work
├── 48c689ee8a0738e3f13c4abd2b821355d38c4fdd4365295ed990fb075b855911-init ##init层
│ ├── lower-id
│ ├── merged
│ ├── upper
│ └── work
├── 499ca7dd25b3af8df2c09e539e2f87bbc11a88d927b597f9b054955248af15d1 ##只读层/镜像层
│ └── root
可以看到有两种目录结构:
只有root目录的只读层/镜像层
含有3个目录和一个名为lower-id文件的可读写层和init层
大家可以看到,上面的只读层只有一个root目录,这是为什么呢?由于OverlayFS的原理是将一层目录重叠于另一层目录之上,即只涉及两个目录,而Docker镜像可能有很多层,为了解决这个矛盾,overlayFS在存储只读层时,会把父镜像层的内容“复制”到当前层,然后再写入当前层,复制过程中为了节省空间,普通文件采用硬链接的方式链接到父镜像层对应的文件,其他类型的文件或目录则直接复制,所以此时,最上层的只读层将拥有容器镜像所以文件和目录——即拥有整个镜像的文件系统,于是,只需要一个root目录就够了。
可以看到,与之前说的一样,该层有4类目录,不再赘述。还剩下一个文件lower-id,查看里面存储的值:
# cat lower-id
499ca7dd25b3af8df2c09e539e2f87bbc11a88d927b597f9b054955248af15d1
可以发现这个文件里面记录的值正是只读层最上层的UUID,init层使用lower-id中记录的值找到只读层的root目录作为下层目录。
可读写层在准备最上层的时候,系统会将init层的lower-id和upper目录中的内容全部复制到可读写层中,最后为容器准备rootfs的时候,将对应的4类目录联合挂载即可。