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-进阶场景与优化
    • 常见问题与故障排查
    • 参考资料与延伸阅读

20-OOM Killer 机制详解

学习目标

  • 深入理解 Linux OOM Killer 的触发条件和工作原理
  • 掌握 oom_score 的计算方法和内核源码
  • 理解 Cgroup OOM 与系统 OOM 的区别
  • 掌握 OOM 防护策略和问题排查方法
  • 能够分析和预防生产环境中的 OOM 问题

前置知识

  • Linux 内存管理基础
  • Cgroup 基本概念
  • 基本的 C 语言阅读能力

一、OOM Killer 概述

1.1 什么是 OOM Killer?

OOM (Out of Memory) Killer 是 Linux 内核的一个保护机制,当系统内存严重不足且无法通过正常回收获得足够内存时,内核会选择杀死一个或多个进程来释放内存,防止系统崩溃。

┌────────────────────────────────────────────────────────────────────────┐
│                        OOM Killer 触发场景                              │
├────────────────────────────────────────────────────────────────────────┤
│                                                                        │
│   正常情况:                                                            │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │ 进程请求内存 → 分配成功 → 继续运行                                │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
│   内存紧张:                                                            │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │ 进程请求内存 → 触发回收 → Page Cache/Swap → 分配成功             │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
│   OOM 情况:                                                            │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │ 进程请求内存 → 触发回收 → 回收失败 → OOM Killer → 杀死进程       │ │
│   │                                      ↓                           │ │
│   │                               释放内存 → 分配成功                 │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

1.2 OOM 类型

┌────────────────────────────────────────────────────────────────────────┐
│                          OOM 类型对比                                   │
├────────────────────────────────────────────────────────────────────────┤
│                                                                        │
│   1. 系统级 OOM (Global OOM)                                           │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │ - 整个系统物理内存耗尽                                            │ │
│   │ - Swap 也已用尽或未配置                                           │ │
│   │ - 从所有进程中选择 victim                                         │ │
│   │ - 日志: "Out of memory: Kill process..."                         │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
│   2. Cgroup OOM (Container OOM)                                        │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │ - Cgroup 内存使用达到 memory.limit                               │ │
│   │ - 系统可能还有大量空闲内存                                        │ │
│   │ - 只从 Cgroup 内的进程中选择 victim                              │ │
│   │ - 日志: "Memory cgroup out of memory: Kill process..."           │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
│   3. NUMA OOM                                                          │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │ - 特定 NUMA 节点内存耗尽                                          │ │
│   │ - 其他节点可能有空闲内存                                          │ │
│   │ - 与 cpuset.mems 配置相关                                         │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

二、OOM 触发流程

2.1 完整触发流程

