2. 容器内存限制:cgroup v1/v2 内存子系统架构、memory.limit_in_bytes 与 memory.high 的区别、容器内存上限设置

2.1 cgroup 内存子系统架构概述

容器运行时(containerd / runc / cri-o)通过 Linux cgroup 对容器进程施加内存上限。cgroup 有两个主要版本:v1 和 v2。理解两者的内存子系统架构是调优 OOM 优先级和回收行为的前提。

核心要点: cgroup v1 使用层级树 + 独立控制器,v2 使用统一层级树 + 一体化接口。v2 在内存回收和压力通知方面提供了更精细的控制能力。

2.1.1 cgroup v1 内存子系统

cgroup v1 将内存控制器独立挂载在 /sys/fs/cgroup/memory 下。每个容器对应一个子目录,内核通过 memory.limit_in_bytes 和 memory.soft_limit_in_bytes 控制硬限制和软限制。

  • memory.limit_in_bytes:硬限制,容器进程总内存(RSS + Page Cache + Swap)超过此值即触发 OOM Killer。
  • memory.soft_limit_in_bytes:软限制,仅在系统内存紧张时优先回收该 cgroup 的内存。
  • memory.memsw.limit_in_bytes:内存 + Swap 总硬限制(需内核开启 CONFIG_MEMCG_SWAP)。
  • memory.usage_in_bytes:当前内存使用量。
  • memory.stat:详细的内存统计信息,包括 cache、rss、pgfault 等。
注意: cgroup v1 中 memory.limit_in_bytes 设置的值会立即生效,但内核可能不会立刻回收内存,导致实际使用量短暂超过限制。OOM Killer 的触发存在延迟。

2.1.2 cgroup v2 内存子系统

cgroup v2 将内存控制器集成在统一层级 /sys/fs/cgroup/ 下,不再有独立的 memory 挂载点。v2 引入了更细粒度的控制参数:

  • memory.max:对应 v1 的 memory.limit_in_bytes,硬限制。
  • memory.high:软限制,当内存使用超过此值时,内核会主动进行内存回收,但不会触发 OOM。
  • memory.low:保护性下限,保证该 cgroup 在内存竞争时至少保留此内存量。
  • memory.min:硬性保护下限,即使系统内存不足也不会回收此部分内存(需内核支持 PSI)。
  • memory.current:当前内存使用量。
  • memory.events:内存压力事件统计,包括 low、high、max、oom 等。
最佳实践: 在 cgroup v2 环境中,推荐同时设置 memory.high 和 memory.max。前者用于主动回收,后者作为硬性兜底,避免容器进程突然被 OOM 杀死。

2.2 memory.limit_in_bytes 与 memory.high 的区别

这是容器内存调优中最容易混淆的两个参数。下表从多个维度对比两者的行为差异:

对比维度 memory.limit_in_bytes (v1) / memory.max (v2) memory.high (v2 特有)
类型 硬限制(Hard Limit) 软限制(Soft Limit)
触发行为 超过后触发 OOM Killer,杀死容器内进程 超过后触发同步内存回收,进程可能被 throttle(限流)
回收时机 被动回收,仅在 OOM 前尝试回收 主动回收,内核在分配内存时同步执行
对进程影响 进程可能被直接杀死,无降级机会 进程可能因回收而变慢,但不会被杀
适用场景 严格的内存隔离,防止容器占用过多内存 内存弹性控制,允许短时突发但限制长期使用
默认值 无限制(max) 无限制(max)
与 Swap 的关系 可与 memory.swap.max 配合限制 Swap 使用 不影响 Swap,仅控制物理内存回收
关键结论: memory.high 是 cgroup v2 引入的“优雅降级”机制。设置合理的 memory.high 可以让容器在内存压力下通过回收和限流来避免 OOM,适合对延迟不敏感但需要稳定性的工作负载。而 memory.limit_in_bytes / memory.max 是最后的防线,必须设置以防止内存泄漏导致宿主机崩溃。

2.3 容器内存上限设置实践

2.3.1 在 containerd 中设置内存限制

containerd 通过 CRI(Container Runtime Interface)接收 kubelet 下发的资源限制,然后传递给 runc。在 Pod 的 YAML 中设置:

apiVersion: v1
kind: Pod
metadata:
  name: memory-demo
