HiHuo
首页
博客
手册
工具
关于
首页
博客
手册
工具
关于
  • 手撸容器系统

    • 完整手撸容器技术文档系列
    • 01-容器本质与基础概念
    • 02-Namespace隔离机制
    • 03-CGroup资源控制
    • 04-Capabilities与安全机制
    • 05-容器网络原理
    • 06-网络模式与实现
    • 07-CNI插件开发
    • 08-RootFS与文件系统隔离
    • 09-OverlayFS镜像分层
    • 10-命令行手撸容器
    • 11-Go实现最小容器
    • 12-Go实现完整容器
    • 13-容器生命周期管理
    • 14-调试技术与工具
    • 15-OCI规范与标准化
    • 16-进阶场景与优化
    • 常见问题与故障排查
    • 参考资料与延伸阅读

21-Cgroup 内存控制深度

学习目标

  • 深入理解 memory.limit_in_bytes 限制的具体内容
  • 掌握 memory.stat 各项指标的含义
  • 理解 Swap 与 memsw 限制的关系
  • 对比 Cgroup v1 和 v2 内存控制的差异
  • 能够分析内核源码中的关键实现

前置知识

  • Linux 内存管理基础
  • Cgroup 基本概念
  • OOM Killer 机制

一、核心问题:memory.limit 限制的是什么?

这是面试中最常问的问题之一。让我们彻底搞清楚。

1.1 官方定义

┌────────────────────────────────────────────────────────────────────────┐
│                memory.limit_in_bytes 限制的内容                         │
├────────────────────────────────────────────────────────────────────────┤
│                                                                        │
│   memory.limit_in_bytes = RSS + Page Cache (属于该 Cgroup 的)          │
│                                                                        │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │                     被限制的内存                                  │ │
│   │                                                                 │ │
│   │   ┌─────────────────────────────────────────────────────────┐  │ │
│   │   │  Anonymous Pages (匿名页)                                │  │ │
│   │   │  - 堆内存 (malloc/new)                                   │  │ │
│   │   │  - 栈内存                                                │  │ │
│   │   │  - 匿名 mmap (MAP_ANONYMOUS)                             │  │ │
│   │   │  - tmpfs/shmem                                           │  │ │
│   │   └─────────────────────────────────────────────────────────┘  │ │
│   │                                                                 │ │
│   │   ┌─────────────────────────────────────────────────────────┐  │ │
│   │   │  File-backed Pages (文件页)                              │  │ │
│   │   │  - 文件 mmap                                             │  │ │
│   │   │  - Page Cache (该 Cgroup 进程读写的文件)                  │  │ │
│   │   │  - 共享库 (.so 文件)                                     │  │ │
│   │   └─────────────────────────────────────────────────────────┘  │ │
│   │                                                                 │ │
│   │   ┌─────────────────────────────────────────────────────────┐  │ │
│   │   │  Kernel Memory (可选)                                    │  │ │
│   │   │  - 页表                                                  │  │ │
│   │   │  - 网络缓冲区                                            │  │ │
│   │   │  - inode/dentry 缓存                                     │  │ │
│   │   │  (需要 memory.kmem.limit_in_bytes 单独配置)               │  │ │
│   │   └─────────────────────────────────────────────────────────┘  │ │
│   │                                                                 │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │                     不被限制的内存                                │ │
│   │                                                                 │ │
│   │   - 虚拟内存中未分配物理页的部分                                 │ │
│   │   - Swap (除非设置 memsw 限制)                                  │ │
│   │   - 其他 Cgroup 进程的 Page Cache                               │ │
│   │                                                                 │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

1.2 常见误解