┌────────────────────────────────────────────────────────────────────────┐
│                       OOM Killer 触发流程                               │
├────────────────────────────────────────────────────────────────────────┤
│                                                                        │
│   ┌─────────────────┐                                                 │
│   │ 进程请求分配内存 │  malloc() / mmap() / page fault                │
│   └────────┬────────┘                                                 │
│            │                                                          │
│            ▼                                                          │
│   ┌─────────────────┐                                                 │
│   │ 尝试分配物理页面 │  __alloc_pages_slowpath()                      │
│   └────────┬────────┘                                                 │
│            │                                                          │
│            ▼                                                          │
│   ┌─────────────────┐     是                                          │
│   │   分配成功?    │────────────────────────▶ 返回页面               │
│   └────────┬────────┘                                                 │
│            │ 否                                                       │
│            ▼                                                          │
│   ┌─────────────────┐                                                 │
│   │  触发直接回收   │  __perform_reclaim()                            │
│   │  (Direct Reclaim)│                                                │
│   └────────┬────────┘                                                 │
│            │                                                          │
│            ▼                                                          │
│   ┌─────────────────┐     是                                          │
│   │ 回收到足够内存? │────────────────────────▶ 重试分配              │
│   └────────┬────────┘                                                 │
│            │ 否                                                       │
│            ▼                                                          │
│   ┌─────────────────┐                                                 │
│   │  唤醒 kswapd   │  wakeup_kswapd()                                 │
│   │  等待回收完成   │                                                  │
│   └────────┬────────┘                                                 │
│            │                                                          │
│            ▼                                                          │
│   ┌─────────────────┐     是                                          │
│   │ 回收到足够内存? │────────────────────────▶ 重试分配              │
│   └────────┬────────┘                                                 │
│            │ 否                                                       │
│            ▼                                                          │
│   ┌─────────────────┐                                                 │
│   │  检查 OOM 条件  │  out_of_memory()                                │
│   └────────┬────────┘                                                 │
│            │                                                          │
│            ▼                                                          │
│   ┌─────────────────┐     否                                          │
│   │ 可以触发 OOM?  │────────────────────────▶ 返回分配失败           │
│   │ (oom_killer_disabled?)│                                           │
│   └────────┬────────┘                                                 │
│            │ 是                                                       │
│            ▼                                                          │
│   ┌─────────────────┐                                                 │
│   │   选择 victim   │  select_bad_process()                          │
│   └────────┬────────┘                                                 │
│            │                                                          │
│            ▼                                                          │
│   ┌─────────────────┐                                                 │
│   │   杀死进程      │  oom_kill_process()                             │
│   │   发送 SIGKILL  │                                                 │
│   └────────┬────────┘                                                 │
│            │                                                          │
│            ▼                                                          │
│   ┌─────────────────┐                                                 │
│   │ 等待内存释放后  │                                                 │
│   │   重试分配     │                                                  │
│   └─────────────────┘                                                 │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

2.2 内核源码:OOM 入口

// mm/page_alloc.c
static inline struct page *
__alloc_pages_slowpath(gfp_t gfp_mask, unsigned int order,
                       struct alloc_context *ac)
{
    // ... 尝试直接回收 ...

    // 回收失败,检查是否需要 OOM
    if (should_reclaim_retry(gfp_mask, order, ac, alloc_flags,
                             did_some_progress > 0, &no_progress_loops))
        goto retry;

    // 检查是否应该触发 OOM
    if (check_retry_cpuset(cpuset_mems_cookie, ac) ||
        check_retry_zonelist(zonelist))
        goto retry_cpuset;

    // 触发 OOM
    page = __alloc_pages_may_oom(gfp_mask, order, ac, &did_some_progress);
    if (page)
        goto got_pg;

    // ...
}

// mm/oom_kill.c
bool out_of_memory(struct oom_control *oc)
{
    // 检查是否禁用了 OOM killer
    if (oom_killer_disabled)
        return false;

    // 检查是否有进程正在退出
    if (task_will_free_mem(current))
        return true;

    // 选择并杀死进程
    select_bad_process(oc);

    if (!oc->chosen) {
        // 没有找到可杀的进程
        dump_header(oc, NULL);
        panic("Out of memory and no killable processes...\n");
    }

    // 杀死选中的进程
    oom_kill_process(oc, "Out of memory");

    return true;
}

三、oom_score 计算详解

3.1 计算公式

┌────────────────────────────────────────────────────────────────────────┐
│                      oom_score 计算原理                                 │
├────────────────────────────────────────────────────────────────────────┤
│                                                                        │
│   基础分数 = (进程内存占用 / 系统总内存) × 1000                         │
│                                                                        │
│   最终分数 = 基础分数 + oom_score_adj                                   │
│                                                                        │
│   oom_score_adj 范围: -1000 ~ 1000                                     │
│   - -1000: 永不被 OOM Kill (OOM_SCORE_ADJ_MIN)                         │
│   -     0: 默认值                                                       │
│   -  1000: 优先被 OOM Kill (OOM_SCORE_ADJ_MAX)                         │
│                                                                        │
│   示例计算:                                                            │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │ 系统内存: 8 GB                                                   │ │
│   │ 进程内存: 2 GB                                                   │ │
│   │ oom_score_adj: 0                                                 │ │
│   │                                                                  │ │
│   │ 基础分数 = (2 GB / 8 GB) × 1000 = 250                            │ │
│   │ 最终分数 = 250 + 0 = 250                                         │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │ 进程 A: 内存 2GB, oom_score_adj=0    → oom_score ≈ 250          │ │
│   │ 进程 B: 内存 1GB, oom_score_adj=500  → oom_score ≈ 625          │ │
│   │ 进程 C: 内存 4GB, oom_score_adj=-500 → oom_score ≈ 0            │ │
│   │                                                                  │ │
│   │ 被杀顺序: B (625) > A (250) > C (0)                              │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

