容器与镜像
什么是容器?
操作系统里的进程
- 进程之间可以相互看到、相互通信;
- 使用的是同一个文件系统,可以对同一个文件进行读写操作;
- 使用相同的系统资源。(文件系统也算系统资源吧?)
存在的问题
-
进程被其他进程影响,高级权限的进程可以攻击其他进程;
-
不同进程对相同文件的使用可能存在冲突;
-
抢占系统资源,影响其他进程。
如何给应用提供独立的运行环境
- 使用 Namespace 技术来实现进程在资源的视图上进行隔离。在 chroot 和 Namespace 的帮助下,进程就能够运行在一个独立的环境下了;
- Linux 和 Unix 操作系统可以通过 chroot 系统调用将子目录变成根目录,达到视图级别的隔离;进程在 chroot 的帮助下可以具有独立的文件系统,对于这样的文件系统进行增删改查不会影响到其他进程;
- 通过 Cgroup(Control Groups) 来限制进程资源使用率,设置其能够使用的 CPU 以及内存量。
容器定义
视图隔离、资源可限制、独立文件系统的进程集合。
容器 VS 虚拟机
-
传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程。
-
而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。
-
每个容器之间互相隔离,每个容器有自己的文件系统 ,容器之间进程不会相互影响,能区分计算资源;
- 左侧为虚拟机示意图,右侧为容器示意图。
什么是镜像?
容器运行时所需要的所有的文件集合称之为容器镜像。
可以采用 Dockerfile 来构建镜像。Dockerfile 描述了构建的每个步骤,每个构建步骤会带来文件系统内容的变化,也就是 changeset。就像洋葱一样,一层一层的。这样也会带来一个问题,对镜像的改动越多,会导致镜像文件体积越大。
changeset 的分层以及复用特点能够带来几点优势:
-
第一,能够提高分发效率,简单试想一下,对于大的镜像而言,如果将其拆分成各个小块就能够提高镜像的分发效率,这是因为镜像拆分之后就可以并行下载这些数据;
现在的下载软件很多都支持拆分大文件为多个小文件,下载后再组合。也是同样的思想,不同的实现方式罢了。
-
第二,因为这些数据是相互共享的,也就意味着当本地存储上包含了一些数据的时候,只需要下载本地没有的数据即可,举个简单的例子就是 golang 镜像是基于 alpine 镜像进行构建的,当本地已经具有了 alpine 镜像之后,在下载 golang 镜像的时候只需要下载本地 alpine 镜像中没有的部分即可;
复用 changeset。
-
第三,因为镜像数据是共享的,因此可以节约大量的磁盘空间,简单设想一下,当本地存储具有了 alpine 镜像和 golang 镜像,在没有复用的能力之前,alpine 镜像具有 5M 大小,golang 镜像有 300M 大小,因此就会占用 305M 空间;而当具有了复用能力之后,只需要 300M 空间即可。
节约存储空间。
镜像加载原理
docker的镜像实际上由一层一层的文件系统组成,这种层级的文件系统就是 UnionFS。
UnionFS
联合文件系统。Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。Union 文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
特性:一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录
bootfs
boot file system。主要包含bootloader和kernel, bootloader主要是引导加载kernel, Linux刚启动时会加载bootfs文件系统,在Docker镜像的最底层是bootfs。这一层与我们典型的Linux/Unix系统是一样的,包含boot加载器和内核。当boot加载完成之后整个内核就都在内存中了,此时内存的使用权已由bootfs转交给内核,此时系统也会卸载bootfs。
rootfs
root file system。在bootfs之上。包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc 等标准目录和文件。rootfs就是各种不同的操作系统发行版,比如Ubuntu,Centos等等。
对于一个精简的OS,rootfs可以很小,只需要包括最基本的命令、工具和程序库就可以了,因为底层直接用Host的kernel,自己只需要提供 rootfs 就行了。由此可见对于不同的linux发行版, bootfs基本是一致的, rootfs会有差别, 因此不同的发行版可以公用bootfs。
容器的生命周期
使用 docker run 的时候会选择一个镜像来提供独立的文件系统并指定相应的运行程序。这里指定的运行程序称之为 initial 进程,这个 initial 进程启动的时候,容器也会随之启动,当 initial 进程退出的时候,容器也会随之退出。
所以容器运行需要一个前台进程。
容器数据持久化
容器的生命周期依赖于应用,可能很短暂,但有的数据需要持久化,因此产生了数据卷。
两种使用方式:
- 通过命令
docker run -v
指定需要挂载到容器的目录。 - 声明数据卷,交给 Docker 管理,当然也能在宿主机里找到。可以使用命令
docker inspect containerid
查看,一般在/var/lib/docker/volums/containerid