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 高频问题
memory.limit_in_bytes 限制的是什么?
- 物理内存 RSS + Page Cache
- 不包括虚拟内存中未分配的部分
- 默认不包括 Swap(除非设置 memsw)
为什么容器 RSS 很小还是 OOM?
- Page Cache 也计入限制
- 读写大量文件会占用 Page Cache
- 检查 memory.stat 中的 cache 值
Cgroup v1 和 v2 内存控制的主要区别?
- v2 增加了 memory.high 软限制
- v2 增加了 memory.low/min 保护
- v2 接口更简洁统一
kubectl top 显示的内存是什么?
- 工作集 = usage - inactive_file
- 排除了可立即回收的缓存
7.2 进阶问题
如何计算容器的"真实"内存使用?
- RSS(匿名页)是必需的
- Page Cache 可以被回收
- 工作集更接近真实需求
memory.high vs memory.max 的区别?
- high 是软限制,超过会限流
- max 是硬限制,超过会 OOM
- high 用于避免突发 OOM
如何优化容器内存配置?
- 监控实际工作集而非 limit
- 合理设置 requests 和 limits
- 考虑应用的 Page Cache 需求
相关链接
- 19-Linux内存管理深度剖析 - 内存管理基础
- 20-OOM-Killer机制详解 - OOM 详细分析
- 22-Kubernetes资源模型 - K8s 资源管理
下一步:让我们了解 Kubernetes 是如何利用这些底层机制进行资源管理的!