┌────────────────────────────────────────────────────────────────────────┐
│                         常见误解澄清                                    │
├────────────────────────────────────────────────────────────────────────┤
│                                                                        │
│   误解 1: 只限制堆内存                                                  │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │ ✗ 错误: 我设置了 1GB 限制,进程堆只用了 500MB,为什么 OOM?       │ │
│   │ ✓ 正确: Page Cache 和其他内存也计入限制                          │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
│   误解 2: 限制虚拟内存                                                  │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │ ✗ 错误: 进程虚拟内存显示 10GB,限制 1GB,会 OOM                   │ │
│   │ ✓ 正确: 只限制实际驻留在物理内存中的页面                          │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
│   误解 3: Page Cache 会自动回收                                         │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │ ✗ 错误: 我读了很多文件但会自动回收,不用管                        │ │
│   │ ✓ 正确: Page Cache 计入限制,达到限制时才会回收或触发 OOM        │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
│   误解 4: Swap 不受限制                                                 │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │ ✗ 错误: 只要有 Swap,进程就不会 OOM                              │ │
│   │ ✓ 正确: 默认 memsw.limit = 2 × memory.limit,超过也会 OOM        │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

二、memory.stat 详解

2.1 Cgroup v1 memory.stat

$ cat /sys/fs/cgroup/memory/<group>/memory.stat

cache                   104857600   # Page Cache 大小 (100 MB)
rss                     52428800    # 匿名内存 RSS (50 MB)
rss_huge                0           # 透明大页
shmem                   0           # 共享内存 (tmpfs)
mapped_file             10485760    # 映射文件大小 (10 MB)
dirty                   4096        # 脏页大小
writeback               0           # 正在写回的页
swap                    0           # Swap 使用量
pgpgin                  123456      # 页面换入次数
pgpgout                 234567      # 页面换出次数
pgfault                 345678      # 页错误次数
pgmajfault              123         # 主页错误次数
inactive_anon           10485760    # 非活跃匿名页
active_anon             41943040    # 活跃匿名页
inactive_file           52428800    # 非活跃文件页
active_file             52428800    # 活跃文件页
unevictable             0           # 不可回收页
hierarchical_memory_limit 1073741824 # 层级内存限制 (1 GB)
hierarchical_memsw_limit 2147483648  # 层级内存+Swap 限制 (2 GB)
total_cache             104857600   # 包含子 Cgroup 的 cache 总和
total_rss               52428800    # 包含子 Cgroup 的 RSS 总和
# ... 更多 total_* 指标

2.2 关键指标解读

┌────────────────────────────────────────────────────────────────────────┐
│                      memory.stat 指标关系图                             │
├────────────────────────────────────────────────────────────────────────┤
│                                                                        │
│   memory.usage_in_bytes ≈ cache + rss + swap                           │
│                                                                        │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │                                                                 │ │
│   │   cache = inactive_file + active_file                           │ │
│   │         = 文件页 (可回收)                                        │ │
│   │                                                                 │ │
│   │   rss = inactive_anon + active_anon + shmem                     │ │
│   │       = 匿名页 (需要 Swap 才能回收)                              │ │
│   │                                                                 │ │
│   │   mapped_file ⊆ cache                                           │ │
│   │       = 被进程 mmap 的文件页                                     │ │
│   │                                                                 │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
│   工作集 (Working Set) 估算:                                           │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │                                                                 │ │
│   │   working_set = usage - inactive_file                           │ │
│   │                                                                 │ │
│   │   因为 inactive_file 是可以立即回收的缓存                        │ │
│   │                                                                 │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
│   Kubernetes 使用的计算方式:                                           │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │                                                                 │ │
│   │   container_memory_working_set_bytes =                          │ │
│   │       usage - total_inactive_file                               │ │
│   │                                                                 │ │
│   │   这就是 kubectl top pod 显示的内存值                            │ │
│   │                                                                 │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

2.3 Cgroup v2 memory.stat

$ cat /sys/fs/cgroup/<group>/memory.stat

