Skip to content

Cgroups 完全指南

Cgroups(Control Groups)是 Linux 内核提供的功能,用于限制、记录和隔离进程组使用的物理资源(CPU、内存、磁盘 I/O 等)。它是 Docker、Kubernetes 等容器技术的核心基础。

一、Cgroups 概述

1.1 什么是 Cgroup

定义:Cgroup 是 Linux 内核的特性,能够将一组进程组织成一个或多个具有参数的层级结构(hierarchy),以限制、记录和隔离它们对系统资源的使用。

1.2 发展历史

版本发布时间特点
cgroup v1Linux 2.6.24 (2008)每个子系统独立挂载,灵活但复杂
cgroup v2Linux 4.5 (2016)统一层次结构,简化管理
默认切换RHEL 8 / Ubuntu 21.04+系统默认使用 cgroup v2

1.3 核心概念

概念说明
Task(任务)系统中的进程或线程
Cgroup(控制组)按某种标准划分的进程组
Hierarchy(层级)Cgroup 的树形组织结构
Subsystem(子系统)具体的资源控制器(如 cpu、memory)

二、Cgroup 子系统详解

2.1 子系统总览

2.2 CPU 子系统

功能

  • 限制进程组使用的 CPU 时间
  • 支持多种调度策略

关键参数(v1)

参数说明示例
cpu.cfs_period_usCFS 调度器的时间周期(微秒),默认 100000 (100ms)100000
cpu.cfs_quota_us在一个周期内可使用的 CPU 时间,-1 表示不限制50000 = 0.5 核
cpu.sharesCPU 时间片权重(相对值),默认 1024512 = 权重减半
cpu.rt_period_us实时调度周期1000000
cpu.rt_runtime_us实时调度运行时间950000

CPU 配额计算公式

实际可用 CPU 核数 = cpu.cfs_quota_us / cpu.cfs_period_us

# 示例:
# quota=200000, period=100000 → 可用 2 核
# quota=50000, period=100000 → 可用 0.5 核
# quota=-1 → 不限制(使用所有核心)

配置示例

bash
# 创建 cgroup 目录
mkdir -p /sys/fs/cgroup/cpu/myapp

# 限制最多使用 1.5 个 CPU 核心
echo 150000 > /sys/fs/cgroup/cpu/myapp/cpu.cfs_quota_us
echo 100000 > /sys/fs/cgroup/cpu/myapp/cpu.cfs_period_us

# 设置相对权重(与其他 cgroup 比较)
echo 2048 > /sys/fs/cgroup/cpu/myapp/cpu.shares

# 将进程加入 cgroup
echo 12345 > /sys/fs/cgroup/cpu/myapp/tasks

v2 参数变化

bash
# cgroup v2 使用 unified 层级
# CPU 控制在同一个目录下
mkdir -p /sys/fs/cgroup/myapp

# 最大 CPU 核心数(直接指定核数)
echo "150000 100000" > /sys/fs/cgroup/myapp/cpu.max  # 1.5 核
# 或
echo "max" > /sys/fs/cgroup/myapp/cpu.max             # 不限制

# 权重(范围 1-10000)
echo 256 > /sys/fs/cgroup/myapp/cpu.weight

2.3 Memory 子系统

功能

  • 限制进程组的物理内存使用
  • 统计内存使用情况
  • 控制 Swap 使用
  • 支持 OOM(Out of Memory)处理

关键参数(v1)

参数说明默认值
memory.limit_in_bytes内存使用上限(字节)无限
memory.soft_limit_in_bytes软限制(内存紧张时回收)无限
memory.memsw.limit_in_bytes内存 + Swap 总量上限无限
memory.swappiness使用 Swap 的倾向性(0-100)继承父级
memory.oom_controlOOM 行为控制0(启用 OOM Killer)
memory.usage_in_bytes当前内存使用量(只读)-
memory.failcnt达到限制的次数(只读)-

内存限制配置

bash
mkdir -p /sys/fs/cgroup/memory/myapp

# 限制最大使用 512MB 物理内存
echo 512M > /sys/fs/cgroup/memory/myapp/memory.limit_in_bytes

