Docker 基础支撑技术概览
2023-02-07 Docker Cloud Native
概述
Docker 容器本质上是宿主机上的进程。
Docker
通过 Namespaces
实现了资源隔离,通过 CGroups
实现了资源限制,通过 UnionFS
实现了高效的文件 (镜像) 操作。
Namespaces
命名空间 (namespace)
是一种 内核级别的资源隔离机制,用于确保运行在同一操作系统上的各进程互不干扰,
和平时业务代码中的 namespace
或者 package
概念类似,单个 命名空间
中的进程只能看到该 命名空间
中的相关信息,例如:
- 主机名称
- 网络资源
- 用户信息
- 文件系统
- 进程关系
- …
7 种资源类型命名空间
名称 | 系统调用参数 | 隔离内容 |
---|---|---|
IPC | CLONE_NEWIPC | 主机名和域名 |
Network | CLONE_NEWNET | 网络设备、网络栈、端口等 |
Mount | CLONE_NEWNS | 挂载点 (文件系统) |
PID | CLONE_NEWPID | 进程 ID |
User | CLONE_NEWUSER | 用户和用户组 ID |
UTS | CLONE_NEWUTS | 主机名和域名 |
CGroups | CLONE_NEWCGROUP | CGroups root directory |
以上 7 种命名空间基本覆盖了一个进程运行所需要的独立环境,换句话说,一个进程不属于某一个资源类型的命名空间,而是属于每种资源类型的一个命名空间。
proc 目录
Linux 中每个进程都有一个 /proc/$pid/ns
的目录,里面保存了该进程所在对应 命名空间
的链接。
以笔者机器中的 MySQL 进程为例:
$ ls -l /proc/17042/ns/
total 0
lrwxrwxrwx 1 xxx xxx 0 Feb 11 21:18 ipc -> ipc:[4026532808]
lrwxrwxrwx 1 xxx xxx 0 Feb 11 21:18 mnt -> mnt:[4026532806]
lrwxrwxrwx 1 xxx xxx 0 Feb 11 21:18 net -> net:[4026532812]
lrwxrwxrwx 1 xxx xxx 0 Feb 11 21:18 pid -> pid:[4026532810]
lrwxrwxrwx 1 xxx xxx 0 Feb 11 21:18 user -> user:[4026531837]
lrwxrwxrwx 1 xxx xxx 0 Feb 11 21:18 uts -> uts:[4026532807]
每个文件都是对应的 命名空间
文件描述符,方括号里面的值是 命名空间
的 inode,如果两个进程所属 命名空间
一样,
那么它们的 inode 列表是一样的,反之亦然。如果某个 命名空间
中没有进程了,它会被自动删除,不需要手动删除。
但有个例外,如果 命名空间
对应的文件已经被某个应用进程打开,那么该 命名空间
是不会被删除的,这个特性可以使某个 命名空间
常驻,
便于后续往里面添加进程。
系统调用 API
- clone() : 实现线程的系统调用,用来创建一个新的进程,并设置它的
命名空间
- unshare() : 将进程脱离某个
命名空间
- setns() : 将进程加入某个
命名空间
通过 命名空间
为进程实现了资源隔离,但是 命名空间
无法提供物理资源的使用限制,比如 CPU 或者 内存,如果一台主机上面多个运行的容器中,
有一个容器疯狂抢占内存资源,那么其他容器都会受到影响。如何对多个容器进行物理资源限制,就要用到接下来介绍的 CGroups
。
CGroups
CGroup
全称 Linux Control Group
, 是 Linux 内核的一个功能,用来 限制、控制与分离一个进程组群的资源(如 CPU、内存、磁盘输入输出等),
除此之外,还可以为资源设置权重、计算使用量、操控进程启动和停止等。
主要功能
CGroup
从设计之初使命就很明确,为进程提供资源控制,它主要的功能包括:
- 资源限制 (Resource Limitation):可以对进程组使用的资源总额进行限制,如设定进程运行时使用内存的上限,一旦超过这个配额就发出 OOM(Out of Memory)
- 优先级分配(Prioritization):通过分配的 CPU 时间片数量及硬盘 IO 带宽大小,相当于控制了进程运行的优先级
- 资源统计 (Accounting): 可以统计系统的资源使用量,如 CPU 使用时长、内存用量等,非常适用于计费功能
- 进程控制 (Control):可以对进程组执行挂起、恢复等操作
常见的操作有:
- 隔离一个进程集合(比如:Nginx 的所有进程),并限制他们所消费的资源,比如绑定 CPU 的核数
- 为一组进程分配足够使用的内存
- 为一组进程分配相应的网络带宽和磁盘存储限制
- 限制访问某些设备(通过设置设备的白名单)
核心概念
- task:任务,对应于系统中运行的一个实体,一般是指进程
- subsystem:子系统,具体的资源控制器,控制某个特定的资源使用,比如 CPU 子系统可以控制 CPU 时间,内存子系统可以控制内存使用量
- CGroup:控制组,一组任务和子系统的关联关系,表示对这些任务进行怎样的资源管理策略。一个任务可以加入某个 CGroup,也可以从某个 CGroup 迁移到另外一个 CGroup
- hierarchy:层级树,一系列 CGroup 组成的树形结构。每个节点都是一个 CGroup,CGroup 可以有多个子节点,子节点默认会继承父节点的属性,系统中可以有多个 hierarchy
subsystem (子资源系统)
subsystem
实际上就是 CGroup
的资源控制系统,每种 subsystem
独立地控制一种资源。
- Block IO(blkio):限制块设备(磁盘、SSD、USB 等)的 IO 速率
- CPU Set(cpuset):限制任务能运行在哪些 CPU 核上
- CPU Accounting(cpuacct):生成 CGroup 中任务使用 CPU 的报告
- CPU (CPU):限制调度器分配的 CPU 时间
- Devices (devices):允许或者拒绝 CGroup 中任务对设备的访问
- Freezer (freezer):挂起或者重启 CGroup 中的任务
- Memory (memory):限制 CGroup 中任务使用内存的量,并生成任务当前内存的使用情况报告
- Network Classifier(net_cls):为 cgroup 中的报文设置特定的 classid 标志,这样 tc 等工具就能根据标记对网络进行配置
- Network Priority (net_prio):对每个网络接口设置报文的优先级
- perf_event:识别任务的 CGroup 成员,可以用来做性能分析
本质上来说,CGroup
是内核附加在程序上的一系列钩子(hooks),通过程序运行时对资源的调度触发相应的钩子以达到资源追踪和限制的目的。
UnionFS
UnionFS
将不同物理位置的目录合并到同一个目录中。
Docker
将 UnionFS
的设计理念扩展到了容器的镜像管理功能,并通过不同的存储驱动来管理镜像和容器文件。
AUFS
AUFS
的全称是 Advanced Multi-layered unification filesytem
,即 UnionFS
的升级版,它能够提供更优秀的性能和效率。
和 UnionFS
一样,它的 主要功能是:将多个目录合并为一个目录。
多个目录怎样结合成一个目录呢?具体的读写操作如下:
- 默认情况下,最上层的目录为读写层,只能有一个;下面可以有一个或者多个只读层
- 读文件,打开文件的时候使用了 O_RDONLY 选项:从最上面一个开始往下逐层去找,打开第一个找到的文件,读取其中的内容
- 写文件,打开文件时用了 O_WRONLY 或者 O_RDWR 选项
- 如果在最上层找到了该文件,直接打开,否则,从上往下开始查找,找到文件后,把文件复制到最上层,然后再打开这个 copy(所以,如果要读写的文件很大,这个过程耗时会很久)
- 删除文件:在最上层创建一个 whiteout 文件,.wh.<origin_file_name>,就是在原来的文件名字前面加上 .wh.
上面的图片非常形象地展现了合并的过程,每一个镜像层都是建立在另一个镜像层之上的,除了顶层的镜像层可读写外,其他的镜像层都是只读的,
所有的镜像都建立在一些底层基础镜像上面,比如 Kernel
, Golang
等,这种合并过程通过组合的方式提供了非常大的灵活性,
只读的镜像层通过共享能够减小镜像文件体积,减少磁盘的占用空间。
AUFS
作为联合文件系统,能够将不同目录中的层联合(Union)到同一个目录中,这些目录在 AUFS
中称作分支,整个联合的过程被称为联合挂载(Union Mount):
当我们使用 docker run
命令创建镜像时,会在镜像的最上层添加一个可写的层,也就是容器层,所有对于运行时容器的修改其实都是对这个容器读写层的修改。
容器和镜像的区别就在于,所有的镜像都是只读的,而每一个容器就等于镜像加上一个可读写的层,也就是同一个镜像可以对应多个容器。
各发行版推荐存储驱动
AUFS
只是 Docker
使用的存储驱动的其中一种,除了 AUFS
之外,Docker
还支持了不同的存储驱动。在最新版的 Docker
中,overlay2
取代 AUFS
成为了推荐存储驱动。
Linux 发行版 | 推荐存储驱动 | 备选存储驱动 |
---|---|---|
Ubuntu | overlay2 |
overlay , devicemapper , aufs , zfs , vfs |
Debian | overlay2 |
overlay , devicemapper , aufs , vfs |
CentOS | overlay2 |
overlay , devicemapper , zfs , vfs |
Fedora | overlay2 |
overlay , devicemapper , zfs , vfs |
SLES 15 | overlay2 |
overlay , devicemapper , vfs |
RHEL | overlay2 |
overlay , devicemapper , vfs |
查看当前存储驱动
$ docker info | grep -i storage
Storage Driver: overlay2
小结
本文简单介绍了 Docker
背后的基础支撑技术,没有深入探索底层 API 和具体实践,感兴趣的读者可以参考扩展阅读文章列表自己动手打造 “山寨版 Docker” :-)
Reference
- DOCKER基础技术:LINUX CGROUP
- DOCKER基础技术:AUFS
- Docker 背后的内核知识——Namespace 资源隔离
- Docker 背后的内核知识——CGroups 资源限制
- Docker storage drivers
- Kernel Korner - Unionfs: Bringing Filesystems Together
- Docker 核心技术与实现原理
- Docker——容器与容器云
- docker 容器基础技术:linux namespace 简介
- docker 容器基础技术:linux cgroup 简介
- aufs 简介以及在 docker 中的使用
- Shell 开发的一个简易 docker
- 自己动手写Docker
- Linux Cgroup 入门系列