anon 52428800                    # 匿名内存
file 104857600                   # 文件映射内存
kernel 10485760                  # 内核内存
kernel_stack 1048576             # 内核栈
pagetables 2097152               # 页表
sec_pagetables 0                 # 安全页表
percpu 524288                    # per-CPU 数据
sock 0                           # socket 缓冲区
shmem 0                          # 共享内存
zswap 0                          # zswap 使用
zswapped 0                       # zswap 压缩数据
file_mapped 10485760             # 映射文件
file_dirty 4096                  # 脏页
file_writeback 0                 # 写回中
swapcached 0                     # swap 缓存
anon_thp 0                       # 匿名透明大页
file_thp 0                       # 文件透明大页
shmem_thp 0                      # 共享内存透明大页
inactive_anon 10485760           # 非活跃匿名页
active_anon 41943040             # 活跃匿名页
inactive_file 52428800           # 非活跃文件页
active_file 52428800             # 活跃文件页
unevictable 0                    # 不可回收页
slab_reclaimable 1048576         # 可回收 slab
slab_unreclaimable 524288        # 不可回收 slab
# ... 更多指标

三、Swap 与 memsw 限制

3.1 Swap 限制机制

┌────────────────────────────────────────────────────────────────────────┐
│                      Cgroup v1 Swap 限制                                │
├────────────────────────────────────────────────────────────────────────┤
│                                                                        │
│   memory.limit_in_bytes        = 物理内存限制                           │
│   memory.memsw.limit_in_bytes  = 物理内存 + Swap 总限制                 │
│                                                                        │
│   示例场景:                                                            │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │                                                                 │ │
│   │   memory.limit_in_bytes = 1 GB                                  │ │
│   │   memory.memsw.limit_in_bytes = 2 GB                            │ │
│   │                                                                 │ │
│   │   表示:最多使用 1GB 物理内存 + 1GB Swap                         │ │
│   │                                                                 │ │
│   │   ┌───────────────────────────────────────────────────────┐    │ │
│   │   │                                                       │    │ │
│   │   │  ████████████████████  Physical Memory (1 GB)         │    │ │
│   │   │  ░░░░░░░░░░░░░░░░░░░░  Swap Space (1 GB)              │    │ │
│   │   │                                                       │    │ │
│   │   │  Total: 2 GB (memsw.limit)                            │    │ │
│   │   │                                                       │    │ │
│   │   └───────────────────────────────────────────────────────┘    │ │
│   │                                                                 │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
│   禁用 Swap:                                                          │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │                                                                 │ │
│   │   # 设置 memsw = memory,禁止使用 Swap                           │ │
│   │   echo 1073741824 > memory.limit_in_bytes                       │ │
│   │   echo 1073741824 > memory.memsw.limit_in_bytes                 │ │
│   │                                                                 │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

3.2 Cgroup v2 Swap 控制

# Cgroup v2 使用独立的 swap 限制
$ cat /sys/fs/cgroup/<group>/memory.swap.max
# 值为 "max" 或具体字节数

# 设置 Swap 限制
$ echo 0 > /sys/fs/cgroup/<group>/memory.swap.max    # 禁用 Swap
$ echo 1G > /sys/fs/cgroup/<group>/memory.swap.max   # 限制 1GB Swap

# 查看 Swap 使用
$ cat /sys/fs/cgroup/<group>/memory.swap.current
0

# 查看 Swap 事件
$ cat /sys/fs/cgroup/<group>/memory.swap.events
high 0
max 0
fail 0

3.3 Kubernetes 禁用 Swap

# Kubelet 配置要求禁用 Swap
# /var/lib/kubelet/config.yaml
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
failSwapOn: true  # 如果有 Swap 则启动失败

# Pod 中的容器默认禁用 Swap
# Docker/containerd 会设置:
# memory.memsw.limit_in_bytes = memory.limit_in_bytes

四、Cgroup v1 vs v2 内存控制对比

4.1 接口对比