# 限制内存+Swap 总共不超过 1GB
echo 1G > /sys/fs/cgroup/memory/myapp/memory.memsw.limit_in_bytes

# 禁用 OOM Killer(OOM 时暂停而非杀死进程)
echo 1 > /sys/fs/cgroup/memory/myapp/memory.oom_control

# 降低 swap 使用倾向(0 = 尽量不用 swap)
echo 10 > /sys/fs/cgroup/memory/myapp/memory.swappiness

v2 内存参数

bash
mkdir -p /sys/fs/cgroup/myapp

# 内存硬限制
echo 512M > /sys/fs/cgroup/myapp/memory.max

# 内存软限制(回收压力下触发)
echo 384M > /sys/fs/cgroup/myapp/memory.high

# Swap 限制(设为 0 表示禁用 swap)
echo 512M > /sys/fs/cgroup/myapp/memory.swap.max

# OOM 行为
echo 1 > /sys/fs/cgroup/myapp/memory.oom.group  # OOM 时终止整个 group

# 查看 OOM 事件
cat /sys/fs/cgroup/myapp/memory.events
# 输出样例:
# low 0
# high 0
# max 0
# oom 1
# oom_kill 1

2.4 Blkio 子系统(块设备 I/O)

功能

  • 限制进程组对块设备的读写速率
  • 设置 I/O 权重(按比例分配带宽)

关键参数

参数类型参数名说明
比例分配blkio.weight所有设备的默认权重(10-1000)
blkio.weight_device指定设备的权重
节流限制blkio.throttle.read_bps_device读速率限制(字节/秒)
blkio.throttle.write_bps_device写速率限制(字节/秒)
blkio.throttle.read_iops_device读 IOPS 限制
blkio.throttle.write_iops_device写 IOPS 限制

IO 限制配置

bash
mkdir -p /sys/fs/cgroup/blkio/myapp

# 方法一:按比例分配权重
echo 500 > /sys/fs/cgroup/blkio/myapp/blkio.weight  # 默认权重

# 为特定设备设置权重(主设备号:次设备号 权重)
echo "8:0 500" > /sys/fs/cgroup/blkio/myapp/blkio.weight_device

# 方法二:绝对速率限制
# 限制 sda 设备读速率为 10MB/s
echo "8:0 10485760" > /sys/fs/cgroup/blkio/myapp/blkio.throttle.read_bps_device

# 限制写速率为 5MB/s
echo "8:0 5242880" > /sys/fs/cgroup/blkio/myapp/blkio.throttle.write_bps_device

# 限制读 IOPS 为 100
echo "8:0 100" > /sys/fs/cgroup/blkio/myapp/blkio.throttle.read_iops_device

# 限制写 IOPS 为 50
echo "8:0 50" > /sys/fs/cgroup/blkio/myapp/blkio.throttle.write_iops_device

v2 IO 参数(io.max)

bash
mkdir -p /sys/fs/cgroup/myapp

# 格式: MAJ:MIN rbps=wbps=riops=wiops
# 限制 sda (8:0) 读 10MB/s, 写 5MB/s
echo "8:0 rbps=10485760 wbps=5242880" > /sys/fs/cgroup/myapp/io.max

# 或者使用线性模式
echo "8:0 riops=100 wiops=50" > /sys/fs/cgroup/myapp/io.max

2.5 其他重要子系统

cpuset 子系统

bash
# 绑定到指定的 CPU 核心
mkdir -p /sys/fs/cgroup/cpuset/myapp

# 允许使用 CPU 0-3(前4个核心)
echo "0-3" > /sys/fs/cgroup/cpuset/myapp/cpuset.cpus

# 允许使用 NUMA 节点 0
echo "0" > /sys/fs/cgroup/cpuset/myapp/cpuset.mems

devices 子系统

bash
mkdir -p /sys/fs/cgroup/devices/myapp

# 允许访问 /dev/null(字符设备 1:3,读写权限)
echo "c 1:3 rwm" > /sys/fs/cgroup/devices/myapp/devices.allow

# 禁止访问所有块设备
echo "b *:* m" > /sys/fs/cgroup/devices/myapp/devices.deny

# 查看当前允许列表
cat /sys/fs/cgroup/devices/myapp/devices.list

