Systemd Cgroup 驱动详解

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

如果输出中同时有 cgroupcgroup2,说明系统同时支持两个版本。但这不代表当前正在使用哪个版本,需要进一步确认。

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.sharesCPU 时间分配的相对权重,用于决定多个进程竞争 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_uscpu.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,避免多方操作冲突,使资源管理更加规范和可控。


十一、参考资源


相关推荐
向日葵.1 小时前
linux & qnx & git 命令 2
linux·运维·git
‎ദ്ദിᵔ.˛.ᵔ₎1 小时前
linux的vim编辑器
linux
极客先躯1 小时前
高级java每日一道面试题-2026年02月04日-实战篇[Docker]-如何在容器之间共享数据?
java·运维·网络·docker·容器·自动化·高级面试题
用户805533698031 小时前
嵌入式Linux开发——烧写你的镜像:存储介质基础 - 先分清 SD、eMMC 和块设备
linux·嵌入式
Android系统攻城狮1 小时前
Linux Pulseaudio深度解析之pa_context_set_sink_mute_by_index用流程与实战(四十七)
linux·运维·服务器·音频进阶·pulseaudio进阶
木白CPP2 小时前
aarch64-linux-gnu* (gcc,ld,objcopy,objdump)工具总结
linux·运维·gnu
豆是浪个2 小时前
Linux(Centos 7.6)命令详解:xargs
linux·运维·服务器
艾莉丝努力练剑2 小时前
【Linux网络】网络层IP协议(二):网段划分
linux·运维·服务器·网络·tcp/ip·udp