┌────────────────────────────────────────────────────────────────────────┐
│                    Cgroup v1 vs v2 内存接口对比                         │
├────────────────────────────────────────────────────────────────────────┤
│                                                                        │
│   Cgroup v1                          Cgroup v2                         │
│   ────────────────────────────────   ────────────────────────────────  │
│                                                                        │
│   memory.limit_in_bytes              memory.max                        │
│   memory.soft_limit_in_bytes         memory.high                       │
│   memory.usage_in_bytes              memory.current                    │
│   memory.max_usage_in_bytes          memory.peak                       │
│   memory.memsw.limit_in_bytes        memory.swap.max                   │
│   memory.memsw.usage_in_bytes        memory.swap.current               │
│   memory.stat                        memory.stat (格式不同)             │
│   memory.oom_control                 memory.events                     │
│   memory.kmem.limit_in_bytes         (内核内存统一管理)                 │
│   memory.kmem.usage_in_bytes         (在 memory.stat 中)               │
│                                                                        │
│   新增功能 (v2):                                                        │
│   ────────────────────────────────────────────────────────────────     │
│   memory.low                         低水位保护 (软保证)                │
│   memory.min                         最小水位保护 (硬保证)              │
│   memory.oom.group                   整组 OOM kill                     │
│   memory.pressure                    内存压力 (PSI)                     │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

4.2 v2 新增的水位保护

┌────────────────────────────────────────────────────────────────────────┐
│                      Cgroup v2 内存水位线                               │
├────────────────────────────────────────────────────────────────────────┤
│                                                                        │
│   memory.max (硬限制)                                                   │
│   ─────────────────────────────────────────────────────────────────    │
│   │                                                                    │
│   │   超过此限制触发 OOM                                               │
│   │                                                                    │
│   ─────────────────────────────────────────────────────────────────    │
│   memory.high (软限制 / 限流)                                           │
│   ─────────────────────────────────────────────────────────────────    │
│   │                                                                    │
│   │   超过此值时进程会被限流 (throttled)                               │
│   │   内核积极回收内存,但不会 OOM                                     │
│   │                                                                    │
│   ─────────────────────────────────────────────────────────────────    │
│   memory.low (低水位保护)                                               │
│   ─────────────────────────────────────────────────────────────────    │
│   │                                                                    │
│   │   低于此值时,内存受"尽力保护"                                     │
│   │   系统级内存紧张时仍可能被回收                                     │
│   │                                                                    │
│   ─────────────────────────────────────────────────────────────────    │
│   memory.min (最小保护)                                                 │
│   ─────────────────────────────────────────────────────────────────    │
│   │                                                                    │
│   │   低于此值时,内存绝对不会被回收                                   │
│   │   硬保证,即使系统 OOM 也不会回收                                  │
│   │                                                                    │
│   ─────────────────────────────────────────────────────────────────    │
│   0                                                                    │
│                                                                        │
│   示例配置:                                                            │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │                                                                 │ │
│   │   echo 256M > memory.min    # 保证 256MB                        │ │
│   │   echo 512M > memory.low    # 尽力保护 512MB                    │ │
│   │   echo 800M > memory.high   # 超过 800MB 开始限流               │ │
│   │   echo 1G > memory.max      # 硬限制 1GB                        │ │
│   │                                                                 │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

五、内核源码分析

5.1 核心数据结构

// include/linux/memcontrol.h
struct mem_cgroup {
    struct cgroup_subsys_state css;

    // 内存计数器
    struct page_counter memory;       // 内存使用计数
    struct page_counter swap;         // Swap 使用计数
    struct page_counter memsw;        // 内存 + Swap 计数 (v1)
    struct page_counter kmem;         // 内核内存计数 (v1)
    struct page_counter tcpmem;       // TCP 内存计数

    // 水位线 (v2)
    unsigned long high;               // memory.high
    unsigned long low;                // memory.low
    unsigned long min;                // memory.min

    // LRU 管理
    struct mem_cgroup_lru_info lru;

    // OOM 相关
    bool oom_lock;
    int oom_kill_disable;             // v1: memory.oom_control
    int under_oom;
    atomic_t oom_kills;               // OOM kill 计数

    // 事件计数
    struct mem_cgroup_events_info events_info;

    // 统计信息
    struct mem_cgroup_stat_cpu __percpu *stat_cpu;
    // ...
};

// 页面计数器
struct page_counter {
    atomic_long_t usage;              // 当前使用量
    unsigned long max;                // 限制值
    unsigned long high;               // 高水位
    unsigned long low;                // 低水位
    unsigned long min;                // 最小值
    // ...
};