pid 子系统(v2)

bash
# 限制进程数量
echo 100 > /sys/fs/cgroup/myapp/pid.max

freezer 子系统

bash
# 挂起进程组
echo FROZEN > /sys/fs/cgroup/freezer/myapp/freezer.state

# 恢复执行
echo THAWED > /sys/fs/cgroup/freezer/myapp/freezer.state

三、Cgroup V1 vs V2 对比

3.1 架构差异

3.2 主要区别

特性Cgroup V1Cgroup V2
挂载方式每个子系统独立挂载统一挂载到 /sys/fs/cgroup
层级结构多个独立层级单一统一层级
线程模型线程可以属于不同 cgroup线程模式(threaded mode)
接口文件多种命名风格统一命名规范
内存统计分散在多个文件统一的 memory.current, memory.max
默认支持需要手动挂载systemd 自动管理
容器支持Docker/K8s 支持新版本原生支持

3.3 迁移检查清单

bash
# 检查当前系统使用的 cgroup 版本
stat -f %T /sys/fs/cgroup
# 输出: cgroup2fs 表示 v2, tmpfs 表示 v1

# 或查看内核配置
grep cgroup /proc/filesystems
# nodev   cgroup
# nodev   cgroup2

# 检查是否启用了 hybrid 模式(v1+v2 共存)
ls /sys/fs/cgroup/
# 如果同时有 cpu/ memory/ blkio/ 和 unified 目录,则是混合模式

四、Docker 中的 Cgroup 应用

4.1 Docker 与 Cgroup 的关系

Docker 通过 cgroup 来实现容器的资源限制。每个 Docker 容器对应一个 cgroup。

4.2 Docker 资源限制参数

CPU 限制

bash
# 限制容器最多使用 1.5 个 CPU 核心
docker run --cpus="1.5" nginx

# 或使用 --cpu-quota(等效于 cgroup 的 cfs_quota_us)
docker run --cpu-period=100000 --cpu-quota=150000 nginx

# 设置 CPU 相对权重(默认 1024)
docker run --cpu-shares=512 nginx  # 权重为默认的一半

# 指定容器只能在某些 CPU 核心上运行
docker run --cpuset-cpus="0-3" nginx  # 使用 CPU 0-3

# 限制实时调度
docker run --cpu-rt-runtime=950000 --cpu-rt-period=1000000 nginx

内存限制

bash
# 限制内存使用量为 512MB
docker run -m 512M nginx
# 或完整写法
docker run --memory=512M nginx

# 设置软限制(内存压力时触发回收)
docker run --memory-reservation=256M nginx

# 限制内存+Swap 总量
docker run -m 512M --memory-swap=1G nginx

# 禁用 swap
docker run -m 512M --memory-swap=512M nginx

# 设置内核内存限制(TCP 缓冲区等)
docker run --kernel-memory=64M nginx

# OOM 行为
docker run --oom-kill-disable nginx  # 禁止 OOM Kill
docker run --oom-score-adj=-500 nginx  # 调整 OOM 分数

IO 限制

bash
# 限制读取速度为 10MB/s
docker run --device-read-bps=/dev/sda:10MB nginx

# 限制写入速度为 5MB/s
docker run --device-write-bps=/dev/sda:5MB nginx

# 限制读取 IOPS
docker run --device-read-iops=/dev/sda:100 nginx

# 限制写入 IOPS
docker run --device-write-iops=/dev/sda:50 nginx

# 设置 IO 权重
docker run --device-weight=/dev/sda:500 nginx

PIDs 限制

bash
# 限制容器内最大进程数为 100
docker run --pids-limit=100 nginx

4.3 查看 Docker 容器的 Cgroup

bash
# 启动一个测试容器
docker run -d --name test --cpus="1.5" -m 512M nginx

# 获取容器 ID
DOCKER_ID=$(docker inspect -f '{{.Id}}' test)

# 查看 cgroup 信息
docker inspect test | grep -i cgroup

