3. OOM优先级核心参数:oom_score_adj取值范围与含义、oom_adj与oom_score_adj的映射关系、进程级OOM优先级设置
在Linux内存过载场景下,内核的OOM Killer(Out-Of-Memory Killer)必须选择一个进程终止以释放内存。选择哪个进程并非随机,而是基于一套优先级评分机制。本章深入剖析OOM优先级的核心参数,帮助容器运行时工程师精确控制容器进程的“被杀概率”。
3.1 OOM评分机制概述
内核为每个进程维护一个动态计算的 oom_score(范围 0 ~ 1000),分数越高,越容易被OOM Killer选中。该分数由以下因素综合决定:
- 内存占用:进程实际使用的物理内存(RSS)越大,分数越高。
- 进程运行时间:运行时间越短,分数越高(倾向于杀死刚启动的进程)。
- 子进程数量:子进程越多,分数越高。
- oom_score_adj:用户可调节的偏移量,直接叠加到最终评分上。
最终评分公式为:
final_oom_score = oom_score + oom_score_adj
其中 oom_score_adj 是用户态唯一可控的OOM优先级参数,也是容器运行时调优的核心抓手。
3.2 oom_score_adj 取值范围与含义
该参数定义在 /proc/[pid]/oom_score_adj 文件中,取值范围为 -1000 到 +1000。不同取值段的含义如下表所示:
| 取值范围 | 含义 | 典型场景 |
|---|---|---|
| -1000 | 完全禁用OOM Killer对该进程的杀伤。进程的 oom_score 被强制设为0,永远不会被选中。 | 关键系统进程(init、sshd)、容器Pause进程 |
| -999 ~ -1 | 降低被杀概率。值越大,保护程度越高。 | 重要业务进程、监控代理 |
| 0 | 默认值。不调整基础评分,完全由内核动态计算决定。 | 普通用户进程、默认容器进程 |
| 1 ~ 999 | 增加被杀概率。值越大,越容易被选中。 | 低优先级批处理任务、缓存进程 |
| 1000 | 强制优先被杀。无论内存占用多少,该进程都会成为首选目标。 | 内存超限的cgroup内所有进程 |
3.3 oom_adj 与 oom_score_adj 的映射关系
在早期的Linux内核(2.6.x)中,OOM优先级通过 /proc/[pid]/oom_adj 控制,取值范围为 -17 ~ +15。为了兼容旧版应用,内核保留了 oom_adj 接口,但内部已映射到 oom_score_adj。映射规则如下:
| oom_adj 值 | 映射后的 oom_score_adj 值 | 说明 |
|---|---|---|
| -17 | -1000 | 完全禁用OOM Killer(等同于 oom_score_adj = -1000) |
| -16 ~ -1 | -1000 ~ -1(线性映射) | 保护程度逐渐降低 |
| 0 | 0 | 默认值,不调整 |
| 1 ~ 14 | 1 ~ 933(线性映射) | 被杀概率逐渐增加 |
| 15 | 1000 | 强制优先被杀(等同于 oom_score_adj = 1000) |
内核源码中的映射函数(简化逻辑):
int oom_adj_to_score_adj(int oom_adj) {
if (oom_adj == -17) return -1000;
if (oom_adj < 0) return oom_adj * 62; // 近似线性映射
if (oom_adj == 0) return 0;
if (oom_adj <= 15) return oom_adj * 66 + 10; // 近似线性映射
return 1000;
}
3.4 进程级OOM优先级设置
在容器运行时环境中,设置OOM优先级主要有三种方式:
3.4.1 通过 /proc 文件系统直接设置
适用于已运行的进程或调试场景:
# 将PID为1234的进程设置为高保护(不易被杀)
echo -500 > /proc/1234/oom_score_adj
# 将PID为5678的进程设置为优先被杀
echo 500 > /proc/5678/oom_score_adj
# 验证设置
cat /proc/1234/oom_score_adj
cat /proc/5678/oom_score_adj
3.4.2 通过 runc 容器运行时设置
在容器创建时通过 OCI 运行时规范中的 process.oomScoreAdj 字段指定:
{
"ociVersion": "1.0.2",
"process": {
"oomScoreAdj": -200,
"args": ["/bin/myapp"],
"env": ["PATH=/usr/local/bin"]
},
"linux": {
"resources": {
"memory": {
"limit": 536870912,
"reservation": 268435456
}
}
}
}
runc 在启动容器进程前,会读取该字段并写入 /proc/self/oom_score_adj,确保容器主进程从启动时就带有正确的OOM优先级。
3.4.3 通过 containerd / crictl 设置
在 containerd 或 Kubernetes 环境中,可以通过 CRI 接口的 LinuxContainerConfig.Resources.OomScoreAdj 字段设置:
# 使用 crictl 创建容器时指定
crictl run \
--oom-score-adj=-300 \
--memory=512M \
my-container
在 Kubernetes Pod 的 YAML 中,可以通过 securityContext 设置:
apiVersion: v1
kind: Pod
metadata:
name: oom-demo
spec:
containers:
- name: app
image: myapp:latest
securityContext:
runAsUser: 1000
procMount: Default
# 注意:Kubernetes 原生不支持直接设置 oom_score_adj
# 需要通过 Pod 的 QoS 等级间接控制
resources:
limits:
memory: "512Mi"
requests:
memory: "256Mi"
3.5 OOM优先级与cgroup内存限制的协同
当进程位于一个设置了 memory.limit_in_bytes 的cgroup中时,OOM Killer的行为会发生变化:
- cgroup内OOM:当cgroup内存使用达到限制时,内核只在该cgroup内选择进程杀死,不会影响其他cgroup。
- 系统级OOM:当整个系统内存不足时,内核在所有进程中根据 oom_score + oom_score_adj 选择。
- 优先级叠加:cgroup内的OOM Killer同样会考虑 oom_score_adj,因此即使在cgroup内,设置合理的优先级也能保护关键进程。
下图展示了OOM优先级参数在进程、cgroup和系统三个层面的作用范围:
3.6 容器运行时调优最佳实践
基于以上参数分析,总结容器运行时OOM优先级设置的推荐策略:
| 容器类型 | 推荐 oom_score_adj | 理由 |
|---|---|---|
| Pause容器(sandbox) | -1000 | 完全禁用OOM Killer,确保网络命名空间不丢失 |
| 关键业务容器 | -200 ~ -100 | 适度保护,避免被非关键进程抢占资源 |
| 普通业务容器 | 0 | 默认值,公平竞争 |
| Sidecar代理容器 | 100 ~ 300 | 优先被杀,保护主业务进程 |
| 批处理/日志容器 | 500 ~ 800 | 高优先级被杀,不影响在线服务 |
| BestEffort QoS Pod | 1000 | Kubernetes自动设置,系统内存不足时首选回收 |
✅ 确认容器主进程的 oom_score_adj 不为 1000(除非明确需要优先被杀)
✅ 确认 Pause 容器始终为 -1000
✅ 避免在同一 cgroup 内将多个进程都设为 -1000(会导致OOM Killer无目标)
✅ 结合 cgroup 内存限制使用,避免系统级OOM影响其他租户