5.2 内存充电 (Charge) 机制

// mm/memcontrol.c
// 内存分配时的 charge 操作
int mem_cgroup_charge(struct page *page, struct mm_struct *mm, gfp_t gfp_mask)
{
    struct mem_cgroup *memcg;
    int ret;

    // 获取进程所属的 memcg
    memcg = get_mem_cgroup_from_mm(mm);
    if (!memcg)
        return 0;

    // 尝试 charge
    ret = try_charge(memcg, gfp_mask, 1);
    if (ret)
        goto out;

    // 关联页面和 memcg
    commit_charge(page, memcg);

out:
    css_put(&memcg->css);
    return ret;
}

// 尝试 charge
static int try_charge(struct mem_cgroup *memcg, gfp_t gfp_mask,
                      unsigned int nr_pages)
{
    unsigned long batch = nr_pages;
    int ret = 0;

retry:
    // 增加计数器
    page_counter_try_charge(&memcg->memory, batch, &counter);

    // 检查是否超过限制
    if (page_counter_read(&memcg->memory) > memcg->memory.max) {
        // 尝试回收
        mem_cgroup_reclaim(memcg, gfp_mask);

        // 再次检查
        if (page_counter_read(&memcg->memory) > memcg->memory.max) {
            // 触发 OOM
            mem_cgroup_oom(memcg, gfp_mask, order);
        }
    }

    // 检查 high 水位 (v2)
    if (page_counter_read(&memcg->memory) > memcg->high) {
        // 限流:调用 schedule() 让出 CPU
        current->memcg_reclaim = 1;
        schedule();
        current->memcg_reclaim = 0;
    }

    return ret;
}

5.3 内存回收流程

// mm/memcontrol.c
// Cgroup 内存回收
static unsigned long mem_cgroup_reclaim(struct mem_cgroup *memcg,
                                        gfp_t gfp_mask)
{
    unsigned long nr_reclaimed = 0;
    struct mem_cgroup *iter;

    // 遍历该 memcg 及其子 cgroup
    for_each_mem_cgroup_tree(iter, memcg) {
        // 获取 LRU 列表
        struct lruvec *lruvec = &iter->lruvec;

        // 回收不活跃文件页 (Page Cache)
        nr_reclaimed += shrink_list(LRU_INACTIVE_FILE, lruvec);

        // 如果有 Swap,也回收不活跃匿名页
        if (get_nr_swap_pages() > 0) {
            nr_reclaimed += shrink_list(LRU_INACTIVE_ANON, lruvec);
        }

        // 检查是否回收足够
        if (nr_reclaimed >= sc->nr_to_reclaim)
            break;
    }

    return nr_reclaimed;
}

六、实战分析

6.1 分析容器内存使用

#!/bin/bash
# analyze_container_memory.sh

CONTAINER_ID="${1:-$(docker ps -q | head -1)}"
CGROUP_PATH="/sys/fs/cgroup/memory/docker/${CONTAINER_ID}"

if [ ! -d "$CGROUP_PATH" ]; then
    echo "Container not found or using cgroup v2"
    exit 1
fi

echo "=== Container Memory Analysis ==="
echo "Container ID: ${CONTAINER_ID:0:12}"
echo

# 基本信息
limit=$(cat $CGROUP_PATH/memory.limit_in_bytes)
usage=$(cat $CGROUP_PATH/memory.usage_in_bytes)
max_usage=$(cat $CGROUP_PATH/memory.max_usage_in_bytes)

echo "=== Basic Info ==="
printf "Limit:      %12d bytes (%d MB)\n" $limit $((limit/1024/1024))
printf "Usage:      %12d bytes (%d MB)\n" $usage $((usage/1024/1024))
printf "Max Usage:  %12d bytes (%d MB)\n" $max_usage $((max_usage/1024/1024))
printf "Percent:    %12.1f%%\n" $(echo "scale=1; $usage*100/$limit" | bc)
echo