# 直接查看 cgroup 文件
cat /sys/fs/cgroup/docker/${DOCKER_ID}/cpu.max          # CPU 限制
cat /sys/fs/cgroup/docker/${DOCKER_ID}/memory.max         # 内存限制
cat /sys/fs/cgroup/docker/${DOCKER_ID}/io.max             # IO 限制
cat /sys/fs/cgroup/docker/${DOCKER_ID}/pids.current       # 当前进程数

# 或使用 docker stats 实时监控
docker stats test

4.4 Docker Compose 资源配置

yaml
version: '3.8'
services:
  web:
    image: nginx
    deploy:
      resources:
        limits:
          cpus: '1.5'
          memory: 512M
          pids: 100
        reservations:
          cpus: '0.5'
          memory: 256M
  
  database:
    image: postgres:15
    deploy:
      resources:
        limits:
          memory: 2G
        reservations:
          memory: 1G
    # IO 限制需要额外配置

五、Kubernetes 中的 Cgroup 应用

5.1 K8s 资源模型与 Cgroup 映射

Kubernetes 通过 Pod 的 resources 字段定义资源请求和限制,最终映射到底层 cgroup。

5.2 Requests vs Limits

类型含义Cgroup 映射调度影响
requests.cpu最小保证资源cpu.weight(v2)/ cpu.shares(v1)✅ 影响调度决策
limits.cpu最大使用上限cpu.max(v2)/ cpu.cfs_quota(v1)❌ 不影响调度
requests.memory最小保证内存memory.low(v2软限制)✅ 影响调度决策
limits.memory最大使用上限memory.max(v2硬限制)❌ 不影响调度

5.3 Pod资源配置示例

yaml
apiVersion: v1
kind: Pod
metadata:
  name: resource-demo
spec:
  containers:
  - name: app
    image: nginx
    resources:
      requests:
        cpu: "250m"      # 0.25 核(millicores)
        memory: "128Mi"  # 128 MB
      limits:
        cpu: "500m"      # 0.5 核
        memory: "256Mi"  # 256 MB
  - name: sidecar
    image: busybox
    resources:
      requests:
        cpu: "100m"
        memory: "64Mi"
      limits:
        cpu: "200m"
        memory: "128Mi"

5.4 QoS 类别与 Cgroup

Kubernetes 根据 Pod 的资源配置自动分配 QoS(Quality of Service)类别:

QoS 类别条件Cgroup 行为
Guaranteed所有限制等于请求且不为 0最高优先级,不会被驱逐
Burstable至少有一个请求 < 限制中等优先级,内存不足时可被驱逐
BestEffort无任何请求或限制最低优先级,最先被驱逐

5.5 查看 Pod 的 Cgroup 配置

bash
# 获取 Pod 的 UID
POD_UID=$(kubectl get pod resource-demo -o jsonpath='{.metadata.uid}')

# 进入节点查看 cgroup(需要在 Node 上执行)
# Kubelet 管理 cgroup 路径通常在:
# /sys/fs/cgroup/kubepods/<QoS>/<pod_uid>/

# Guaranteed QoS 路径
/sys/fs/cgroup/kubepods/burstable/pod_<uid>/

# 查看具体配置
cat /sys/fs/cgroup/kubepods/burstable/pod_*/cpu.max
# 输出: 50000 100000  (0.5 核)

cat /sys/fs/cgroup/kubepods/burstable/pod_*/memory.max
# 输出: 268435456  (256MB)

# 使用 kubectl describe 查看
kubectl describe pod resource-demo | grep -A5 "Limits\|Requests"

5.6 LimitRange 与 ResourceQuota

yaml
# LimitRange - 命名空间级别的默认资源限制
apiVersion: v1
kind: LimitRange
metadata:
  name: default-limits
  namespace: default
spec:
  limits:
  - type: Container
    default:
      cpu: "500m"
      memory: "512Mi"
    defaultRequest:
      cpu: "100m"
      memory: "128Mi"
    max:
      cpu: "2"
      memory: "2Gi"
    min:
      cpu: "50m"
      memory: "64Mi"

# ResourceQuota - 命名空间级别总资源配额
apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-quota
  namespace: default
spec:
  hard:
    requests.cpu: "10"
    requests.memory: "20Gi"
    limits.cpu: "20"
    limits.memory: "40Gi"
    persistentvolumeclaims: "10"