3.2 内核源码:oom_badness 计算

// mm/oom_kill.c
/**
 * oom_badness - 计算进程的 OOM 分数
 * @p: 目标进程
 * @totalpages: 系统总页数
 *
 * 返回值越高,越可能被杀死
 */
long oom_badness(struct task_struct *p, unsigned long totalpages)
{
    long points;
    long adj;

    // 内核线程和 init 进程不能被杀
    if (oom_unkillable_task(p))
        return LONG_MIN;

    // 正在退出的进程已经释放内存,不需要杀
    if (p->mm == NULL)
        return LONG_MIN;

    // 获取 oom_score_adj
    adj = (long)p->signal->oom_score_adj;
    if (adj == OOM_SCORE_ADJ_MIN) {
        // -1000 表示永不杀死
        return LONG_MIN;
    }

    // 计算进程使用的页数
    // 包括: 匿名页 + 文件页 + Swap + 页表
    points = get_mm_rss(p->mm) +           // RSS
             get_mm_counter(p->mm, MM_SWAPENTS) +  // Swap
             mm_pgtables_bytes(p->mm) / PAGE_SIZE; // 页表

    // 归一化到 [0, 1000] 范围
    // points = (pages / totalpages) * 1000
    adj *= totalpages / 1000;
    points += adj;

    // 确保返回值 > 0 (除非 adj = -1000)
    return points > 0 ? points : 1;
}

// 选择最坏的进程
static void select_bad_process(struct oom_control *oc)
{
    struct task_struct *p;
    long chosen_points = LONG_MIN;

    // 遍历所有进程
    for_each_process(p) {
        long points;

        // 跳过不能杀的进程
        if (oom_unkillable_task(p))
            continue;

        // Cgroup OOM: 只考虑 Cgroup 内的进程
        if (oc->memcg && !task_in_mem_cgroup(p, oc->memcg))
            continue;

        // 计算分数
        points = oom_badness(p, oc->totalpages);

        // 选择分数最高的进程
        if (points > chosen_points) {
            chosen_points = points;
            oc->chosen = p;
        }
    }
}

3.3 查看和修改 oom_score

# 查看进程的 OOM 分数
$ cat /proc/<pid>/oom_score
250

# 查看 oom_score_adj
$ cat /proc/<pid>/oom_score_adj
0

# 修改 oom_score_adj (需要 root)
# 设置为永不被 OOM kill
$ echo -1000 > /proc/<pid>/oom_score_adj

# 设置为优先被 OOM kill
$ echo 1000 > /proc/<pid>/oom_score_adj

# 批量查看所有进程的 OOM 分数
$ for pid in /proc/[0-9]*; do
    name=$(cat $pid/comm 2>/dev/null)
    score=$(cat $pid/oom_score 2>/dev/null)
    adj=$(cat $pid/oom_score_adj 2>/dev/null)
    echo "$pid $name $score $adj"
done | sort -k3 -n -r | head -20

四、Cgroup OOM 机制

4.1 Cgroup OOM vs 系统 OOM