# 详细统计
echo "=== Memory Stats ==="
cache=$(grep "^cache " $CGROUP_PATH/memory.stat | awk '{print $2}')
rss=$(grep "^rss " $CGROUP_PATH/memory.stat | awk '{print $2}')
swap=$(grep "^swap " $CGROUP_PATH/memory.stat | awk '{print $2}')
inactive_file=$(grep "^inactive_file " $CGROUP_PATH/memory.stat | awk '{print $2}')
active_file=$(grep "^active_file " $CGROUP_PATH/memory.stat | awk '{print $2}')
inactive_anon=$(grep "^inactive_anon " $CGROUP_PATH/memory.stat | awk '{print $2}')
active_anon=$(grep "^active_anon " $CGROUP_PATH/memory.stat | awk '{print $2}')

printf "Cache:          %12d bytes (%d MB)\n" $cache $((cache/1024/1024))
printf "RSS:            %12d bytes (%d MB)\n" $rss $((rss/1024/1024))
printf "Swap:           %12d bytes (%d MB)\n" $swap $((swap/1024/1024))
printf "Inactive File:  %12d bytes (%d MB)\n" $inactive_file $((inactive_file/1024/1024))
printf "Active File:    %12d bytes (%d MB)\n" $active_file $((active_file/1024/1024))
printf "Inactive Anon:  %12d bytes (%d MB)\n" $inactive_anon $((inactive_anon/1024/1024))
printf "Active Anon:    %12d bytes (%d MB)\n" $active_anon $((active_anon/1024/1024))
echo

# 工作集计算
working_set=$((usage - inactive_file))
echo "=== Working Set (kubectl top 显示值) ==="
printf "Working Set:    %12d bytes (%d MB)\n" $working_set $((working_set/1024/1024))
echo

# OOM 信息
echo "=== OOM Info ==="
cat $CGROUP_PATH/memory.oom_control
echo

# 内存压力
if [ -f $CGROUP_PATH/memory.pressure ]; then
    echo "=== Memory Pressure (v2) ==="
    cat $CGROUP_PATH/memory.pressure
fi

6.2 模拟 Page Cache 占用

#!/bin/bash
# page_cache_demo.sh - 演示 Page Cache 如何影响容器内存

# 创建测试容器
docker run -d --name cache-test \
    --memory=100m \
    --memory-swap=100m \
    alpine sleep 3600

# 获取 cgroup 路径
CONTAINER_ID=$(docker inspect -f '{{.Id}}' cache-test)
CGROUP_PATH="/sys/fs/cgroup/memory/docker/${CONTAINER_ID}"

echo "=== 初始状态 ==="
echo "Usage: $(cat $CGROUP_PATH/memory.usage_in_bytes)"
echo "Cache: $(grep '^cache ' $CGROUP_PATH/memory.stat | awk '{print $2}')"
echo "RSS:   $(grep '^rss ' $CGROUP_PATH/memory.stat | awk '{print $2}')"

# 在容器中创建并读取大文件
echo "=== 读取 50MB 文件 ==="
docker exec cache-test sh -c "dd if=/dev/zero of=/tmp/testfile bs=1M count=50"
docker exec cache-test sh -c "cat /tmp/testfile > /dev/null"

sleep 2

echo "Usage: $(cat $CGROUP_PATH/memory.usage_in_bytes)"
echo "Cache: $(grep '^cache ' $CGROUP_PATH/memory.stat | awk '{print $2}')"
echo "RSS:   $(grep '^rss ' $CGROUP_PATH/memory.stat | awk '{print $2}')"

# 再读取更多文件,触发回收
echo "=== 读取更多文件,触发回收 ==="
docker exec cache-test sh -c "dd if=/dev/zero of=/tmp/testfile2 bs=1M count=80"
docker exec cache-test sh -c "cat /tmp/testfile2 > /dev/null"

sleep 2