六、Systemd 与 Cgroup

6.1 Systemd 作为 Cgroup 管理者

现代 Linux 发行版(RHEL 8+, Ubuntu 21.04+)使用 systemd 管理 cgroup v2。

6.2 Systemd Unit 中的 Cgroup 配置

ini
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/myapp
User=myuser
Group=mygroup

# === Cgroup 资源限制 ===

# CPU 限制
CPUQuota=150%
CPUSchedulingPolicy=rr
CPUSchedulingPriority=50

# 内存限制
MemoryMax=512M
MemoryHigh=384M
MemorySwapMax=512M
MemoryMin=64M

# IO 限制
IOReadBandwidthMax=/dev/sda 10M
IOWriteBandwidthMax=/dev/sda 5M
IOReadIOPSMax=/dev/sda 100
IOWriteIOPSMax=/dev/sda 50
IODeviceWeight=/dev/sda 500

# 进程数限制
TasksMax=100

# OOM 策略
OOMPolicy=continue  # continue/stop/kill

[Install]
WantedBy=multi-user.target

6.3 Systemd Slice 管理

bash
# 创建自定义 slice
mkdir -p /etc/systemd/system/app.slice.d/

# /etc/systemd/system/app.slice.d/override.conf
[Slice]
CPUQuota=300%
MemoryMax=4G
TasksMax=500

# 重载并启动服务
systemctl daemon-reload

# 将服务添加到 slice
systemctl set-property myapp.service Slice=app.slice

# 查看 slice 资源使用
systemctl status app.slice
systemd-cgtop  # 实时查看 cgroup 资源占用

七、实战案例

案例 1:防止失控进程耗尽服务器资源

场景:生产环境部署第三方应用,担心其消耗过多资源影响其他服务。

解决方案

bash
#!/bin/bash
# create_cgroup_for_app.sh

APP_NAME="$1"
PID="$2"

if [ -z "$APP_NAME" ] || [ -z "$PID" ]; then
    echo "Usage: $0 <app_name> <pid>"
    exit 1
fi

CGROUP_PATH="/sys/fs/cgroup/$APP_NAME"

# 创建 cgroup
sudo mkdir -p $CGROUP_PATH

# 资源限制配置
echo "100000000" > $CGROUP_PATH/memory.max           # 100MB 内存
echo "50000 100000" > $CGROUP_PATH/cpu.max            # 0.5 CPU
echo "100" > $CGROUP_PATH/pid.max                     # 最多 100 个进程
echo "512M 512M" > $CGROUP_PATH/io.max                # IO 限制(如适用)

# 将进程加入 cgroup
echo $PID > $CGROUP_PATH/cgroup.procs

# 同时将子进程也加入(递归)
# cgroup v2 会自动继承子进程

echo "已创建 cgroup: $APP_NAME,限制:"
echo "  - 内存: 100MB"
echo "  - CPU: 0.5 核"
echo "  - 进程数: 100"

案例 2:Docker 容器资源隔离最佳实践

yaml
# docker-compose.prod.yml
version: '3.8'

services:
  # Web 前端 - 低资源需求
  frontend:
    image: nginx:alpine
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 256M
          pids: 50
        reservations:
          cpus: '0.1'
          memory: 64M
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/"]
      interval: 30s
      timeout: 5s
      retries: 3

  # API 服务 - 中等资源需求
  api:
    image: myapp-api:latest
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 2Gi
          pids: 200
        reservations:
          cpus: '0.5'
          memory: 512M
    environment:
      - NODE_ENV=production
    restart: unless-stopped

  # 数据库 - 高资源需求,保证稳定
  postgres:
    image: postgres:15-alpine
    deploy:
      resources:
        limits:
          cpus: '4'
          memory: 8Gi
          pids: 300
        reservations:
          cpus: '2'
          memory: 4Gi
    volumes:
      - pgdata:/var/lib/postgresql/data
    restart: unless-stopped

volumes:
  pgdata:

案例 3:Kubernetes 生产环境资源配置模板

yaml
# production-pod-template.yaml
apiVersion: v1
kind: Pod
metadata:
  name: production-app
  labels:
    app: production
    env: prod