┌────────────────────────────────────────────────────────────────────────┐
│                    Cgroup OOM vs 系统 OOM                               │
├────────────────────────────────────────────────────────────────────────┤
│                                                                        │
│   系统 OOM:                                                             │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │                                                                 │ │
│   │   ┌─────────┐  ┌─────────┐  ┌─────────┐  ┌─────────┐           │ │
│   │   │ Process │  │ Process │  │ Process │  │ Process │           │ │
│   │   │    A    │  │    B    │  │    C    │  │    D    │           │ │
│   │   │  2 GB   │  │  3 GB   │  │  1 GB   │  │  2 GB   │           │ │
│   │   └─────────┘  └─────────┘  └─────────┘  └─────────┘           │ │
│   │                                                                 │ │
│   │   系统内存: 8 GB (已用尽)                                        │ │
│   │   OOM Killer 从所有进程中选择 (选择 B, 因为内存最大)             │ │
│   │                                                                 │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
│   Cgroup OOM:                                                          │
│   ┌─────────────────────────────────────────────────────────────────┐ │
│   │                                                                 │ │
│   │   Cgroup (limit: 2GB)          System (still has 4GB free)      │ │
│   │   ┌──────────────────────┐     ┌──────────────────────┐        │ │
│   │   │  ┌───────┐  ┌───────┐│     │  ┌───────┐  ┌───────┐│        │ │
│   │   │  │Proc A │  │Proc B ││     │  │Proc C │  │Proc D ││        │ │
│   │   │  │ 1.2GB │  │ 0.8GB ││     │  │ 1 GB  │  │ 2 GB  ││        │ │
│   │   │  └───────┘  └───────┘│     │  └───────┘  └───────┘│        │ │
│   │   │  Used: 2GB (达到限制) │     │  Not affected        │        │ │
│   │   └──────────────────────┘     └──────────────────────┘        │ │
│   │                                                                 │ │
│   │   OOM Killer 只从 Cgroup 内选择 (选择 A, 因为内存最大)           │ │
│   │                                                                 │ │
│   └─────────────────────────────────────────────────────────────────┘ │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

4.2 Cgroup OOM 控制

# Cgroup v1
$ cat /sys/fs/cgroup/memory/<group>/memory.oom_control
oom_kill_disable 0    # 是否禁用 OOM killer
under_oom 0           # 当前是否处于 OOM 状态
oom_kill 5            # 发生 OOM kill 的次数

# 禁用 Cgroup OOM (进程会被阻塞而非杀死)
$ echo 1 > /sys/fs/cgroup/memory/<group>/memory.oom_control

# Cgroup v2
$ cat /sys/fs/cgroup/<group>/memory.events
low 0                 # 低内存警告次数
high 10               # 达到 high 水位次数
max 3                 # 达到 max 限制次数
oom 2                 # OOM 事件次数
oom_kill 2            # OOM kill 次数
oom_group_kill 0      # 整组 OOM kill 次数

# 查看当前内存使用
$ cat /sys/fs/cgroup/<group>/memory.current
1073741824

# 查看内存限制
$ cat /sys/fs/cgroup/<group>/memory.max
2147483648

4.3 内核源码:Cgroup OOM

// mm/memcontrol.c
// Cgroup 内存分配检查
static int try_charge(struct mem_cgroup *memcg, gfp_t gfp_mask,
                      unsigned int nr_pages)
{
    unsigned long batch = max(MEMCG_CHARGE_BATCH, nr_pages);
    int ret;

    // 尝试 charge
    ret = try_charge_memcg(memcg, gfp_mask, batch);
    if (ret == 0)
        return 0;

    // charge 失败,尝试回收
    mem_cgroup_reclaim(memcg, gfp_mask);

    // 再次尝试
    ret = try_charge_memcg(memcg, gfp_mask, batch);
    if (ret == 0)
        return 0;

    // 回收失败,触发 OOM
    mem_cgroup_oom(memcg, gfp_mask, order);

    return 0;
}

// Cgroup OOM 处理
static void mem_cgroup_oom(struct mem_cgroup *memcg,
                           gfp_t mask, int order)
{
    struct oom_control oc = {
        .zonelist = NULL,
        .nodemask = NULL,
        .memcg = memcg,           // 指定 Cgroup
        .gfp_mask = mask,
        .order = order,
    };

    // 检查是否禁用了 OOM killer
    if (memcg->oom_kill_disable) {
        // 阻塞进程而不是杀死
        current->memcg_in_oom = memcg;
        wait_event(memcg->oom_waitq, !READ_ONCE(memcg->under_oom));
        return;
    }

    // 触发 Cgroup OOM
    out_of_memory(&oc);
}

五、OOM 防护策略

