写点什么

Docker 底层技术

用户头像
混沌畅想
关注
发布于: 2020 年 11 月 02 日

Docker 核心解决的问题是利用 LXC 来实现类似 VM 的功能,从而利用更加节省的硬件资源来提供给用户更多的计算资源。同 VM 的方式不同, LXC 并不是一套硬件虚拟化的方法,是无法归属到全虚拟化、部分虚拟化和半虚拟化中的任意一个,而是一个操作系统级虚拟化方法,我们理解起来可能并不像 VM 那样直观。我们可以通过 Docker 要解决什么问题出发,看看 Docker 是怎样实现用户虚拟化需求的。

Docker 容器的内核版本与宿主机的内核版本是一致的,而容器的主机名却是独立的,容器主机名缺省使用容器的 ID。通过运行 ps -ef 命令,可以发现容器进程是隔离的,在容器内部是无法看到宿主机的进程,容器拥有自己的 PID 为 1 的进程;容器的网络也是隔离的,容器拥有独立的 IP;容器的文件系统也是隔离的,容器拥有自己的系统和文件目录,在容器内修改文件并不能影响宿主机上对应目录的文件。

Docker 实现以上的隔离机制都不是 Docker 的新技术,而是通过 Linux kernel 现有的技术实现的,主要包括:

  • 资源控制(CGroups)

  • 访问控制(Namespace)

  • 文件系统隔离(UnionFS)

1 资源控制

资源控制 Control Groups(CGroups)能够隔离宿主机器上的物理资源,并对共享资源进行隔离、限制和审计,比如 CPU、内存、磁盘 I/O 和网络带宽。每个 CGroup 可以是一组被相同的标准和参数所限制的进程,在不同的 CGroup 之间是存在层级关系的,它们可以从父级 CGroup 继承一些用于资源限制使用的标准和参数。只有控制了分配到容器的资源,才能避免多个容器在同时运行时对系统资源的争用。

Linux 的 CGroup 可以为一组进程分配资源,通过对资源进行分配,CGroup 可以提供以下几种功能:

  • 资源限制,可以对任务使用的资源总额进行限制

  • 优先级分配,通过分配 CPU 时间及磁盘 I/O 控制任务运行的优先级

  • 资源统计,可以统计资源的使用量

  • 任务控制,可以实现任务挂起、恢复等控制操作

子系统是 CGroups 中一个重要的概念,子系统可以理解为资源控制器,每种子系统就是一个资源的分配器,可以独立的控制一种资源。例如 CPU 子系统就是控制 CPU 分配的。每一个 CGroup 下面都有 tasks 文件,tasks 文件中存储着属于当前控制组的所有进程的 pid,如图 5 所示,如果用户需要控制 Docker 某个容器的资源占用情况,可以在 Docker 的控制组下面找到对应的子系统,并改变对应文件的内容。当然 Docker 提供了相应的指令在运行容器时由 Docker 进程去完成相应文件内容的变更;当我们关闭容器时,Docker 子系统对应的文件夹会被 Docker 进程同步移除。

目前 Docker 使用了以下几种子系统:

  • blkio,可以为块设备设定 I/O 限制

  • cpu,可以控制任务对 CPU 的使用

  • cpuacct,记录 CPU 的使用情况

  • cpuset,为任务分配独立的 CPU 和内存

  • devices,开启或关闭对设备的访问

  • - freezer,可以挂起或恢复任务

  • memory,可以设定任务对内存的使用上限,并自动记录内存资源使用情况

  • perf_event,可以进行统一的性能检测

图5-资源控制

2 访问控制

命名空间 Namespaces 可以有效的帮助 Docker 分离进程树、网络接口、挂载点以及进程间的通信等资源。每个容器都有自己单独的命名空间,运行在其中的应用都好像是在对立的操作系统中运行一样,命名空间保证了容器之间相互不会影响和被影响。

CGroups 实现了资源的分配,而命名空间实现了系统资源的隔离。

Linux 命名空间机制提供了以下几种不同的命名空间:

  • pid 命名空间,用于隔离进程

  • net 命名空间,用于隔离网络

  • ipc 命名空间,用于隔离进程间通信

  • mnt 命名空间,用于隔离挂载点

  • uts 命名空间,用于隔离主机名和域名

  • user 命名空间,用于隔离用户组

下面通过两个例子来介绍命名空间。一个是基于用户组的隔离,系统可以为 UID 为 n 的用户,虚拟出一个 namespace,在这个 namespace 里面,用户具备 root 权限,然后在宿主机上该用户仍然是一个普通用户;另一个是基于进程的隔离,如图 6 所示在每个虚拟出的 namespace 内部,每个用户都拥有一个属于自己的进程 ID 为 1 的 init 进程,以及它的子进程。

子进程命名空间的进程映射到父进程命名空间的进程上,在进程命名空间隔离下,子进程命名空间无法知道父进程命名空间的进程,而父进程命名空间可以知道子进程命名空间的所有进程。

图6-进程命名空间示意图

3 文件系统隔离

联合文件系统 UnionFS 是 Linux 操作系统中用于支持将多个不同的文件目录挂载到同一个目录的技术。Docker 支持的 UnionFS 包括 AUFS、OverlayFS、devicemapper、vfs 以及 btrfs 等。

Docker 采用了分层构建的机制,最底层使用的 bootfs,在这之上则是 rootfs。bootfs 包含了操作系统 boot loader 和 kernel,主要用于系统引导,一旦完成引导,整个 Linux 内核加载到内存后会将 bootfs 层卸载,相同内核版本的不同 Linux 发行版的 bootfs 都是相同的,用户是不会修改这个文件系统的;rootfs 是 Docker 容器的根文件系统,包含了典型的目录结构,如/dev、/proc、/bin、/etc、/lib、/usr 和/tmp,这个文件系统在不同的 Linux 发行版中是不同的,用户可以对这些文件进行修改,当 Linux 系统启动时,rootfs 首先会被加载为只读模式,当启动完成后被修改为读写模式。不同的 Linux 发行版,实现 UnionFS 所用到的技术也可能是不一致的,我们可以通过 docker info 命令查看当前系统上使用的是哪一类 UnionFS 实现技术。

下图 7 展示的是基于 AUFS 实现的联合文件系统示意图,启动容器时,Docker 会在镜像上附加一层可写层,也被称作容器层,而在这一层之下都是只读的,所有对于容器运行时的修改都是在容器层完成的,也即是对这个容器层的修改。容器和镜像的最大区别在于,所有的镜像都是只读的,而每个容器则实际上就是镜像加上容器层,因而同一个镜像可以对应多个容器。

图7-Docker文件系统


发布于: 2020 年 11 月 02 日阅读数: 1663
用户头像

混沌畅想

关注

还未添加个人签名 2017.10.22 加入

还未添加个人简介

评论

发布
暂无评论
Docker底层技术