spec:
  containers:
  - name: mem-alloc
    image: polinux/stress
    resources:
      requests:
        memory: "64Mi"
      limits:
        memory: "128Mi"
    command: ["stress"]
    args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]

kubelet 会将 limits.memory 转换为 cgroup v2 的 memory.max,将 requests.memory 转换为 memory.low(v2)或 memory.soft_limit_in_bytes(v1)。

2.3.2 手动设置 cgroup v2 内存上限

在宿主机上直接操作 cgroup 文件系统:

# 创建容器 cgroup 目录
mkdir -p /sys/fs/cgroup/container-demo

# 设置硬限制:最大 256MB
echo 268435456 > /sys/fs/cgroup/container-demo/memory.max

# 设置软限制:超过 192MB 开始主动回收
echo 201326592 > /sys/fs/cgroup/container-demo/memory.high

# 将容器进程 PID 加入 cgroup
echo 12345 > /sys/fs/cgroup/container-demo/cgroup.procs

# 查看当前内存使用
cat /sys/fs/cgroup/container-demo/memory.current

2.3.3 在 runc 配置中设置

runc 的 config.json 中通过 linux.resources.memory 字段设置:

{
  "linux": {
    "resources": {
      "memory": {
        "limit": 268435456,
        "reservation": 134217728,
        "swap": 536870912,
        "kernel": 0,
        "kernelTCP": 0
      }
    }
  }
}

在 cgroup v2 环境下,runc 会自动将 limit 映射为 memory.max,将 reservation 映射为 memory.low。

2.4 cgroup v1 vs v2 内存架构对比图

下图展示了 cgroup v1 和 v2 在内存子系统上的架构差异:

cgroup v1 vs v2 内存子系统架构对比 cgroup v1 vs v2 内存子系统架构对比 cgroup v1 内存子系统 /sys/fs/cgroup/memory/ memory 控制器(独立挂载) memory.limit_in_bytes(硬限制) memory.soft_limit_in_bytes(软限制) memory.memsw.limit_in_bytes(Swap限制) 超过 limit → OOM Killer 无主动回收机制 特点: • 层级树 + 独立控制器挂载 • 硬限制触发 OOM,无优雅降级 cgroup v2 内存子系统 /sys/fs/cgroup/(统一挂载) memory 控制器(集成在统一层级) memory.max(硬限制) memory.high(软限制,主动回收) memory.low / memory.min(保护下限) 超过 high → 同步回收 + throttle 超过 max → OOM Killer(兜底) 特点: • 统一层级树,无独立挂载点 • 软限制主动回收,避免 OOM

2.5 内存回收流程对比图

下图展示了设置 memory.high 和 memory.max 后,容器内存分配与回收的完整流程:

容器内存分配与回收流程 容器内存分配与回收流程(cgroup v2) 容器进程申请内存 超过 memory.high? 否 正常分配内存 是 触发同步内存回收 超过 memory.max? 否 分配成功(可能已回收) 是 触发 OOM Killer

2.6 调优建议与避坑指南

推荐配置策略:
  • 始终设置 memory.max(v2)或 memory.limit_in_bytes(v1),防止内存泄漏影响宿主机。
  • 在 cgroup v2 中,额外设置 memory.high 为 memory.max 的 80%~90%,让内核提前回收。
  • 对于延迟敏感型容器(如数据库),适当调高 memory.high 阈值,减少回收导致的抖动。
  • 对于批处理任务,可以设置较低的 memory.high,允许短时突发但强制回收。
常见陷阱:
  • 只设置 memory.high 而不设置 memory.max:当回收速度跟不上分配速度时,容器可能无限制使用内存,最终导致宿主机 OOM。
  • 在 cgroup v1 中误用 memory.soft_limit_in_bytes 作为硬限制:软限制仅在内存竞争时生效,无法防止单容器内存暴涨。
  • 忽略 Swap 限制:如果未设置 memory.swap.max(v2)或 memory.memsw.limit_in_bytes(v1),容器可能通过 Swap 绕过内存限制。

2.7 总结

容器内存限制的核心在于理解 cgroup v1 和 v2 的架构差异,以及 memory.limit_in_bytes / memory.max 与 memory.high 的协同关系。v2 的 memory.high 提供了主动回收机制,是避免容器 OOM 的关键参数。在实际调优中,应结合容器的工作负载类型(延迟敏感型 vs 吞吐型)来设置合理的阈值,并始终保留硬限制作为安全兜底。