容器与宿主机资源隔离:cgroups 深入理解

容器与宿主机资源隔离:Cgroups 深入理解 (一场关于资源管理的“楚门的世界”)

各位技术大佬、准大佬们,早上好/下午好/晚上好!(取决于你在哪个时区,以及你有没有熬夜加班 😅)。今天咱们要聊聊一个既神秘又重要的东西,它就像电影《楚门的世界》一样,为容器创造了一个“虚拟现实”,让容器们以为自己拥有整个世界,但实际上,它们的一举一动都在老大哥的监视和控制之下。

这个老大哥,就是我们今天的主角: Cgroups (Control Groups)

想象一下,你是一个房东,手里有很多间房子,租给不同的租客(容器)。有些租客喜欢疯狂下载电影,把你的带宽占满;有些租客喜欢挖矿,CPU 烧得滚烫;还有些租客,内存就像无底洞,恨不得把你的房子都塞满。

如果没有 Cgroups,你的房子(宿主机)就会乱成一锅粥,其他租客怨声载道,你作为房东也焦头烂额。但是有了 Cgroups,你就可以给每个租客划定资源边界,限制他们的带宽、CPU、内存使用,甚至 I/O 读写速度,保证整个大楼的和谐稳定。

所以,Cgroups 的本质,就是一个资源管理的利器,它允许我们对进程进行分组管理,并为每个组分配特定的资源配额,从而实现容器与宿主机以及容器之间的资源隔离。

那么,Cgroups 到底是什么?它又是如何工作的呢?

1. Cgroups:资源隔离的“结界”

Cgroups,全称 Control Groups,是 Linux 内核提供的一种机制,用于限制、记录和隔离进程组(process groups)所使用的物理资源(例如 CPU、内存、磁盘 I/O 等)。

你可以把 Cgroups 想象成一个神奇的“结界”,它将容器包裹起来,限制其活动范围。这个结界并非物理上的隔离,而是逻辑上的限制。容器仍然运行在宿主机的内核上,但 Cgroups 确保它们只能使用分配给它们的资源,而不会影响到其他容器或宿主机本身。

Cgroups 的主要功能包括:

  • 资源限制 (Resource Limiting): 限制一个进程组可以使用的资源总量。例如,限制 CPU 使用率、内存使用量、磁盘 I/O 速度等。
  • 优先级分配 (Prioritization): 允许为不同的进程组分配不同的资源使用优先级。例如,可以给关键服务分配更高的 CPU 优先级。
  • 资源统计 (Accounting): 统计一个进程组使用的资源量。例如,可以统计 CPU 使用时间、内存使用量、磁盘 I/O 次数等。
  • 隔离 (Isolation): 隔离进程组,防止它们相互干扰。例如,可以防止一个进程组占用过多的 CPU 资源,导致其他进程组性能下降。
  • 控制 (Control): 可以对进程组进行控制,例如冻结、恢复、重启等。

Cgroups 的核心概念:

  • Cgroup 树 (Cgroup Tree): Cgroups 采用树状结构组织,每个节点就是一个 Cgroup。 父 Cgroup 可以继承子 Cgroup 的资源限制,也可以设置自己的限制。
  • 子系统 (Subsystems): 子系统是 Cgroups 的具体实现模块,负责管理特定类型的资源。 常见的子系统包括 cpu, memory, blkio, cpuset 等。每个子系统都对应一种资源类型,并提供相应的控制接口。
  • 任务 (Tasks): 任务是指运行在 Cgroup 中的进程。可以将一个或多个进程添加到同一个 Cgroup 中,从而对这些进程进行统一管理。

2. Cgroups 的“魔法”:子系统(Subsystems)

子系统是 Cgroups 的核心组成部分,它们负责管理特定类型的资源。 每个子系统都提供一组控制接口,允许我们对 Cgroup 的资源使用进行限制、统计和控制。

你可以把子系统想象成 Cgroups 的“器官”,每个器官负责处理不同的资源。

常见的 Cgroups 子系统:

