一篇搞懂容器技术的基石: cgroup
对于容器技术而言,它实现资源层面上的限制和隔离,依赖于 Linux 内核所提供的 cgroup 和 namespace 技术。
我们先对这两项技术的作用做个概括:
- cgroup 的主要作用:管理资源的分配、限制;
- namespace 的主要作用:封装抽象,限制,隔离,使命名空间内的进程看起来拥有他们自己的全局资源;
本篇,我们重点来聊 cgroup
什么是 cgroup
cgroup 是 Linux 内核的一个功能,用来限制、控制与分离一个进程组的资源(如CPU、内存、磁盘输入输出等)。它是由 Google 的两位工程师进行开发的,自 2008 年 1 月正式发布的 Linux 内核 v2.6.24 开始提供此能力。
cgroup 到目前为止,有两个大版本, cgroup v1 和 v2 。以下内容以 cgroup v2 版本为主,涉及两个版本差别的地方会在下文详细介绍。
cgroup 主要限制的资源是:
- CPU
- 内存
- 网络
- 磁盘 I/O
当我们将可用系统资源按特定百分比分配给 cgroup 时,剩余的资源可供系统上的其他 cgroup 或其他进程使用。
cgroup 的组成
cgroup 代表“控制组”,并且不会使用大写。cgroup 是一种分层组织进程的机制, 沿层次结构以受控的方式分配系统资源。我们通常使用单数形式用于指定整个特征,也用作限定符如 “cgroup controller” 。
cgroup 主要有两个组成部分:
- core - 负责分层组织过程;
- controller - 通常负责沿层次结构分配特定类型的系统资源。每个 cgroup 都有一个
cgroup.controllers
文件,其中列出了所有可供 cgroup 启用的控制器。当在cgroup.subtree_control
中指定多个控制器时,要么全部成功,要么全部失败。在同一个控制器上指定多项操作,那么只有最后一个生效。每个 cgroup 的控制器销毁是异步的,在引用时同样也有着延迟引用的问题;
所有 cgroup 核心接口文件都以 cgroup
为前缀。每个控制器的接口文件都以控制器名称和一个点为前缀。控制器的名称由小写字母和“”组成,但永远不会以“”开头。
cgroup 的归属和迁移
系统中的每个进程都属于一个 cgroup,一个进程的所有线程都属于同一个 cgroup。一个进程可以从一个 cgroup 迁移到另一个 cgroup 。进程的迁移不会影响现有的后代进程所属的 cgroup。
什么是 cgroups
当明确提到多个单独的控制组时,才使用复数形式 “cgroups” 。
cgroups 形成了树状结构。(一个给定的 cgroup 可能有多个子 cgroup 形成一棵树结构体)每个非根 cgroup 都有一个 cgroup.events
文件,其中包含 populated
字段指示 cgroup 的子层次结构是否具有实时进程。所有非根的 cgroup.subtree_control
文件,只能包含在父级中启用的控制器。
cgroups 示例
如图所示,cgroup1 中限制了使用 cpu 及 内存资源,它将控制子节点的 CPU 周期和内存分配(即,限制 cgroup2、cgroup3、cgroup4 中的cpu及内存资源分配)。cgroup2 中启用了内存限制,但是没有启用cpu的资源限制,这就导致了 cgroup3 和 cgroup4 的内存资源受 cgroup2中的 mem 设置内容的限制;cgroup3 和 cgroup4 会自由竞争在 cgroup1 的 cpu 资源限制范围内的 cpu 资源。
由此,也可以明显的看出 cgroup 资源是自上而下分布约束的。只有当资源已经从上游 cgroup 节点分发给下游时,下游的 cgroup 才能进一步分发约束资源。所有非根的 cgroup.subtree_control
文件只能包含在父节点的 cgroup.subtree_control
文件中启用的控制器内容。
那么,子节点 cgroup 与父节点 cgroup 是否会存在内部进程竞争的情况呢?
当然不会。cgroup v2 中,设定了非根 cgroup 只能在没有任何进程时才能将域资源分发给子节点的 cgroup。简而言之,只有不包含任何进程的 cgroup 才能在其 cgroup.subtree_control
文件中启用域控制器,这就保证了,进程总在叶子节点上。
cgroup 和容器的联系
这里我们以 Docker 为例。 创建一个容器,并对其可使用的 CPU 和内存进行限制
➜ ~ docker run --rm -d --cpus=2 --memory=2g --name=2c2g redis:alpine e420a97835d9692df5b90b47e7951bc3fad48269eb2c8b1fa782527e0ae91c8e ➜ ~ cat /sys/fs/cgroup/system.slice/docker-`docker ps -lq --no-trunc`.scope/cpu.max 200000 100000 ➜ ~ cat /sys/fs/cgroup/system.slice/docker-`docker ps -lq --no-trunc`.scope/memory.max 2147483648 ➜ ~ ➜ ~ docker run --rm -d --cpus=0.5 --memory=0.5g --name=0.5c0.5g redis:alpine 8b82790fe0da9d00ab07aac7d6e4ef2f5871d5f3d7d06a5cdb56daaf9f5bc48e ➜ ~ cat /sys/fs/cgroup/system.slice/docker-`docker ps -lq --no-trunc`.scope/cpu.max 50000 100000 ➜ ~ cat /sys/fs/cgroup/system.slice/docker-`docker ps -lq --no-trunc`.scope/memory.max 536870912