5.1 系统级防护

# 1. 禁用 overcommit
# 0 = 允许适度 overcommit (默认)
# 1 = 始终允许 overcommit
# 2 = 禁止 overcommit (严格模式)
$ echo 2 > /proc/sys/vm/overcommit_memory

# 设置 overcommit 比例 (仅 overcommit_memory=2 时有效)
# 允许分配: (物理内存 + Swap) * overcommit_ratio%
$ echo 80 > /proc/sys/vm/overcommit_ratio

# 2. 增加 Swap
$ fallocate -l 4G /swapfile
$ chmod 600 /swapfile
$ mkswap /swapfile
$ swapon /swapfile

# 3. 调整 swappiness
# 0 = 尽量不使用 swap
# 100 = 积极使用 swap
$ echo 10 > /proc/sys/vm/swappiness

# 4. 设置 OOM panic
# 发生 OOM 时直接 panic 而非杀进程
$ echo 1 > /proc/sys/vm/panic_on_oom

5.2 进程级防护

# 1. 设置关键进程不被 OOM kill
$ echo -1000 > /proc/<pid>/oom_score_adj

# 2. 使用 systemd 配置
# /etc/systemd/system/critical-service.service
[Service]
OOMScoreAdjust=-1000

# 3. 使用 Docker 配置
$ docker run --oom-score-adj=-500 myimage

# 4. Kubernetes Pod 配置
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: app
    resources:
      limits:
        memory: "1Gi"
      requests:
        memory: "512Mi"

5.3 容器级防护

# Kubernetes Pod 资源限制最佳实践
apiVersion: v1
kind: Pod
metadata:
  name: memory-safe-pod
spec:
  containers:
  - name: app
    image: myapp
    resources:
      # 设置合理的 requests 和 limits
      requests:
        memory: "256Mi"    # 保证能获得的内存
        cpu: "100m"
      limits:
        memory: "512Mi"    # 最大内存限制
        cpu: "500m"

    # 健康检查
    livenessProbe:
      httpGet:
        path: /health
        port: 8080
      initialDelaySeconds: 10
      periodSeconds: 5

    # 优雅终止
    lifecycle:
      preStop:
        exec:
          command: ["/bin/sh", "-c", "sleep 10"]

---
# 使用 PodDisruptionBudget 防止批量 OOM
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: app-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: myapp

六、OOM 问题排查

6.1 识别 OOM 事件

# 1. 查看内核日志
$ dmesg | grep -i "oom\|killed"
[12345.678901] Out of memory: Killed process 1234 (myapp)

# 2. 查看 journalctl
$ journalctl -k | grep -i "oom\|killed"

# 3. 查看 messages 日志
$ grep -i "oom" /var/log/messages

# 4. 典型的 OOM 日志示例
[12345.678901] myapp invoked oom-killer: gfp_mask=0x100cca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
[12345.678902] CPU: 0 PID: 1234 Comm: myapp Not tainted 5.4.0 #1
[12345.678903] Memory cgroup out of memory: Killed process 1234 (myapp) total-vm:2097152kB, anon-rss:1048576kB, file-rss:4096kB, shmem-rss:0kB
[12345.678904] oom_reaper: reaped process 1234 (myapp), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

6.2 分析 OOM 日志

┌────────────────────────────────────────────────────────────────────────┐
│                      OOM 日志解读                                       │
├────────────────────────────────────────────────────────────────────────┤
│                                                                        │
│  Memory cgroup out of memory: Killed process 1234 (myapp)              │
│  ↑                                    ↑         ↑                      │
│  │                                    │         └─ 进程名              │
│  │                                    └─ 进程 PID                      │
│  └─ Cgroup OOM (不是系统 OOM)                                          │
│                                                                        │
│  total-vm:2097152kB     ← 虚拟内存总量 (约 2 GB)                       │
│  anon-rss:1048576kB     ← 匿名内存 RSS (约 1 GB)                       │
│  file-rss:4096kB        ← 文件映射 RSS (约 4 MB)                       │
│  shmem-rss:0kB          ← 共享内存 RSS                                 │
│                                                                        │
│  oom_score_adj=0        ← OOM 调整值 (默认)                            │
│                                                                        │
│  关键信息:                                                             │
│  - 进程使用了约 1 GB 匿名内存                                           │
│  - 这是 Cgroup OOM,不是系统内存不足                                    │
│  - 检查容器/Cgroup 的内存限制设置                                       │
│                                                                        │
└────────────────────────────────────────────────────────────────────────┘