子系统 功能描述
cpu 限制 CPU 使用率。可以限制进程组的 CPU 使用时间、CPU 核数等。
memory 限制内存使用量。可以限制进程组的内存使用上限、Swap 使用量等。
blkio 限制块设备 I/O。可以限制进程组的磁盘 I/O 速度、I/O 优先级等。
cpuset 限制 CPU 亲和性。可以将进程组绑定到特定的 CPU 核上运行。
devices 控制设备访问权限。可以允许或禁止进程组访问特定的设备。
freezer 冻结和恢复进程组。可以暂停进程组的执行,并在需要时恢复执行。
net_cls 标记网络数据包,用于流量控制。
net_prio 设置网络流量优先级。
pids 限制进程组可以创建的进程数量。
hugetlb 限制 HugeTLB 页面的使用。
perf_event 允许使用 perf 事件对 Cgroup 进行性能分析。
rdma 限制 RDMA 资源的使用。
unified Cgroups v2 的统一控制接口。

举个例子:cpu 子系统

cpu 子系统允许我们限制进程组的 CPU 使用率。它提供了一些控制文件,例如:

  • cpu.shares: 相对 CPU 权重,数值越大,分配到的 CPU 时间越多。
  • cpu.cfs_period_us: CPU 时间片的周期长度,单位为微秒。
  • cpu.cfs_quota_us: 进程组在一个周期内可以使用的 CPU 时间,单位为微秒。

通过调整这些参数,我们可以精确地控制进程组的 CPU 使用情况。

再举个例子:memory 子系统

memory 子系统允许我们限制进程组的内存使用量。它提供了一些控制文件,例如:

  • memory.limit_in_bytes: 进程组可以使用的最大内存量,单位为字节。
  • memory.memsw.limit_in_bytes: 进程组可以使用的最大内存 + Swap 总量,单位为字节。
  • memory.kmem.limit_in_bytes: 进程组可以使用的内核内存量,单位为字节。

如果进程组的内存使用量超过了 memory.limit_in_bytes,内核会触发 OOM (Out Of Memory) 杀手,杀死进程组中的某个进程。

3. Cgroups 的使用方法:从“命令行”到“API”

Cgroups 的使用方法有很多种,最直接的方式是通过命令行工具手动创建和配置 Cgroup。 这种方式比较繁琐,但可以帮助我们深入理解 Cgroups 的工作原理。

1. 挂载 Cgroups 文件系统:

首先,我们需要将 Cgroups 文件系统挂载到文件系统中的某个目录。 通常情况下,我们会将 Cgroups 文件系统挂载到 /sys/fs/cgroup 目录下。

   mount -t cgroup -o subsystems=cpu,memory cgroup /sys/fs/cgroup

这个命令会将 cpumemory 子系统挂载到 /sys/fs/cgroup 目录下。

2. 创建 Cgroup:

在挂载 Cgroups 文件系统后,我们就可以创建 Cgroup 了。 创建 Cgroup 实际上就是在 /sys/fs/cgroup 目录下创建一个子目录。

   mkdir /sys/fs/cgroup/my_cgroup

这个命令会在 /sys/fs/cgroup 目录下创建一个名为 my_cgroup 的 Cgroup。

3. 配置 Cgroup:

创建 Cgroup 后,我们需要配置 Cgroup 的资源限制。 这需要修改 Cgroup 目录下的控制文件。

例如,要限制 my_cgroup 的 CPU 使用率为 50%,可以执行以下命令:

   echo 50000 > /sys/fs/cgroup/cpu/my_cgroup/cpu.cfs_quota_us
   echo 100000 > /sys/fs/cgroup/cpu/my_cgroup/cpu.cfs_period_us

这两个命令将 cpu.cfs_quota_us 设置为 50000,cpu.cfs_period_us 设置为 100000,这意味着 my_cgroup 中的进程在一个 100ms 的周期内最多可以使用 50ms 的 CPU 时间。

要限制 my_cgroup 的内存使用量为 1GB,可以执行以下命令:

   echo 1073741824 > /sys/fs/cgroup/memory/my_cgroup/memory.limit_in_bytes

这个命令将 memory.limit_in_bytes 设置为 1073741824 字节,即 1GB。

4. 将进程添加到 Cgroup:

配置 Cgroup 后,我们需要将进程添加到 Cgroup 中。 这需要将进程的 PID 写入 Cgroup 目录下的 tasks 文件。

   echo <pid> > /sys/fs/cgroup/cpu/my_cgroup/tasks

<pid> 替换为要添加到 Cgroup 的进程的 PID。

5. 删除 Cgroup:

当不再需要某个 Cgroup 时,可以将其删除。 删除 Cgroup 实际上就是删除 Cgroup 目录。

   rmdir /sys/fs/cgroup/my_cgroup

在删除 Cgroup 之前,需要确保 Cgroup 中没有运行任何进程。

Cgroups 的 API:libcg