spec:
  # 安全上下文
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    fsGroup: 1000
  
  # 服务质量保证
  priorityClassName: high-priority
  
  containers:
  - name: main-app
    image: registry.example.com/app:v1.2.3
    
    # 资源配置 - 根据压测结果设定
    resources:
      requests:
        cpu: "500m"       # 基于平均负载
        memory: "512Mi"   # 基于常驻内存
      limits:
        cpu: "2000m"      # 基于峰值负载 × 1.2
        memory: "1536Mi"  # 基于最大内存 × 1.5
    
    # 就绪和存活探针
    readinessProbe:
      httpGet:
        path: /health/ready
        port: 8080
      initialDelaySeconds: 10
      periodSeconds: 5
    livenessProbe:
      httpGet:
        path: /health/live
        port: 8080
      initialDelaySeconds: 30
      periodSeconds: 10
      failureThreshold: 3
    
    # 环境变量
    env:
    - name: JAVA_OPTS
      value: "-Xmx1024m -Xms512m"
    
    # 卷挂载
    volumeMounts:
    - name: config
      mountPath: /app/config
      readOnly: true
    - name: logs
      mountPath: /app/logs
  
  volumes:
  - name: config
    configMap:
      name: app-config
  - name: logs
    emptyDir:
      sizeLimit: "1Gi"  # 限制 emptyDir 大小

八、故障排查指南

8.1 常见问题诊断

问题 1:容器/进程被 OOM Kill

bash
# 检查 dmesg 日志
dmesg | grep -i "oom\|killed"

# 检查 cgroup 的 OOM 事件
cat /sys/fs/cgroup/<path>/memory.events

# 查看内存使用详情
cat /sys/fs/cgroup/<path>/memory.stat

# 解决方案:
# 1. 增加 memory.limit 或 memory.max
# 2. 优化应用内存使用
# 3. 启用 memory.oom.group=false(仅终止触发进程)

问题 2:容器 CPU 使用率异常

bash
# 检查 CPU 限制配置
cat /sys/fs/cgroup/<path>/cpu.max
cat /sys/fs/cgroup/<path>/cpu.stat

# 查看节流情况(nr_throttled 表示被限制次数)
cat /sys/fs/cgroup/<path>/cpu.stat
# nr_periods nr_throttled throttled_usec

# 解决方案:
# 1. 检查 cpu.max 是否设置过低
# 2. 如果 nr_throttled 很高,说明需要更多 CPU 资源
# 3. 调整 --cpus 或 limits.cpu

问题 3:IO 性能瓶颈

bash
# 检查 IO 限制
cat /sys/fs/cgroup/<path>/io.max

# 查看 IO 统计
cat /sys/fs/cgroup/<path>/io.stat

# 使用 iotop 观察实际 IO
iotop -p <pid>

# 解决方案:
# 1. 放宽 io.max 限制
# 2. 检查是否有其他高 IO 进程在同一 cgroup
# 3. 考虑使用更快的存储

8.2 监控工具

bash
# 1. systemd-cgtop - 实时查看 cgroup 资源使用
systemd-cgtop

# 2. 直接查看 cgroup 文件
watch -n 1 'cat /sys/fs/cgroup/*/memory.current'

# 3. 使用 pidstat 查看进程资源
pidstat -ru 1  # CPU
pidstat -r 1   # 内存

# 4. 使用 top/htop 的 cgroup 视图
htop  # 按 F2 开启 cgroup 显示列

# 5. Prometheus + cAdvisor(容器环境)
# cAdvisor 自动暴露 cgroup 指标给 Prometheus

8.3 性能调优检查清单

  • [ ] CPU: 是否设置了合理的 requests/limits?是否存在不必要的节流?
  • [ ] 内存: 是否有频繁的 OOM?soft limit 是否合理?
  • [ ] IO: IO wait 是否过高?是否需要调整权重或限制?
  • [ ] PIDs: 是否接近 pids.max?是否有僵尸进程?
  • [ ] Swap: 是否禁用了不必要的 swap?swappiness 是否合适?
  • [ ] 层级深度: cgroup 层级是否过深?(建议 ≤ 3 层)

九、相关文档