echo "Usage: $(cat $CGROUP_PATH/memory.usage_in_bytes)"
echo "Cache: $(grep '^cache ' $CGROUP_PATH/memory.stat | awk '{print $2}')"
echo "RSS:   $(grep '^rss ' $CGROUP_PATH/memory.stat | awk '{print $2}')"

# 清理
docker rm -f cache-test

6.3 监控脚本

#!/usr/bin/env python3
# memory_monitor.py - 实时监控容器内存

import os
import time
import sys

def read_file(path):
    try:
        with open(path) as f:
            return f.read().strip()
    except:
        return None

def read_stat(path, key):
    content = read_file(path)
    if not content:
        return 0
    for line in content.split('\n'):
        parts = line.split()
        if len(parts) >= 2 and parts[0] == key:
            return int(parts[1])
    return 0

def monitor_container(container_id, interval=1):
    cgroup_path = f"/sys/fs/cgroup/memory/docker/{container_id}"

    if not os.path.exists(cgroup_path):
        print(f"Container {container_id[:12]} not found")
        return

    limit = int(read_file(f"{cgroup_path}/memory.limit_in_bytes") or 0)
    limit_mb = limit / 1024 / 1024

    print(f"Container: {container_id[:12]}")
    print(f"Limit: {limit_mb:.0f} MB")
    print("-" * 80)
    print(f"{'Time':<10} {'Usage(MB)':<12} {'Cache(MB)':<12} {'RSS(MB)':<12} {'WS(MB)':<12} {'%Used':<8}")
    print("-" * 80)

    while True:
        usage = int(read_file(f"{cgroup_path}/memory.usage_in_bytes") or 0)
        cache = read_stat(f"{cgroup_path}/memory.stat", "cache")
        rss = read_stat(f"{cgroup_path}/memory.stat", "rss")
        inactive_file = read_stat(f"{cgroup_path}/memory.stat", "inactive_file")

        usage_mb = usage / 1024 / 1024
        cache_mb = cache / 1024 / 1024
        rss_mb = rss / 1024 / 1024
        ws_mb = (usage - inactive_file) / 1024 / 1024
        percent = usage * 100 / limit if limit > 0 else 0

        timestamp = time.strftime("%H:%M:%S")
        print(f"{timestamp:<10} {usage_mb:<12.1f} {cache_mb:<12.1f} {rss_mb:<12.1f} {ws_mb:<12.1f} {percent:<8.1f}")

        time.sleep(interval)

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: python memory_monitor.py <container_id>")
        sys.exit(1)

    monitor_container(sys.argv[1])

七、面试要点

7.1 高频问题

  1. memory.limit_in_bytes 限制的是什么?

    • 物理内存 RSS + Page Cache
    • 不包括虚拟内存中未分配的部分
    • 默认不包括 Swap(除非设置 memsw)
  2. 为什么容器 RSS 很小还是 OOM?

    • Page Cache 也计入限制
    • 读写大量文件会占用 Page Cache
    • 检查 memory.stat 中的 cache 值
  3. Cgroup v1 和 v2 内存控制的主要区别?

    • v2 增加了 memory.high 软限制
    • v2 增加了 memory.low/min 保护
    • v2 接口更简洁统一
  4. kubectl top 显示的内存是什么?

    • 工作集 = usage - inactive_file
    • 排除了可立即回收的缓存

7.2 进阶问题

  1. 如何计算容器的"真实"内存使用?

    • RSS(匿名页)是必需的
    • Page Cache 可以被回收
    • 工作集更接近真实需求
  2. memory.high vs memory.max 的区别?

    • high 是软限制,超过会限流
    • max 是硬限制,超过会 OOM
    • high 用于避免突发 OOM
  3. 如何优化容器内存配置?

    • 监控实际工作集而非 limit
    • 合理设置 requests 和 limits
    • 考虑应用的 Page Cache 需求

相关链接

  • 19-Linux内存管理深度剖析 - 内存管理基础
  • 20-OOM-Killer机制详解 - OOM 详细分析
  • 22-Kubernetes资源模型 - K8s 资源管理

下一步:让我们了解 Kubernetes 是如何利用这些底层机制进行资源管理的!