1 次浏览0 条回复

适用于 Linux 主机或 systemd 服务出现进程被 SIGKILL、退出码 137,且怀疑与内存有关的场景。命令基线为 Linux 5.4+、systemd 245+、procps-ng 3.3+;cgroup v2 的文件名与 v1 不同,以下先给出 v2 路径。读取完整内核日志及其他进程的 /proc 信息通常需要 root 权限。示例单元 app.service 必须替换为实际值。先保留现场,不要一开始就重启服务、清缓存或放大内存限额。

1. 先确认是否真的发生了 OOM Kill

date -u
uname -r
systemctl --version | head -n 1

journalctl -k --since '-30 min' --no-pager -o short-iso \
  | grep -Ei 'out of memory|oom-kill|killed process|memory cgroup'
journalctl -u app.service --since '-30 min' --no-pager -o short-iso
systemctl show app.service \
  -p ActiveState -p SubState -p Result \
  -p ExecMainCode -p ExecMainStatus -p MainPID

退出码 137 只表示进程最终收到 SIGKILL,不能单独证明是内核 OOM killer;人工 kill -9、编排器超时和服务管理器也可能产生相同结果。应以同一时间窗口内的内核日志、cgroup 计数器和服务退出记录相互印证。过滤结果为空时仍应检查该时间段的完整内核日志,因为不同内核版本的字段和措辞可能不同。若故障跨过重启,且系统启用了持久化 journal,可再查:

journalctl -k -b -1 --no-pager -o short-iso

2. 判断是主机级压力还是 cgroup 达到上限

先确认层级类型并定位服务的控制组:

stat -fc %T /sys/fs/cgroup
cg=$(systemctl show app.service -p ControlGroup --value)
printf 'ControlGroup=%s\n' "$cg"

systemctl show app.service \
  -p MemoryCurrent -p MemoryHigh -p MemoryMax -p MemorySwapMax -p OOMPolicy

stat 返回 cgroup2fs 时,可读取该服务的 v2 指标:

base="/sys/fs/cgroup${cg}
"
base=${base%$'\n'}
cat "$base/memory.current"
cat "$base/memory.max"
cat "$base/memory.events"
[ ! -r "$base/memory.events.local" ] || cat "$base/memory.events.local"
sed -n '/^anon /p;/^file /p;/^slab /p;/^sock /p' "$base/memory.stat"

memory.maxmax 表示未设置硬上限;memory.eventsoomoom_kill 增加,才支持该 cgroup 在其生命周期内触发过 OOM。这个文件的计数是累计值,单次读取不能确定故障时间;最好与告警前基线、内核日志时间和服务重启时间对齐。memory.events 可能包含子 cgroup 事件,内核提供 memory.events.local 时可用它区分本层事件。

若不是 cgroup2fs,先用 findmnt -t cgroup 找到 v1 memory 控制器挂载点,再按 ControlGroup 映射路径读取 memory.usage_in_bytesmemory.limit_in_bytesmemory.failcnt,不要把 v2 文件名直接套到 v1。

同时检查主机整体压力:

free -h
sed -n '/^MemAvailable:/p;/^SwapTotal:/p;/^SwapFree:/p' /proc/meminfo
vmstat 1 10
[ ! -r /proc/pressure/memory ] || cat /proc/pressure/memory

MemAvailable、swap 和 PSI 都需要连续观测;故障后的单个快照不能还原峰值。内核日志若明确出现 constraint=CONSTRAINT_MEMCGoom_memcg 或目标 cgroup 路径,更接近 cgroup 限额;若主机可用内存持续耗尽并出现全局 OOM 记录,则应从主机容量、匿名内存、页缓存和 slab 一并排查。PSI 的 full 持续升高表示所有非空闲任务都受内存停顿影响,但 PSI 高本身不等于已经发生 OOM Kill。

3. 保留当前进程的内存构成

若服务已经自动拉起,当前 PID 是新实例,不能代替被杀进程的现场;它仍可用于寻找可重复增长的基线:

pid=$(systemctl show app.service -p MainPID --value)
ps -eo pid,ppid,user,rss,vsz,stat,comm --sort=-rss | head -n 20

if [ "${pid:-0}" -gt 0 ]; then
  sed -n '/^Name:/p;/^VmRSS:/p;/^VmSwap:/p;/^RssAnon:/p;/^RssFile:/p;/^Threads:/p;/^oom_score_adj:/p' \
    "/proc/$pid/status"
  [ ! -r "/proc/$pid/smaps_rollup" ] || \
    sed -n '/^Rss:/p;/^Pss:/p;/^Private_/p;/^Swap:/p' "/proc/$pid/smaps_rollup"
fi

RSS 排序适合找当前大户,但共享页会影响简单求和,短生命周期进程也可能已经消失。对容器化服务,应沿实际容器或 Pod 的 cgroup 读取指标,并核对编排器事件;只看宿主机 free 可能漏掉容器自己的硬上限。

4. 按证据修复并闭环

先区分增长来源:memory.statanon 持续增长更接近堆、栈或匿名映射,file 增长多与文件页缓存相关,slab 增长需要继续拆分内核对象。再结合应用指标、请求并发、队列积压和发布时间定位。不要仅凭一次 RSS 峰值宣布存在内存泄漏。

若确认为 cgroup 上限,先检查应用工作集和峰值是否合理,再决定降低并发、修复无界缓存,或调整 MemoryHighMemoryMaxMemoryHigh 主要施加回收压力,MemoryMax 是硬边界;盲目增大上限可能把局部故障扩大为主机级 OOM。若确认为主机级耗尽,还应检查同机其他服务、swap 策略和容量余量。

修复前后应在获准环境以相同负载窗口记录:服务 cgroup 的 memory.currentmemory.events、主机 MemAvailable 与 memory PSI、内核 OOM 日志以及应用成功率。闭环至少应满足 oom/oom_kill 计数不再增加、没有新增同类内核记录、内存峰值留有可解释余量,并且服务功能与延迟仍符合预期。