6.3 排查脚本

#!/bin/bash
# oom_analysis.sh - OOM 问题分析脚本

echo "=== 系统内存状态 ==="
free -h
echo

echo "=== 内存详细信息 ==="
cat /proc/meminfo | grep -E "^(MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree)"
echo

echo "=== Top 10 内存使用进程 ==="
ps aux --sort=-%mem | head -11
echo

echo "=== OOM 相关日志 ==="
dmesg | grep -i "oom\|killed" | tail -20
echo

echo "=== Cgroup 内存使用 (Docker) ==="
for dir in /sys/fs/cgroup/memory/docker/*/; do
    if [ -d "$dir" ]; then
        container_id=$(basename "$dir")
        usage=$(cat "$dir/memory.usage_in_bytes" 2>/dev/null)
        limit=$(cat "$dir/memory.limit_in_bytes" 2>/dev/null)
        oom_kill=$(cat "$dir/memory.oom_control" 2>/dev/null | grep oom_kill | awk '{print $2}')

        if [ -n "$usage" ] && [ -n "$limit" ]; then
            usage_mb=$((usage / 1024 / 1024))
            limit_mb=$((limit / 1024 / 1024))
            percent=$((usage * 100 / limit))
            echo "Container: ${container_id:0:12} | Usage: ${usage_mb}MB / ${limit_mb}MB (${percent}%) | OOM kills: $oom_kill"
        fi
    fi
done
echo

echo "=== 高 OOM Score 进程 ==="
for pid in /proc/[0-9]*; do
    if [ -d "$pid" ]; then
        pid_num=$(basename "$pid")
        name=$(cat "$pid/comm" 2>/dev/null)
        score=$(cat "$pid/oom_score" 2>/dev/null)
        adj=$(cat "$pid/oom_score_adj" 2>/dev/null)
        if [ -n "$score" ] && [ "$score" -gt 100 ]; then
            echo "PID: $pid_num | Name: $name | Score: $score | Adj: $adj"
        fi
    fi
done | sort -t: -k4 -n -r | head -10

6.4 预防性监控

#!/usr/bin/env python3
# memory_monitor.py - 内存监控与预警

import os
import time
import subprocess

def get_memory_info():
    """获取系统内存信息"""
    with open('/proc/meminfo') as f:
        meminfo = {}
        for line in f:
            parts = line.split(':')
            key = parts[0].strip()
            value = int(parts[1].strip().split()[0])
            meminfo[key] = value
    return meminfo

def get_cgroup_memory():
    """获取 Cgroup 内存使用"""
    cgroups = []
    cgroup_base = '/sys/fs/cgroup/memory'

    if os.path.exists(cgroup_base + '/docker'):
        for container in os.listdir(cgroup_base + '/docker'):
            path = f"{cgroup_base}/docker/{container}"
            if os.path.isdir(path):
                try:
                    with open(f"{path}/memory.usage_in_bytes") as f:
                        usage = int(f.read().strip())
                    with open(f"{path}/memory.limit_in_bytes") as f:
                        limit = int(f.read().strip())
                    cgroups.append({
                        'id': container[:12],
                        'usage': usage,
                        'limit': limit,
                        'percent': usage * 100 / limit
                    })
                except:
                    pass

    return cgroups

def check_and_alert():
    """检查并告警"""
    meminfo = get_memory_info()
    total = meminfo['MemTotal']
    available = meminfo['MemAvailable']
    percent_used = (total - available) * 100 / total

    # 系统级告警
    if percent_used > 90:
        print(f"[CRITICAL] System memory usage: {percent_used:.1f}%")
    elif percent_used > 80:
        print(f"[WARNING] System memory usage: {percent_used:.1f}%")

    # Cgroup 级告警
    for cg in get_cgroup_memory():
        if cg['percent'] > 90:
            print(f"[CRITICAL] Container {cg['id']} memory: {cg['percent']:.1f}%")
        elif cg['percent'] > 80:
            print(f"[WARNING] Container {cg['id']} memory: {cg['percent']:.1f}%")

if __name__ == "__main__":
    while True:
        check_and_alert()
        time.sleep(10)

七、实战练习

7.1 模拟 OOM

#!/bin/bash
# trigger_oom.sh - 触发 OOM 并观察

# 创建测试 Cgroup (Cgroup v2)
CGROUP_PATH="/sys/fs/cgroup/test-oom"
mkdir -p $CGROUP_PATH

# 设置内存限制 100MB
echo "100M" > $CGROUP_PATH/memory.max

# 将当前 shell 加入 Cgroup
echo $$ > $CGROUP_PATH/cgroup.procs

echo "=== 开始消耗内存 ==="
echo "内存限制: $(cat $CGROUP_PATH/memory.max)"
echo "当前使用: $(cat $CGROUP_PATH/memory.current)"

# 启动内存消耗程序
python3 -c "
import time
data = []
try:
    while True:
        # 每次分配 10MB
        data.append('x' * (10 * 1024 * 1024))
        print(f'Allocated: {len(data) * 10} MB')
        time.sleep(0.5)
except MemoryError:
    print('MemoryError!')
"

# 检查 OOM 事件
echo "=== OOM 事件 ==="
cat $CGROUP_PATH/memory.events

# 清理
rmdir $CGROUP_PATH

7.2 保护关键进程

#!/bin/bash
# protect_process.sh - 保护关键进程不被 OOM Kill

CRITICAL_SERVICES=("sshd" "systemd" "kubelet")

for service in "${CRITICAL_SERVICES[@]}"; do
    pids=$(pgrep -x "$service")
    for pid in $pids; do
        if [ -f "/proc/$pid/oom_score_adj" ]; then
            echo -1000 > /proc/$pid/oom_score_adj
            echo "Protected $service (PID: $pid) from OOM"
        fi
    done
done

# 验证
echo
echo "=== 受保护的进程 ==="
for pid in /proc/[0-9]*; do
    adj=$(cat "$pid/oom_score_adj" 2>/dev/null)
    if [ "$adj" = "-1000" ]; then
        name=$(cat "$pid/comm" 2>/dev/null)
        echo "$(basename $pid) $name"
    fi
done

八、面试要点

8.1 高频问题

  1. OOM Killer 是什么?什么时候触发?

    • 内存保护机制,防止系统崩溃
    • 当内存耗尽且无法回收时触发
  2. oom_score 是怎么计算的?

    • 基于进程内存占用比例
    • 加上 oom_score_adj 调整
    • 分数越高越先被杀
  3. 如何防止进程被 OOM Kill?

    • 设置 oom_score_adj = -1000
    • 合理设置资源限制
    • 增加系统内存或 Swap
  4. Cgroup OOM 和系统 OOM 的区别?

    • Cgroup OOM: 容器内存达限制
    • 系统 OOM: 整个系统内存耗尽
    • Cgroup OOM 只影响容器内进程

8.2 进阶问题

  1. OOM Killer 选择 victim 的算法?

    • 遍历所有进程计算 oom_badness
    • 选择分数最高的进程
    • 考虑子进程和线程组
  2. 如何调试 OOM 问题?

    • 查看 dmesg/journalctl 日志
    • 分析内存使用趋势
    • 检查 Cgroup 限制设置
  3. overcommit 对 OOM 的影响?

    • overcommit_memory=2 可防止 OOM
    • 但可能导致分配失败
    • 权衡可用性和稳定性

相关链接

  • 19-Linux内存管理深度剖析 - 内存管理基础
  • 21-Cgroup内存控制深度 - 容器内存限制
  • 22-Kubernetes资源模型 - K8s 资源管理

下一步:让我们深入了解 Cgroup 是如何限制容器内存的!