容器的镜像构成直接关系到你对启动一个容器的理解 ,非常重要
一 引入问题
每启动一个容器,在容器内都有一个操作系统,这里有两个问题我们需要搞清楚
(1)容器的操作系统来源于什么?
(2)我们是如何把操作系统安装到容器里的?
二 容器的操作系统来源
2.1 操作系统的来源
你一定为物理机/虚拟机安装过操作系统,具体你是先下载了一个系统镜像文件,然后把该镜像写到了u盘或光盘里称之为启动盘,然后用来为机器安装操作系统。也就说一台机器的操作系统来自于一个镜像文件,我们称之为系统镜像。
一个容器就相当于一台“虚拟机”,既然虚拟机的操作系统来源于镜像,那么容器内的操作系统必然也来源于镜像了?完全正确
但因为容器要做到全方位的轻量级,所以容器的操作系统镜像必然也要是在一个完整操作系统镜像的基础上做精简,具体如何实现的呢?我们来一一解析
2.2 完整操作系统的镜像
镜像的本质就是一个iso格式的压缩包,操作系统镜像里面存放了该系统所有的内容,具体来说分为两大部分
一个典型的 Linux 文件系统由 bootfs 和 rootfs 两部分组成
- 1、bootfs(boot file system) 主要包含 bootloader 和 kernel,bootloader 主要用于引导加载 kernel,当 kernel 被加载到内存中后 bootfs 会被 umount 掉,从而释放内存,同样的内核版本不同Linux发行版,其bootfs都是一致的。
- 2、rootfs (root file system) 包含的就是典型 Linux 系统中的/dev,/proc,/bin,/etc 等标准目录和文件。Linux系统在启动时,rootfs首先会被挂载为只读模式,然后在启动完成后被修改为读写模式,随后它们就可以被修改了。不同的 linux 发行版(如 ubuntu 和 CentOS ) 在 rootfs 这一层会有所区别,体现发行版本的差异性。
2.3 docker image是什么?如何构成的
上一小节提到,一个完整的文件系统是由bootfs+rootfs组成的
而容器image镜像其实就是把rootfs打包到一起(镜像的本质就是一种压缩包),这个rootfs系统里包含了各种依赖文件,以及你的应用程序文件都在一起。
也就说世界上第一个镜像是怎么产生的呢?
就是某人在一台物理机上安装了一个linux操作系统,包括bootfs,也包括rootfs,然后他在rootfs里安装了各种依赖包,做好了软件启动的一切配置,然后他把这个rootfs(译:根文件系统)打包压缩成了一个文件,这个文件就称之为docker的image,也就是镜像。
我们启动一个容器,必须指定一个镜像,镜像里包含的是rootfs,目的就是把镜像文件共享给了容器的名称空间里,在容器内,可以看到完全独立的文件系统,查看容器中的根文件系统 (rootfs)。然后,你会发现,它和宿主机上的根文件系统也是不一样的。所以此时你应该知道了,在容器里看到的根文件系统,就是镜像里的东西。下图docker image 中最基础的两层结构,不同的 linux 发行版(如 ubuntu 和 CentOS ) 在 rootfs 这一层会有所区别,体现发行版本的差异性。
需要再次强调的一点就是,容器本质是没有内核的、它只有rootfs,至于下层的bootfs内核部分是共享自物理机的,并且所有容器共享的都是物理主机上的运行系统的内核即bootfs相关内容。
ps:物理机运行的文件系统内核如果有漏洞,那么必然会影响到容器。
三 如何为容器安装操作系统
3.1 UnionFS(联合文件系统)的由来
容器就相当于一台虚拟机嘛,所以我们还是对比着来看。
安装操作系统的原理本质很简单,操作系统的开发者在他们自己的机器上把操作系统代码写好了之后,要给我们用、要放到我们的机器上跑起来,怎么做?
-
第一步:操作系统的开发者们会把自己编写的操作系统代码打包成一个压缩包,格式为iso,称之为系统镜像,镜像顾名思义,你去照镜子,镜子里的像给你是完全一样的,所以系统镜像的意思就是说,该文件就是完全拷贝了开发者们开发的操作系统代码文件,一模一样。
-
第二步:把iso文件“释放/解压”到目标主机,这就是安装操作系统。如何释放呢?如果目标位置是一块裸盘,那就需要借助一个传输介质,也就是我们说的U启、系统光盘等传输介质
那如何为容器安装操作系统呢?原理都是一样的,但对于容器来说,我们是想在宿主机上启动多个容器,让它们拥有操作系统,所以宿主机就是我们的传输介质,目标位置就是容器,我们需要做的就是把容器的镜像下载到宿主机就可以,让把容器的镜像文件关联给每个容器1就行,如果你想让容器有不同的系统,那就关联不同的镜像就可以,不同的镜像就是不同的系统嘛
但说到这里,我们就需要思考一个问题,对于虚拟机来说,它很笨重,每个虚拟机要想拥有自己的操作系统,我们都需要把一份操作系统镜像的内存完整地拷贝给它,如果一个镜像为1G,部署了10台虚拟机,就需要耗费10G的空间,而这10G的内容都是冗余的,
所以对于容器来说,具体做法并不是把容器镜像直接拷贝给每个容器的名称空间,而是通过挂载的方式完成的,而且是以只读的方式挂载的,至于容器内只负责存自己新增的改动就可以,不影响其他容器,这就用到了UnionFS(联合文件系统)技术,
UnionFS(联合文件系统)详解如下
3.2 总结同一个镜像启动多个容器的本质
容器的镜像的本质就是rootfs,启动容器,指定镜像的意义,就是为了把rootfs共享给容器的名称空间,这就如同给容器安装了一个操作系统一样。但是等一等,为何我这里用的是共享这个词呢?镜像的本质就是把rootfs打包压缩得到一个压缩包,那么启动容器不应该是把该压缩包解压到该容器的名称空间内吗?
思路完全正确,但具体操作不是这样的!
如果真的这么做,那假设我们一个镜像10G,那么我们用这同一个镜像启动10个容器,岂不是要解压10次,数据冗余达到了50G,太浪费了。
那如何解决该问题呢?
先说答案:我之前提到的共享一次的意思指的就是,我们用一个镜像启动容器,其实是把该镜像里的目录都mount挂载到了容器名称空间内,并且设置为只读(如何写入,后续在介绍),也就说,我们用同一个镜像启动了10个容器,其实本质就是把这个镜像里的目录mount给了10个容器名称空间而已,因此大家是共享镜像的。
ps:与namespace联合在一起思考,namespace中隔离的诸多资源中就包括mount挂载,如此,哪怕是同一个镜像,mount到了不同的容器或者说名称空间里,肯定是与其他容器/名称空间隔离的
3.3 容器内如何写入数据
上一小节提到多个容器用一个镜像启动,本质是以只读方式把镜像目录都mount到了每个容器的名称空间里。那容器内岂不是都无法写入数据了,但是我们明明是可以在容器内写入数据的啊?难道说以只读方式挂在镜像是错误的?这肯定是没错的,要想理解明明启动容器是以只读方式把rootfs里包含的目录挂载到容器里,但是却可以在容器里执行增删改查操作,那就需要了解一下UnionFS联合文件系统。
什么是联合文件系统,先说答案:
容器内的rootfs根文件系统类型,并不是大在普通linux节点上看到的Ext4或者XFS之类的常见文件系统,而是Overlay,Overlay是一种联合文件系统UnionFS(OverlayFS简称overlay 是 UnionFS 的一种具体实现,更多实现详解3.8小节),联合文件系统顾名思义,可以把文件系统上多个目录(分支)内容联合挂载到同一个目录下,使用者也就是容器看起来认为是一个目录,而实际上在物理机里是由多个目录构成的。
OverlayFS使用两个目录,把一个目录置放于另一个之上,并且对外提供单个统一的视角。这两个目录通常被称作层,这个分层的技术被称作union mount。术语上,下层的目录叫做lowerdir,上层的叫做upperdir。对外展示的统一视图称作merged
lowerdir—>镜像层,只读
upperdir—->容器层,修改相关的内容都在这里放着
merged—->容器映射层,我们在容器里看到的就是这一层,所以该层也可以称之为展现层。
针对这三层,我们从下往下看,看到的merged里的内容来自与upperdir与lowdir,upperdir里的内容会遮挡住lowerdir里的内容。
了解
容器overlay读写有三种场景
1、lowerdir里有,upperdir里没有
容器会通过overlay只读访问文件 容器层不存在的文件 如果容器只读打开一个文件,但该容器不在容器层(upperdir),就要从镜像层(lowerdir)中读取。这会引起很小的性能消耗。
2、lowerdir里没有,upperdir里有
只存在于容器层的文件 如果容器只读权限打开一个文件,并且容器只存在于容器层(upperdir)而不是镜像层(lowerdir),那么直接从镜像层读取文件,无额外的性能损耗
3、lowerdir里有,upperdir里也有
文件同时存在于容器层和镜像层 那么会读取容器层的文件,因为容器层(upperdir)隐层了镜像层(lowerdir)的同名文件,因此,也没有额外的性能损耗
总结一句话就是,lowerdir镜像层里的内容是只读的(可以通过挂载配置只读),增删改的文件都写到upperdir里,upperdir里的同名文件会遮挡住lowdir里的内容、优先级更高,lowerdir+upperdir联合挂载到了merged里
至此,你应该明白,镜像层里的内容肯定是只读的,但是读写的内容其实都放到了upperdir里,并且因为upperdir对lowerdir有遮挡效果,在当前容器里,可以看到修改,在其他容器里可能没有修改、于是在upperdir里看不到修改,没有遮挡,则看到的仍是lowerdir里的内容
了解:
为何部署docker与k8s时内核建议升级到4.0以上:overlay与overlay2
推荐阅读:https://www.cnblogs.com/linhaifeng/p/16443917.html
3.4 联合文件系统挂载实验
总结:
我们可以看到,OverlayFS 的一个 mount 命令牵涉到四类目录,分别是 lower,upper, merged 和 work,那它们是什么关系呢?
1、首先,最下面的"lower/",也就是被 mount 两层目录中底下的这层(lowerdir)。
在 OverlayFS 中,最底下这一层里的文件是不会被修改的,你可以认为它是只读的/ro。我还 想提醒你一点,在这个例子里我们只有一个 lower/ 目录,不过 OverlayFS 是支持多个 lowerdir 的。
2、然后我们看"uppder/",它是被 mount 两层目录中上面的这层 (upperdir)。在 OverlayFS 中,如果有文件的创建,修改,删除操作,那么都会在这一层反映出来,它是 可读写的/rw。
3、接着是最上面的"merged" ,它是挂载点(mount point)目录,也是用户看到的目录, 用户的实际文件操作在这里进行。
4、其实还有一个"work/",这个目录没有在这个图里,它只是一个存放临时文件的目录, OverlayFS 中如果有文件修改,就会在中间过程中临时存放文件到这里。