Systemd Cgroup 驱动详解
一、从问题出发
1.1 一个真实的场景
假设你在一台服务器上运行了 10 个容器:
┌─────────────────────────────────────────────────────────────┐
│ 服务器资源:4 核 CPU、8GB 内存 │
│ │
│ 容器1:Nginx → 需要限制 0.5 核、512MB │
│ 容器2:MySQL → 需要限制 2 核、4GB │
│ 容器3:Redis → 需要限制 1 核、1GB │
│ ... │
│ 容器10:监控服务 → 需要限制 0.5 核、256MB │
│ │
│ 问题: │
│ 1. 如何限制每个容器使用的 CPU 和内存? │
│ 2. 如何防止某个容器耗尽所有资源? │
│ 3. 如何统计每个容器的资源使用情况? │
│ 4. 如何在容器崩溃时自动重启? │
└─────────────────────────────────────────────────────────────┘
答案就是:Cgroup
二、Cgroup 是什么?
2.1 定义
Cgroup(Control Group,控制组) 是 Linux 内核提供的一种机制,用于限制、记录和隔离进程组使用的物理资源。
2.2 核心功能
┌─────────────────────────────────────────────────────────────┐
│ Cgroup 四大功能 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 资源限制(Resource Limitation) │ │
│ │ 限制进程组能使用的资源上限 │ │
│ │ 例如:最多使用 2GB 内存 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 2. 优先级分配(Prioritization) │ │
│ │ 分配 CPU、磁盘 IO 等资源的权重 │ │
│ │ 例如:高优先级进程获得更多 CPU 时间 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 3. 资源统计(Accounting) │ │
│ │ 统计进程组使用的资源量 │ │
│ │ 例如:统计 CPU 使用时间、内存使用峰值 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 4. 进程控制(Control) │ │
│ │ 冻结进程组或重启进程组 │ │
│ │ 例如:冻结某个容器的所有进程 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
2.3 子系统(Subsystem)
Cgroup 通过子系统来管理不同类型的资源:
| 子系统 | 名称 | 功能 |
|---|---|---|
| cpu | CPU | 限制 CPU 使用时间 |
| cpuacct | CPU 统计 | 统计 CPU 使用情况 |
| cpuset | CPU 亲和性 | 绑定到特定 CPU 核心 |
| memory | 内存 | 限制内存使用量 |
| blkio | 块设备 IO | 限制磁盘读写速率 |
| devices | 设备访问 | 控制设备访问权限 |
| freezer | 冻结 | 暂停/恢复进程 |
| net_cls | 网络分类 | 标记网络数据包 |
| pids | 进程数 | 限制进程数量 |
2.4 Cgroup 版本
┌─────────────────────────────────────────────────────────────┐
│ Cgroup 版本演进 │
│ │
│ Cgroup v1(2008年引入) │
│ ├── 每个子系统独立挂载 │
│ ├── /sys/fs/cgroup/memory/ │
│ ├── /sys/fs/cgroup/cpu/ │
│ └── 问题:资源管理分散、进程可能属于不同 cgroup │
│ │
│ Cgroup v2(2016年引入,Linux 4.5+) │
│ ├── 统一层级结构 │
│ ├── /sys/fs/cgroup/(统一挂载点) │
│ ├── 线程模式改进 │
│ └── 优点:统一管理、更好的资源控制 │
└─────────────────────────────────────────────────────────────┘
2.5 查看 Cgroup 版本
2.5.1 查看系统支持的版本
bash
# 查看系统支持哪些 cgroup 文件系统类型
cat /proc/filesystems | grep cgroup
# 示例输出:
# nodev cgroup ← 支持 Cgroup v1
# nodev cgroup2 ← 支持 Cgroup v2
如果输出中同时有 cgroup 和 cgroup2,说明系统同时支持两个版本。但这不代表当前正在使用哪个版本,需要进一步确认。
2.5.2 查看当前实际使用的版本
方法一:查看挂载情况(最准确)
bash
# 查看 cgroup 挂载情况
mount | grep cgroup
# 如果看到以下输出,说明使用的是 Cgroup v2(统一挂载)
# cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
# 如果看到多个独立的子系统挂载,说明使用的是 Cgroup v1
# tmpfs on /sys/fs/cgroup type tmpfs (rw...)
# cgroup on /sys/fs/cgroup/memory type cgroup (rw,memory)
# cgroup on /sys/fs/cgroup/cpu type cgroup (rw,cpu)
# cgroup on /sys/fs/cgroup/cpuacct type cgroup (rw,cpuacct)
方法二:检查关键文件
bash
# 查看 /sys/fs/cgroup 目录结构
ls -la /sys/fs/cgroup/
# Cgroup v2 特征:目录下有 cgroup.controllers 文件
# -rw-r--r-- 1 root root 0 cgroup.controllers
# -rw-r--r-- 1 root root 0 cgroup.max.depth
# drwxr-xr-x 2 root root 0 system.slice/
# Cgroup v1 特征:目录下是 memory/、cpu/ 等子目录
# drwxr-xr-x 2 root root 0 blkio/
# drwxr-xr-x 2 root root 0 cpu/
# drwxr-xr-x 2 root root 0 memory/
方法三:一行命令快速判断
bash
# 判断当前使用的 cgroup 版本
if [ -f /sys/fs/cgroup/cgroup.controllers ]; then
echo "当前使用 Cgroup v2(统一层级)"
else
echo "当前使用 Cgroup v1(传统层级)"
fi
# 或者更简洁的写法
ls /sys/fs/cgroup/cgroup.controllers 2>/dev/null && echo "Cgroup v2" || echo "Cgroup v1"
2.5.3 不同 Linux 发行版的默认情况
| 发行版 | 默认 Cgroup 版本 | 说明 |
|---|---|---|
| CentOS 7 | v1 | 传统系统,仅支持 v1 |
| CentOS 8 / RHEL 8 | v1(默认),支持 v2 | 可切换到 v2 |
| Ubuntu 18.04 | v1 | LTS 版本默认 v1 |
| Ubuntu 20.04 | v2(默认) | 新版本默认使用 v2 |
| Ubuntu 22.04+ | v2 | 完全使用 v2 |
| Debian 10+ | v2 | 新版本默认 v2 |
| Fedora 31+ | v2 | 默认使用 v2 |
2.5.4 混合模式(Hybrid)
某些系统可能使用混合模式:部分子系统用 v1,部分用 v2:
bash
# 混合模式的挂载情况示例
mount | grep cgroup
# 输出可能包含:
# cgroup2 on /sys/fs/cgroup/unified type cgroup2 (rw...) ← v2 部分
# cgroup on /sys/fs/cgroup/memory type cgroup (rw,memory) ← v1 部分
# cgroup on /sys/fs/cgroup/cpu type cgroup (rw,cpu) ← v1 部分
混合模式通常是为了兼容旧工具,但不推荐在生产环境使用,建议统一使用 v2。
2.5.5 版本对 Containerd 配置的影响
┌─────────────────────────────────────────────────────────────┐
│ 不同 Cgroup 版本的配置要求 │
│ │
│ Cgroup v2 系统 │
│ ├── 必须使用:SystemdCgroup = true │
│ ├── 原因:v2 的统一层级只能由 systemd 管理 │
│ └── cgroupfs 驱动在 v2 上无法正常工作 │
│ │
│ Cgroup v1 系统 │
│ ├── 推荐使用:SystemdCgroup = true │
│ ├── 可以使用:SystemdCgroup = false(cgroupfs 驱动) │
│ └── 建议:跟随 Kubernetes 官方推荐,使用 systemd │
│ │
│ 混合模式 │
│ ├── 推荐升级到纯 v2 │
│ └── 配置应使用 systemd 驱动 │
└─────────────────────────────────────────────────────────────┘
三、Systemd 是什么?
3.1 定义
Systemd 是现代 Linux 系统的初始化系统和服务管理器,是 PID 为 1 的进程,负责:
- 启动系统服务
- 管理服务生命周期
- 挂载文件系统
- 管理 cgroup
3.2 Systemd 与 Cgroup 的关系
┌─────────────────────────────────────────────────────────────┐
│ Systemd 是 Cgroup 的"管理员" │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Systemd (PID 1) │ │
│ │ │ │ │
│ │ 管理服务和 cgroup │ │
│ │ │ │ │
│ │ ┌────────────────────┼────────────────────┐ │ │
│ │ ↓ ↓ ↓ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ nginx.service │ │ mysql.service │ │ docker.service│ │
│ │ │ (cgroup) │ │ (cgroup) │ │ (cgroup) │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
3.3 Systemd 的 Cgroup 层级结构
bash
# 查看 systemd 管理的 cgroup 层级
systemd-cgls
# 示例输出:
# Control group /:
# -.slice
# ├─user.slice
# │ └─user-0.slice
# │ └─session-1.scope
# │ └─ 1234 sshd: root@pts/0
# ├─system.slice
# │ ├─nginx.service
# │ │ └─ 5678 nginx: master process
# │ │ └─ 5679 nginx: worker process
# │ ├─docker.service
# │ │ └─ 7890 containerd
# │ └─sshd.service
# │ └─ 1234 sshd: root@pts/0
# └─init.scope
# └─ 1 /sbin/init
层级结构说明:
┌─────────────────────────────────────────────────────────────┐
│ Systemd Cgroup 树形结构 │
│ │
│ / (根 cgroup) │
│ │ │
│ ├── system.slice ← 系统服务 │
│ │ ├── nginx.service │
│ │ ├── docker.service │
│ │ ├── mysql.service │
│ │ └── containerd.service │
│ │ │
│ ├── user.slice ← 用户会话 │
│ │ └── user-1000.slice │
│ │ └── session-2.scope │
│ │ │
│ └── machine.slice ← 虚拟机和容器 │
│ └─ libvirt-qemu │
│ │
│ .slice = 切片,资源分配的边界 │
│ .service = 服务 │
│ .scope = 临时进程组 │
└─────────────────────────────────────────────────────────────┘
四、什么是 Systemd Cgroup 驱动?
4.1 两种 Cgroup 驱动
容器运行时(Docker、containerd)管理 cgroup 有两种方式:
┌─────────────────────────────────────────────────────────────┐
│ 方式一:cgroupfs 驱动 │
│ │
│ 容器运行时 → 直接操作 /sys/fs/cgroup/ 文件系统 │
│ │
│ 容器运行时自己创建目录、写入配置文件 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ /sys/fs/cgroup/memory/ │ │
│ │ └── docker/ ← Docker 自己创建 │ │
│ │ ├── container1/ │ │
│ │ │ └── memory.limit_in_bytes │ │
│ │ └── container2/ │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
├─────────────────────────────────────────────────────────────┤
│ 方式二:systemd cgroup 驱动 │
│ │
│ 容器运行时 → systemd API → systemd 管理 cgroup │
│ │
│ 容器运行时告诉 systemd 创建 slice,由 systemd 统一管理 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ /sys/fs/cgroup/system.slice/ │ │
│ │ └── docker.service/ ← systemd 创建 │ │
│ │ ├── container1.scope │ │
│ │ └── container2.scope │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
4.2 配置方式
Containerd 配置:
toml
# /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true # 使用 systemd cgroup 驱动
# SystemdCgroup = false # 使用 cgroupfs 驱动(默认)
Docker 配置:
json
# /etc/docker/daemon.json
{
"exec-opts": ["native.cgroupdriver=systemd"]
}
4.3 实际效果对比
使用 cgroupfs 驱动时:
bash
# Docker 直接在 cgroupfs 创建目录
ls /sys/fs/cgroup/memory/docker/
# 输出:
# container1/ container2/ ...
# 这些目录是 Docker 自己管理的,systemd 不知道它们的存在
使用 systemd cgroup 驱动时:
bash
# 容器在 systemd 管理的结构下
ls /sys/fs/cgroup/system.slice/docker.service/
# 输出:
# container1.scope/ container2.scope/ ...
# systemd 统一管理,可以用 systemd-cgls 查看
systemd-cgls | grep -A 10 docker
五、为什么 Kubernetes 推荐使用 Systemd Cgroup?
5.1 问题场景
┌─────────────────────────────────────────────────────────────┐
│ 问题:两个 cgroup 管理者冲突 │
│ │
│ 场景: │
│ 1. Systemd 作为 PID 1,管理所有系统服务 │
│ 2. Docker/Containerd 使用 cgroupfs 直接操作 cgroup │
│ 3. Kubelet 也需要管理 cgroup │
│ │
│ 冲突: │
│ ├── 三方同时操作 cgroup,可能互相覆盖配置 │
│ ├── 资源统计不准确 │
│ ├── 进程可能被误杀 │
│ └── cgroup 层级混乱 │
└─────────────────────────────────────────────────────────────┘
5.2 具体问题示例
bash
# 问题1:资源统计不完整
# systemd 不知道 docker 创建的 cgroup,统计不到容器资源
# 问题2:OOM(内存不足)行为异常
# docker 设置了 memory.limit,但 systemd 可能有不同配置
# 导致进程被意外 OOM Kill
# 问题3:cgroup 目录孤立
# docker 停止容器后,cgroup 目录可能残留
ls /sys/fs/cgroup/memory/docker/dead_container/
# 问题4:系统监控工具无法正确统计
# top、htop 等工具依赖 systemd 的 cgroup 信息
5.3 统一使用 Systemd Cgroup 的好处
┌─────────────────────────────────────────────────────────────┐
│ 统一使用 Systemd Cgroup 驱动的好处 │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 1. 单一 cgroup 管理者 │ │
│ │ systemd 统一管理所有进程组的资源 │ │
│ │ 避免 docker、kubelet、systemd 三方冲突 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 2. 资源统计准确 │ │
│ │ systemd-cgtop 可以正确显示所有进程资源使用 │ │
│ │ 包括容器进程 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 3. 生命周期管理 │ │
│ │ 服务停止时,systemd 自动清理相关 cgroup │ │
│ │ 不会有残留目录 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 4. 权限隔离 │ │
│ │ 容器进程在独立的 slice 中 │ │
│ │ 更安全 │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 5. 与 Kubernetes 一致 │ │
│ │ Kubelet 默认使用 systemd cgroup │ │
│ │ 保持一致性,减少问题 │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
5.4 架构对比图
混乱的架构(cgroupfs):
┌─────────────────────────────────────────────────────────────┐
│ 混乱的架构 │
│ │
│ Systemd ──────→ 管理 system.slice 下的服务 │
│ │ │
│ └── sshd.service │
│ │
│ Docker ────────→ 直接操作 /sys/fs/cgroup/docker/ │
│ │ │
│ └── container1/ │
│ │
│ Containerd ────→ 直接操作 /sys/fs/cgroup/containerd/ │
│ │ │
│ └── container2/ │
│ │
│ Kubelet ───────→ 自己管理一部分 cgroup │
│ │ │
│ └── pod123/ │
│ │
│ 问题:各方各自为政,层级混乱,可能冲突 │
└─────────────────────────────────────────────────────────────┘
统一的架构(systemd cgroup):
┌─────────────────────────────────────────────────────────────┐
│ 统一的架构 │
│ │
│ Systemd (PID 1) │
│ │ │
│ ┌─────────────┼─────────────┐ │
│ ↓ ↓ ↓ │
│ system.slice user.slice machine.slice │
│ │ │
│ ┌─────┴─────┐ │
│ ↓ ↓ │
│ sshd.service docker.service │
│ │ │
│ ┌──────┴──────┐ │
│ ↓ ↓ │
│ container1.scope container2.scope │
│ │
│ 优点:所有 cgroup 由 systemd 统一管理,层级清晰 │
└─────────────────────────────────────────────────────────────┘
六、实际应用场景
6.1 场景一:Kubernetes 集群
┌─────────────────────────────────────────────────────────────┐
│ Kubernetes 节点资源管理 │
│ │
│ 要求: │
│ - Kubelet、Docker、Containerd 使用相同的 cgroup 驱动 │
│ - 推荐全部使用 systemd │
│ │
│ 配置检查: │
│ 1. Kubelet: --cgroup-driver=systemd │
│ 2. Docker: "native.cgroupdriver=systemd" │
│ 3. Containerd: SystemdCgroup = true │
│ │
│ 如果不一致会怎样? │
│ - Kubelet 无法正确统计容器资源 │
│ - Pod 驱逐策略失效 │
│ - 节点资源超卖 │
└─────────────────────────────────────────────────────────────┘
6.2 场景二:资源限制
bash
# 使用 systemd 运行服务并限制资源
# 创建服务文件
vim /etc/systemd/system/myapp.service
# 内容:
[Unit]
Description=My Application
[Service]
ExecStart=/usr/bin/myapp
# CPU 限制:最多使用 1.5 个 CPU 核心
CPUQuota=150%
# 内存限制:最多使用 2GB
MemoryMax=2G
# 内存软限制
MemoryHigh=1.5G
[Install]
WantedBy=multi-user.target
# 启动服务
systemctl daemon-reload
systemctl start myapp
# 查看资源使用
systemctl status myapp
systemd-cgtop
6.3 场景三:容器资源监控
bash
# 使用 systemd 统一查看资源
# 查看所有服务资源使用
systemd-cgtop
# 输出示例:
# Control Group Tasks %CPU Memory Input/s Output/s
# / 200 15.0 4.0G - -
# system.slice 150 12.0 2.0G - -
# ├─docker.service 50 8.0 1.5G - -
# │ ├─container1.scope 25 4.0 800.0M - -
# │ └─container2.scope 25 4.0 700.0M - -
# ├─nginx.service 5 0.5 100.0M - -
# └─sshd.service 2 0.0 10.0M - -
# user.slice 10 0.5 500.0M - -
6.4 场景四:容器 OOM 保护
bash
# 设置容器不被 OOM Killer 杀死
# 查看当前 OOM 分数
cat /proc/<pid>/oom_score
# 设置 OOM 分数调整值(-1000 到 1000)
# -1000 表示永远不会被 OOM Kill
echo -1000 > /proc/<pid>/oom_score_adj
# 在 systemd 服务中配置
[Service]
OOMScoreAdjust=-500
# 在 Kubernetes Pod 中配置
# oomScoreAdj: -500
七、Cgroup 操作实践
7.1 查看 Cgroup 信息
bash
# 查看进程的 cgroup
cat /proc/<pid>/cgroup
# 示例输出:
# 0::/system.slice/docker.service/container1.scope
# 查看内存 cgroup 配置
cat /sys/fs/cgroup/memory/system.slice/docker.service/memory.limit_in_bytes
# 查看 CPU cgroup 配置
cat /sys/fs/cgroup/cpu/system.slice/docker.service/cpu.shares
# 使用 systemd 命令查看
systemctl show docker.service --property=ControlGroup
systemctl show docker.service --property=MemoryCurrent
systemctl show docker.service --property=CPUUsageNSec
7.2 手动操作 Cgroup(cgroupfs 方式)
bash
# 创建 cgroup 组
mkdir /sys/fs/cgroup/memory/mygroup
# 设置内存限制(100MB)
echo 104857600 > /sys/fs/cgroup/memory/mygroup/memory.limit_in_bytes
# 将进程加入 cgroup
echo <pid> > /sys/fs/cgroup/memory/mygroup/tasks
# 查看当前使用
cat /sys/fs/cgroup/memory/mygroup/memory.usage_in_bytes
# 删除 cgroup
rmdir /sys/fs/cgroup/memory/mygroup
7.3 使用 Systemd 操作 Cgroup
bash
# 创建临时 scope
systemd-run --scope --unit=myapp.scope sleep 100
# 查看 cgroup
systemd-cgls | grep -A 5 myapp
# 设置资源限制
systemctl set-property myapp.scope MemoryMax=500M
# 查看属性
systemctl show myapp.scope
7.4 CPU 子系统参数详解
7.4.1 cpu.shares - CPU 时间分配权重
查看 CPU 权重配置:
bash
# 查看 docker 服务的 CPU shares
cat /sys/fs/cgroup/cpu/system.slice/docker.service/cpu.shares
# 输出示例:1024
cpu.shares 的含义:
cpu.shares 是 CPU 时间分配的相对权重,用于决定多个进程竞争 CPU 时,各自能获得多少 CPU 时间。
┌─────────────────────────────────────────────────────────────┐
│ cpu.shares 的本质 │
│ │
│ 它是一个"相对值",不是"绝对值" │
│ │
│ ❌ 错误理解:1024 = 可以使用 1024 个 CPU │
│ ❌ 错误理解:1024 = 可以使用 1024% 的 CPU │
│ │
│ ✅ 正确理解:1024 = 默认权重,用于和其他进程比较 │
└─────────────────────────────────────────────────────────────┘
1024 是默认值 ,表示标准权重,即没有特殊的 CPU 优先级,与其他默认进程公平竞争 CPU。
实际工作原理示例:
┌─────────────────────────────────────────────────────────────┐
│ 场景:单核 CPU,3 个容器同时满负载运行 │
│ │
│ 容器A:cpu.shares = 512 │
│ 容器B:cpu.shares = 1024 │
│ 容器C:cpu.shares = 2048 │
│ │
│ 总权重 = 512 + 1024 + 2048 = 3584 │
│ │
│ CPU 分配: │
│ 容器A 获得:512/3584 ≈ 14.3% 的 CPU 时间 │
│ 容器B 获得:1024/3584 ≈ 28.6% 的 CPU 时间 │
│ 容器C 获得:2048/3584 ≈ 57.1% 的 CPU 时间 │
│ │
│ 结论: │
│ - 容器C 的权重是 B 的 2 倍,所以获得 2 倍 CPU 时间 │
│ - 容器B 的权重是 A 的 2 倍,所以获得 2 倍 CPU 时间 │
└─────────────────────────────────────────────────────────────┘
常见值及含义:
| shares 值 | 含义 | 相对比例 |
|---|---|---|
| 512 | 低优先级 | 默认值的一半 |
| 1024 | 默认/标准 | 基准值 |
| 2048 | 高优先级 | 默认值的 2 倍 |
| 4096 | 更高优先级 | 默认值的 4 倍 |
重要特性:
┌─────────────────────────────────────────────────────────────┐
│ 特性 1:只在竞争时生效 │
│ │
│ 如果 CPU 空闲,即使 shares = 512,也能使用 100% CPU │
│ shares 只在多个进程争抢 CPU 时才起作用 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 特性 2:是相对值 │
│ │
│ 单独一个进程设置 shares = 2048 没有意义 │
│ 必须和其他进程比较才有意义 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 特性 3:最小值是 2 │
│ │
│ echo 1 > cpu.shares 会报错 │
│ 最小有效值是 2 │
└─────────────────────────────────────────────────────────────┘
与 Kubernetes 的关系:
在 Kubernetes 中,cpu.requests 会映射到 cpu.shares:
yaml
# Pod 配置
resources:
requests:
cpu: "500m" # 0.5 核
limits:
cpu: "1000m" # 1 核
转换规则:
cpu.shares = cpu.requests(毫核)× 1024 / 1000
示例:
cpu: "500m" → shares = 500 × 1024 / 1000 = 512
cpu: "1000m" → shares = 1000 × 1024 / 1000 = 1024(默认)
cpu: "2000m" → shares = 2000 × 1024 / 1000 = 2048
7.4.2 cpu.cfs_quota_us 与 cpu.cfs_period_us - CPU 硬限制
cpu.shares 是软限制(相对权重),而 cpu.cfs_quota_us 和 cpu.cfs_period_us 组合使用可以实现硬限制。
bash
# 查看当前 CPU 配额配置
cat /sys/fs/cgroup/cpu/docker/container_id/cpu.cfs_period_us
# 输出:100000(单位:微秒,即 100ms)
cat /sys/fs/cgroup/cpu/docker/container_id/cpu.cfs_quota_us
# 输出:-1 表示无限制
# 或 50000 表示每 100ms 可使用 50ms CPU 时间(即 0.5 核)
工作原理:
┌─────────────────────────────────────────────────────────────┐
│ CFS(完全公平调度器)配额机制 │
│ │
│ cpu.cfs_period_us = 100000 (周期:100ms) │
│ cpu.cfs_quota_us = 50000 (配额:50ms) │
│ │
│ 含义:在每个 100ms 周期内,该 cgroup 最多使用 50ms CPU 时间 │
│ │
│ 计算:50ms / 100ms = 0.5 核 CPU │
│ │
│ 这就是 Kubernetes 中 cpu.limits 的底层实现 │
└─────────────────────────────────────────────────────────────┘
常见配置示例:
| 配置 | quota | period | CPU 限制 |
|---|---|---|---|
| 无限制 | -1 | 100000 | 不限制 |
| 0.5 核 | 50000 | 100000 | 0.5 CPU |
| 1 核 | 100000 | 100000 | 1 CPU |
| 1.5 核 | 150000 | 100000 | 1.5 CPU |
| 2 核 | 200000 | 100000 | 2 CPU |
7.4.3 CPU 参数对比总结
| 参数 | 类型 | 作用 | 场景 |
|---|---|---|---|
cpu.shares |
软限制 | 相对权重,决定 CPU 时间分配比例 | 多进程竞争时生效 |
cpu.cfs_quota_us + cpu.cfs_period_us |
硬限制 | 绝对限制 CPU 使用上限 | 严格限制进程的 CPU 使用 |
┌─────────────────────────────────────────────────────────────┐
│ 软限制 vs 硬限制 │
│ │
│ 软限制(shares): │
│ ├── 像"优先级",CPU 空闲时可以超过 │
│ ├── 只在竞争时生效 │
│ └── 对应 Kubernetes 的 cpu.requests │
│ │
│ 硬限制(quota): │
│ ├── 像"天花板",绝对不能超过 │
│ ├── 始终生效 │
│ └── 对应 Kubernetes 的 cpu.limits │
└─────────────────────────────────────────────────────────────┘
7.5 Memory 子系统参数详解
bash
# 查看内存限制
cat /sys/fs/cgroup/memory/docker/container_id/memory.limit_in_bytes
# 输出:1073741824(1GB,单位:字节)
# 查看当前内存使用
cat /sys/fs/cgroup/memory/docker/container_id/memory.usage_in_bytes
# 查看内存+Swap 总限制
cat /sys/fs/cgroup/memory/docker/container_id/memory.memsw.limit_in_bytes
关键参数说明:
| 参数 | 说明 |
|---|---|
memory.limit_in_bytes |
内存使用硬限制 |
memory.usage_in_bytes |
当前内存使用量 |
memory.memsw.limit_in_bytes |
内存+Swap 总限制 |
memory.oom_control |
OOM Killer 控制开关 |
memory.soft_limit_in_bytes |
内存软限制 |
八、Cgroup v2 与 Cgroup v1 的区别
8.1 架构差异
┌─────────────────────────────────────────────────────────────┐
│ Cgroup v1 架构 │
│ │
│ /sys/fs/cgroup/ │
│ ├── memory/ ← 内存子系统独立挂载 │
│ │ └── docker/ │
│ ├── cpu/ ← CPU 子系统独立挂载 │
│ │ └── docker/ │
│ ├── blkio/ ← IO 子系统独立挂载 │
│ │ └── docker/ │
│ └── ... │
│ │
│ 问题:同一进程可能在不同子系统属于不同 cgroup │
│ 管理复杂,需要操作多个目录 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Cgroup v2 架构 │
│ │
│ /sys/fs/cgroup/ ← 统一挂载点 │
│ └── system.slice/ │
│ └── docker.service/ │
│ ├── memory.max ← 内存配置在同一目录 │
│ ├── cpu.max ← CPU 配置在同一目录 │
│ └── io.max ← IO 配置在同一目录 │
│ │
│ 优点:统一层级,一个进程只属于一个 cgroup │
│ 配置集中管理 │
└─────────────────────────────────────────────────────────────┘
8.2 配置文件差异
Cgroup v1:
bash
# 内存限制
/sys/fs/cgroup/memory/docker/container1/memory.limit_in_bytes
# CPU 限制
/sys/fs/cgroup/cpu/docker/container1/cpu.shares
/sys/fs/cgroup/cpu/docker/container1/cpu.cfs_quota_us
# IO 限制
/sys/fs/cgroup/blkio/docker/container1/blkio.throttle.read_bps_device
Cgroup v2:
bash
# 所有配置在同一目录
/sys/fs/cgroup/system.slice/docker.service/container1.scope/
# 内存限制
memory.max
# CPU 限制
cpu.max
# IO 限制
io.max
8.3 Containerd 对 Cgroup v2 的支持
toml
# /etc/containerd/config.toml
# Cgroup v2 配置(Linux 4.5+)
[plugins."io.containerd.grpc.v1.cri".containerd]
snapshotter = "overlayfs"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
# 注意:Cgroup v2 必须使用 systemd cgroup 驱动
九、故障排查
9.1 常见错误
错误1:cgroup 驱动不一致
bash
# Kubelet 日志
# "Failed to start ContainerManager"
# "misconfiguration: kubelet cgroup driver: \"cgroupfs\""
# 解决:统一配置
# Docker
echo '{"exec-opts": ["native.cgroupdriver=systemd"]}' > /etc/docker/daemon.json
# Containerd
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
systemctl restart containerd
错误2:无法创建 cgroup
bash
# 错误信息
# "failed to create fsnotify watcher: too many open files"
# 解决
ulimit -n 65535
# 永久解决
echo "* soft nofile 65535" >> /etc/security/limits.conf
echo "* hard nofile 65535" >> /etc/security/limits.conf
错误3:cgroup 目录残留
bash
# 症状
ls /sys/fs/cgroup/memory/docker/
# 显示已删除容器的目录
# 清理残留
find /sys/fs/cgroup/memory/docker -type d -exec rmdir {} \; 2>/dev/null
# 使用 systemd 驱动可避免此问题
9.2 诊断命令
bash
# 检查 cgroup 挂载
mount | grep cgroup
# 检查 systemd cgroup 状态
systemd-cgls --no-pager
# 查看容器资源使用
systemd-cgtop --no-pager
# 查看进程的 cgroup
cat /proc/$(pgrep -f nginx)/cgroup
# 检查 cgroup 驱动配置
# Docker
cat /etc/docker/daemon.json | grep cgroup
# Containerd
cat /etc/containerd/config.toml | grep -i cgroup
# Kubelet
ps aux | grep kubelet | grep cgroup-driver
十、总结
10.1 核心概念回顾
┌─────────────────────────────────────────────────────────────┐
│ 核心概念 │
│ │
│ Cgroup │
│ └── Linux 内核功能,限制和隔离进程资源 │
│ │
│ Systemd │
│ └── Linux 初始化系统,是 cgroup 的管理员 │
│ │
│ Systemd Cgroup 驱动 │
│ └── 容器运行时通过 systemd API 管理 cgroup │
│ │
│ Cgroupfs 驱动 │
│ └── 容器运行时直接操作 cgroup 文件系统 │
└─────────────────────────────────────────────────────────────┘
10.2 选择建议
| 场景 | 推荐驱动 | 原因 |
|---|---|---|
| Kubernetes 集群 | systemd | 与 kubelet 一致,避免冲突 |
| 生产环境 | systemd | 稳定、统一管理、易于监控 |
| 开发环境 | 均可 | 无特殊要求时默认即可 |
| Cgroup v2 系统 | systemd | v2 要求必须使用 systemd |
| 旧系统(CentOS 7) | systemd | systemd 是 PID 1,推荐统一 |
10.3 一句话总结
Systemd Cgroup 驱动让容器运行时通过 Systemd 统一管理 Cgroup,避免多方操作冲突,使资源管理更加规范和可控。
十一、参考资源
- Linux Cgroup 官方文档:https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt
- Systemd 官方文档:https://systemd.io/CGROUP_DELEGATION/
- Kubernetes Cgroup 驱动:https://kubernetes.io/docs/setup/production-environment/container-runtimes/#cgroup-drivers
- Containerd 配置指南:https://github.com/containerd/containerd/blob/main/docs/cri/config.md