除了命令行工具,我们还可以使用 Cgroups 的 API (libcg) 来进行 Cgroup 管理。 libcg 提供了一组 C 函数,允许我们以编程的方式创建、配置和管理 Cgroups。

使用 libcg 可以更加灵活地控制 Cgroups,并将其集成到自己的应用程序中。

Docker 和 Cgroups:天生一对

Docker 容器技术与 Cgroups 紧密集成。 Docker 使用 Cgroups 来限制容器的资源使用,从而实现容器的资源隔离。

当你运行一个 Docker 容器时,Docker 会自动创建一个 Cgroup,并将容器中的所有进程添加到该 Cgroup 中。 Docker 还会根据你在 docker run 命令中指定的资源限制(例如 -m--cpus 参数)来配置 Cgroup 的资源限制。

例如,以下命令会创建一个名为 my_container 的 Docker 容器,并限制其内存使用量为 1GB,CPU 使用率为 50%:

docker run -d --name my_container -m 1g --cpus 0.5 nginx

Docker 使用 Cgroups 来确保 my_container 容器只能使用 1GB 的内存和 50% 的 CPU 资源,而不会影响到宿主机上的其他进程。

4. Cgroups v1 vs Cgroups v2:一场“变革”

Cgroups 经历了两个主要版本:Cgroups v1 和 Cgroups v2。 Cgroups v2 是 Cgroups v1 的改进版本,它解决了 Cgroups v1 中的一些问题,并提供了一些新的特性。

Cgroups v1 的问题:

  • 复杂性: Cgroups v1 的子系统之间相互独立,配置和管理比较复杂。
  • 资源竞争: 不同的子系统可能对同一个资源进行竞争,导致资源分配不公平。
  • 功能重复: 不同的子系统可能提供类似的功能,导致功能重复。

Cgroups v2 的改进:

  • 统一的层级结构: Cgroups v2 使用单一的层级结构,简化了配置和管理。
  • 资源分配公平性: Cgroups v2 解决了资源竞争问题,确保资源分配公平。
  • 功能精简: Cgroups v2 精简了子系统,消除了功能重复。
  • 更好的容器支持: Cgroups v2 提供了更好的容器支持,例如支持嵌套容器。

Cgroups v1 和 Cgroups v2 的比较:

特性 Cgroups v1 Cgroups v2
层级结构 多个独立的层级结构 单一的层级结构
子系统 多个独立的子系统 精简的子系统
资源竞争 存在资源竞争问题 解决了资源竞争问题
容器支持 支持有限 更好的容器支持
配置和管理 复杂 简化
默认配置 复杂,需要手动配置 更智能,开箱即用
应用场景 早期容器技术,例如 Docker (旧版本) 新一代容器技术,例如 Kubernetes (推荐)
兼容性 兼容性更好,许多旧的工具和应用程序都支持 Cgroups v1 兼容性较差,需要更新工具和应用程序才能支持 Cgroups v2

总而言之,Cgroups v2 更加简单、高效、稳定,是未来发展的趋势。

5. Cgroups 的未来:无限可能

Cgroups 作为 Linux 内核的关键组成部分,在容器技术和云计算领域发挥着至关重要的作用。 随着容器技术的不断发展,Cgroups 的应用场景也将越来越广泛。

Cgroups 的未来发展方向:

  • 更精细的资源控制: 未来的 Cgroups 将提供更精细的资源控制能力,例如可以限制进程的 I/O 延迟、网络带宽等。
  • 更智能的资源调度: 未来的 Cgroups 将能够根据应用程序的需求动态调整资源分配,提高资源利用率。
  • 更安全的容器隔离: 未来的 Cgroups 将提供更安全的容器隔离机制,防止容器逃逸和恶意攻击。
  • 与硬件加速器的集成: 未来的 Cgroups 将与硬件加速器(例如 GPU、FPGA)集成,为应用程序提供更强大的计算能力。
  • 支持 Serverless 计算: Cgroups 将在 Serverless 计算中发挥重要作用,为每个函数提供隔离的运行环境。

总结:

Cgroups 就像一个精密的“资源调度师”,它默默地守护着我们的系统,确保每个容器都能获得公平的资源分配,防止资源争抢和系统崩溃。

希望通过今天的讲解,大家对 Cgroups 有了更深入的了解。 掌握 Cgroups 的原理和使用方法,可以帮助我们更好地理解容器技术,并构建更加稳定、高效的应用程序。

下次再见啦!👋 (希望你们没有听睡着 😴)

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注