我不再将这个世界与我所期待的,塑造的圆满世界比较照,而是接受这个世界,爱它,属于它。 — 《悉达多》
写在前面
博文内容涉及到OOM Killer
机制,以及利用 Cgroup/dmesg/BPF 观测 OOM Killer 事件,包括云原生环境下的 OOM Killer 机制的简单介绍
这是内存调优的最后一篇,之后会分享一些网络调优相关内容
理解不足小伙伴帮忙指正 :),生活加油
我不再将这个世界与我所期待的,塑造的圆满世界比较照,而是接受这个世界,爱它,属于它。 — 《悉达多》
持续分享技术干货,感兴趣小伙伴可以关注下 ^_^
OOM Killer 机制如何工作? OOM Killer(Out-Of-Memory Killer)
是内核在系统内存严重不足时触发的紧急机制,通过终止进程释放内存以维持系统稳定
,每个进程有一个 OOM 相关的分数,终止进程的时候基于这个分数进行处理,有一些内核参数可以控制 OOM Killer 的行为,生产中考虑QOS
可以进行相关的配置,当然更合理的方式是使用Cgroup
对不同进程的内存资源进行限制,
OOMKiller 的工作原理是选择消耗最多内存的进程,该进程也被认为对系统操作最不重要。此选择过程基于多个因素,包括进程的内存使用情况、优先级以及运行的时间量。
一旦 OOMKiller 选择要终止的进程,它就会向该进程发送信号,要求它正常终止。如果进程不响应信号,内核将强制终止进程并释放其内存。
OOMKiller 是一种最后的手段机制,仅在系统面临内存不足的危险时才调用。虽然它可以帮助防止系统因内存耗尽而崩溃,但重要的是要注意,终止进程可能导致数据丢失和系统不稳定。因此,建议配置系统以避免 OOM 情况
,例如,通过监视内存使用情况、设置资源限制和优化应用程序中的内存使用情况。
可以通过调整内核参数来修改 ,OOM 是否自动触发。如果内核参数sysctl vm.panic_on_oom
设置为1而不是0,内核将会发生 panic,即直接摆烂,什么时候挂掉算什么时候。默设置0时.即自动启动OOM killer
1 2 3 4 ┌──[root@liruilongs.github.io]-[~] └─$ sysctl vm.panic_on_oom vm.panic_on_oom = 0
如果你希望强制的执行OOM Killer
,可以echo f > /proc/sysrq-trigger
,但请记住,至少会有一个进程被杀死。
1 2 3 4 [root@ecs-liruilong ~] Message from syslogd@ecs-liruilong at Aug 1 14:32:18 ... kernel:[340648.118967] Kernel panic - not syncing: Out of memory: system-wide panic_on_oom is enabled
输出将被发送到dmesg。
对于 OOM Killer 的触发进程和被 Kill 进程,内核会记录一些信息,这些信息可以通过dmesg
命令查看。
1 2 3 [日 5月 11 15:41:12 2025] Out of memory: Killed process 39693 (stress-ng) total-vm:2410396kB, anon-rss:1896300kB, file-rss:4kB, shmem-rss:60kB, UID:0 pgtables:3772kB oom_score_adj:1000
在后台,Linux 内核为主机上运行的每个进程保持一个运行不良评分。此分数越高,进程被终止的可能性就越大。
1 2 [root@ecs-liruilong ~] 0
分数越高,进程越有可能被OOM杀手杀死。许多因素被用来计算这个分数:
VM大小(不是RSS大小),
进程所有子进程的累积VM大小,
nice值(正的nice值会给出更高的分数),
总运行时间(较长的总运行时间会降低分数),
运行用户(根进程会得到轻微的保护),
进程执行直接硬件访问,分数也会降低。
内核本身和PID1 (sysemd)是免疫的OOM杀手。
需要说明的是这个分数是无法被修改的,但是可以调整oom_score_adj
的值,
在旧的内核版本中,这个值用 oom_adj
表示,通过的/proc/PID/oom_adj
可以用来手动调整oom_score
。配置该pid进程被oom killer杀掉的权重
,oom_adj
可以的值从-17到15
,其中0表示不改变(默认),越高的权重,意味着更可能被oom killer选中,-17表示免疫(永远不会杀死)。
1 2 [root@ecs-liruilong ~] 0
新内核版本中,之前的参数已经废弃,可以使用/proc/PID/oom_score_adj
来调整,范围是-1000到1000
,-1000
表示免疫OOM killer,1000
表示优先被OOM killer杀死。
1 2 [root@ecs-liruilong ~] 0
也可以在服务启动时配置,通过systemd
的OOMScoreAdjust
参数,可以设置OOM
的评分
1 2 3 4 5 6 7 cat > /etc/systemd/system/nginx.service.d/10-oom.conf << EOF [Service] # 设置OOM评分调整值 # 取值范围:-1000(最难被杀死)到1000(最易被杀死) # 这里设置为-500,适合重要但非核心的服务 OOMScoreAdjust=-500 EOF
对于一些云原生环境中,比如 Kubernetes 在为 Pod 定义服务质量 (QoS) 类时会使用该值。有三个 QoS 类可以分配给一个 pod,每个类都有一个匹配的值:oom_score_adj
Guaranteed
(完全可靠的): -997
BestEffort
: 1000
Burstable
: min(max(2, 1000 — (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999)
当前的 QoS 级别 直接由 Requests 和 Limits 来定义。在Kubernetes中容器的QoS级别等于容器所在Pod的QoS级别,查看 Pod 的 QoS 类,请运行以下命令:
1 2 3 ┌──[root@vms100.liruilongs.github.io]-[~/ansible/oomkiller] └─$kubectl get pod oom-killer-pod -o jsonpath='{.status.qosClass}' Burstable┌
如果Pod中的所有容器对所有资源类型都定义了 Limits
和Requests
,并且所有容器的Limits值都和Requests值全部相等(且都不为0),那么该Pod的QoS级别就是Guaranteed
Guaranteed
级别的 Pod 是 在节点过载时最后被 Kill 掉的 Pod,所以如果当前 Pod 很重要,建议设置为 Guaranteed
级别,可以减少当前节点其他 Pod 超用的影响,或者考虑 HPA
OOM Killer 如何观测? 下面实验用的 Linux 环境
1 2 3 4 5 6 7 8 9 10 11 12 13 [root@developer ~] Static hostname: developer Icon name: computer-vm Chassis: vm Machine ID: 7ad73f2b5f7046a2a389ca780f472467 Boot ID: cef15819a5c34efa92443b6eff608cc9 Virtualization: kvm Operating System: openEuler 22.03 (LTS-SP4) Kernel: Linux 5.10.0-250.0.0.154.oe2203sp4.aarch64 Architecture: arm64 Hardware Vendor: OpenStack Foundation Hardware Model: OpenStack Nova [root@developer ~]
利用 Cgroup/dmesg 观测 OOM Killer 事件 传统的 OOM killer
历史数据查看一般通过内核日志
查看,可以结合 Cgroup
相关内存子系统的事件计数器。
Cgroup
内存子系统有 OOM
相关的事件统计, memory.events 指标,是一个内存事件计数器:
只要系统使用了 systemd,那么就可以在对应的服务单元下找到对应的 内存事件统计
1 2 3 4 5 6 7 8 ┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system] └─$cat /sys/fs/cgroup/memory/system.slice/tuned.service/memory.events low 0 high 0 limit_in_bytes 0 oom 0 ┌──[root@liruilongs.github.io]-[/usr/lib/systemd/system] └─$
具体的参数指标说明:
low
: 低内存压力事件次数
high
: 高内存压力事件次数
limit_in_bytes
: 达到内存限制的次数
oom
: 这里的 OOM 指标为 OOM的触发次数。全为 0 表示无相关事件发生。
下面两个在上面的输出中没有,不同的内核版本,Cgroup版本对应的指标数据展示的不一样。
oom_kill
: OOM killer 触发次数,属于此cgroup的进程被任何类型的00M杀手杀死的数量。
oom_group_kill
: 对应的 Cgroup group 被 OOM Killer 的次数。
内核日志dmesg
可以显示详细的 OOM killer
进程相关数据
下面的日志:系统因内存耗尽触发了 OOM Killer
,终止了 stress-ng
进程(PID 39693),这是一个我们测试用的压测工具。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 [root@liruilongs.github.io ~] [日 5月 11 15:41:12 2025] Out of memory: Killed process 39693 (stress-ng) total-vm:2410396kB, anon-rss:1896300kB, file-rss:4kB, shmem-rss:60kB, UID:0 pgtables:3772kB oom_score_adj:1000 [日 5月 11 15:41:13 2025] stress-ng invoked oom-killer: gfp_mask=0x100dca(GFP_HIGHUSER_MOVABLE|__GFP_ZERO), order=0, oom_score_adj=1000 [日 5月 11 15:41:13 2025] CPU: 0 PID: 39692 Comm: stress-ng Kdump: loaded Not tainted 5.10.0-250.0.0.154.oe2203sp4.aarch64 [日 5月 11 15:41:13 2025] Hardware name: OpenStack Foundation OpenStack Nova, BIOS 0.0.0 02/06/2015 [日 5月 11 15:41:13 2025] Call trace: [日 5月 11 15:41:13 2025] dump_backtrace+0x0/0x214 [日 5月 11 15:41:13 2025] show_stack+0x20/0x2c [日 5月 11 15:41:13 2025] dump_stack+0xf0/0x138 [日 5月 11 15:41:13 2025] dump_header+0x50/0x1b0 [日 5月 11 15:41:13 2025] oom_kill_process+0x258/0x270 [日 5月 11 15:41:13 2025] out_of_memory+0xf4/0x3b0 [日 5月 11 15:41:13 2025] __alloc_pages+0x1024/0x1214 [日 5月 11 15:41:13 2025] alloc_pages_vma+0xb4/0x3e0 [日 5月 11 15:41:13 2025] do_anonymous_page+0x1d4/0x784 [日 5月 11 15:41:13 2025] handle_pte_fault+0x19c/0x240 [日 5月 11 15:41:13 2025] __handle_mm_fault+0x1bc/0x3ac [日 5月 11 15:41:13 2025] handle_mm_fault+0xf4/0x260 [日 5月 11 15:41:13 2025] do_page_fault+0x184/0x464 [日 5月 11 15:41:13 2025] do_translation_fault+0xb8/0xe4 [日 5月 11 15:41:13 2025] do_mem_abort+0x48/0xc0 [日 5月 11 15:41:13 2025] el0_da+0x44/0x80 [日 5月 11 15:41:13 2025] el0_sync_handler+0x68/0xc0 [日 5月 11 15:41:13 2025] el0_sync+0x160/0x180 [日 5月 11 15:41:13 2025] Mem-Info: [日 5月 11 15:41:13 2025] active_anon:4575 inactive_anon:1652614 isolated_anon:0 active_file:21 inactive_file:34 isolated_file:0 unevictable:1551 dirty:0 writeback:0 slab_reclaimable:5171 slab_unreclaimable:10557 mapped:2877 shmem:10220 pagetables:6130 bounce:0 free:14963 free_pcp:25 free_cma:0 [日 5月 11 15:41:13 2025] Node 0 active_anon:18300kB inactive_anon:6610456kB active_file:76kB inactive_file:132kB unevictable:6204kB isolated(anon):0kB isolated(file):0kB mapped:11508kB dirty:0kB writeback:0kB shmem:40880kB shmem_thp: 0kB shmem_pmdmapped: 0kB anon_thp: 3829760kB writeback_tmp:0kB kernel_stack:6384kB all_unreclaimable? yes [root@liruilongs.github.io ~]
从 dmesg
日志来看,系统因内存耗尽触发了 OOM Killer
,终止了 stress-ng
进程(PID 39693),内核日志可以看到详细的数据信息,
下面是日志中内存触发条件,也就是相关的第一条日志
1 2 3 [日 5月 11 15:41:12 2025] Out of memory: Killed process 39693 (stress-ng) total-vm:2410396kB, anon-rss:1896300kB, file-rss:4kB, shmem-rss:60kB, UID:0 pgtables:3772kB oom_score_adj:1000
这条日志对应字段的含义:
total-vm:2410396kB
:进程申请的总虚拟内存为 2.4GB(含物理内存和交换空间)
anon-rss:1896300kB
:实际占用的匿名内存(堆/栈)为 1.8GB, rss
说明是驻留的物理内存
file-rss:4kB
: 该进程当前通过文件映射
占用了 4KB 物理内存(进程通过 mmap()
映射文件)
shmem-rss:60kB : 该进程当前通过
共享内存占用了 60KB 物理内存(共享内存通常通过
shmget()或
tmpfs`(如 /dev/shm)实现)
UID:0
: 0 表示 root 用户
pgtables:3772kB
: 该进程的页表占用了约 3.7MB 内存
oom_score_adj:1000
:进程的 OOM 评分为最高(1000),因此被内核选为牺牲者。
下面的是 对应的函数调用栈,我们可以看到对应的内核态的系统函数调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [日 5月 11 15:41:13 2025] Call trace: [日 5月 11 15:41:13 2025] dump_backtrace+0x0/0x214 [日 5月 11 15:41:13 2025] show_stack+0x20/0x2c [日 5月 11 15:41:13 2025] dump_stack+0xf0/0x138 [日 5月 11 15:41:13 2025] dump_header+0x50/0x1b0 [日 5月 11 15:41:13 2025] oom_kill_process+0x258/0x270 [日 5月 11 15:41:13 2025] out_of_memory+0xf4/0x3b0 [日 5月 11 15:41:13 2025] __alloc_pages+0x1024/0x1214 [日 5月 11 15:41:13 2025] alloc_pages_vma+0xb4/0x3e0 [日 5月 11 15:41:13 2025] do_anonymous_page+0x1d4/0x784 [日 5月 11 15:41:13 2025] handle_pte_fault+0x19c/0x240 [日 5月 11 15:41:13 2025] __handle_mm_fault+0x1bc/0x3ac [日 5月 11 15:41:13 2025] handle_mm_fault+0xf4/0x260 [日 5月 11 15:41:13 2025] do_page_fault+0x184/0x464 [日 5月 11 15:41:13 2025] do_translation_fault+0xb8/0xe4 [日 5月 11 15:41:13 2025] do_mem_abort+0x48/0xc0 [日 5月 11 15:41:13 2025] el0_da+0x44/0x80 [日 5月 11 15:41:13 2025] el0_sync_handler+0x68/0xc0 [日 5月 11 15:41:13 2025] el0_sync+0x160/0x180 ...
函数调用栈下部分为内存分配失败 触发 进程 OOM 以及 oom_kill_process
的调用触发 内存杀手,后面部分为一些日志转储的操作。
下面为发生 OOM Kill 时候系统全局内存状态
1 2 3 4 5 6 7 [日 5月 11 15:41:13 2025] Mem-Info: [日 5月 11 15:41:13 2025] active_anon:4575 inactive_anon:1652614 isolated_anon:0 active_file:21 inactive_file:34 isolated_file:0 unevictable:1551 dirty:0 writeback:0 slab_reclaimable:5171 slab_unreclaimable:10557 mapped:2877 shmem:10220 pagetables:6130 bounce:0 free:14963 free_pcp:25 free_cma:0
在内核日志中看到发生异常是的内存具体指标数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 active_anon:4575kB inactive_anon:1652614kB isolated_anon:0kB active_file:21kB inactive_file:34kB isolated_file:0kB unevictable:1551kB slab_reclaimable:5171kB slab_unreclaimable:10557kB shmem:10220kB mapped:2877kB pagetables:6130kB free:14963kB free_pcp:25kB free_cma:0kB
同时会输出对应的 NUMA 节点 0 的内存指标
,可以看到几乎完全耗尽(尤其是匿名内存),且透明大页占用显著(anon_thp: 3829760kB),加剧了内存碎片化问题
1 [日 5月 11 15:41:13 2025] Node 0 active_anon:18300kB inactive_anon:6610456kB active_file:76kB inactive_file:132kB unevictable:6204kB isolated(anon):0kB isolated(file):0kB mapped:11508kB dirty:0kB writeback:0kB shmem:40880kB shmem_thp: 0kB shmem_pmdmapped: 0kB anon_thp: 3829760kB writeback_tmp:0kB kernel_stack:6384kB all_unreclaimable? yes
可以看到通过日子跟踪 OOM Killer
我们可以得到相对完善的 OOM killer
信息,以及触发是系统的内存状态信息
利用 BPF/eBPF 观测 OOM Killer 事件 对于 BPF 的监控,主要通过 BPF 和 bpftrace 的 oomkill
工具,我们可以在触发 OOM killer
事件之后,观察到系统平均负载等一些其他的信息,原理是通过动态插桩内核函数 oom_kill_process
(),捕获 OOM Killer
触发事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 [日 5月 11 15:41:13 2025] Call trace: [日 5月 11 15:41:13 2025] dump_backtrace+0x0/0x214 [日 5月 11 15:41:13 2025] show_stack+0x20/0x2c [日 5月 11 15:41:13 2025] dump_stack+0xf0/0x138 [日 5月 11 15:41:13 2025] dump_header+0x50/0x1b0 [日 5月 11 15:41:13 2025] oom_kill_process+0x258/0x270 [日 5月 11 15:41:13 2025] out_of_memory+0xf4/0x3b0. [日 5月 11 15:41:13 2025] __alloc_pages+0x1024/0x1214 [日 5月 11 15:41:13 2025] alloc_pages_vma+0xb4/0x3e0 [日 5月 11 15:41:13 2025] do_anonymous_page+0x1d4/0x784 [日 5月 11 15:41:13 2025] handle_pte_fault+0x19c/0x240 [日 5月 11 15:41:13 2025] __handle_mm_fault+0x1bc/0x3ac [日 5月 11 15:41:13 2025] handle_mm_fault+0xf4/0x260 [日 5月 11 15:41:13 2025] do_page_fault+0x184/0x464 [日 5月 11 15:41:13 2025] do_translation_fault+0xb8/0xe4 [日 5月 11 15:41:13 2025] do_mem_abort+0x48/0xc0 [日 5月 11 15:41:13 2025] el0_da+0x44/0x80 [日 5月 11 15:41:13 2025] el0_sync_handler+0x68/0xc0 [日 5月 11 15:41:13 2025] el0_sync+0x160/0x180 ...
当系统内存不足的时候,out_of_memory()
被触发,然后调用oom_kill_process()
杀掉进程
比如平均负载
信息可以在 OOM
发生时提供整个系统状态的一些 上下文信息
,展示出系统整体是正在变忙还是处于稳定状态
,以及那个进程触发了 OOM Killer 和,被 OOM Killer 杀掉的进程是那个等数据。
用内存测试工具简单复现一下 OOM killer
,我们看看如何监控,这里需要把交换分区禁用掉,要不换页进程(kswapd)
疯狂的输出,不太容易触发 OOM Killer
1 [root@liruilongs.github.io ~]
stress-ng
对 Linux 系统内存施加高压负载
1 2 3 4 5 [root@liruilongs.github.io ~] stress-ng: info: [37336] setting to a 60 second run per stressor stress-ng: info: [37336] dispatching hogs: 4 vm ^[c^Cstress-ng: info: [37336] successful run completed in 40.87s [root@liruilongs.github.io ~]
通过 free
命令观察内存使用情况,中间的那一次输出可以直观的看到内存使用情况
1 2 3 4 5 6 7 8 9 10 11 12 13 [root@liruilongs.github.io ~] total used free shared buff/cache available Mem: 6.5Gi 815Mi 5.6Gi 37Mi 293Mi 5.7Gi Swap: 0B 0B 0B total used free shared buff/cache available Mem: 6.5Gi 6.4Gi 183Mi 39Mi 113Mi 139Mi Swap: 0B 0B 0B total used free shared buff/cache available Mem: 6.5Gi 4.5Gi 2.0Gi 39Mi 84Mi 2.0Gi Swap: 0B 0B 0B [root@liruilongs.github.io ~]
通过 oomkill
工具观察 OOM Killer
情况
内存分配失败调用栈,上面的 BPF 工具实际上是在 oom_kill_process
内核函数处埋点实现的
可以看到触发的进程主要是 stress-ng(内存压力测试工具)
持续申请内存,导致系统物理内存耗尽。部分系统进程(如 oeaware、Xvnc)
也触发 OOM
,说明内存竞争激烈,系统整体处于高压状态。通过负载指标:loadavg 值较高(如 4.59),表明 CPU 资源负载在升高 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [root@liruilongs.github.io ~] Tracing OOM kills... Ctrl-C to stop. 15:41:14 Triggered by PID 1039 ("oeaware" ), OOM kill of PID 39693 ("stress-ng" ), 1704429 pages, loadavg: 4.34 2.87 1.77 6/396 39695 15:41:15 Triggered by PID 39692 ("stress-ng" ), OOM kill of PID 39692 ("stress-ng" ), 1704429 pages, loadavg: 4.34 2.87 1.77 5/396 39696 15:41:16 Triggered by PID 39696 ("stress-ng" ), OOM kill of PID 39694 ("stress-ng" ), 1704429 pages, loadavg: 4.31 2.89 1.78 5/396 39697 15:41:17 Triggered by PID 39698 ("stress-ng" ), OOM kill of PID 39695 ("stress-ng" ), 1704429 pages, loadavg: 4.31 2.89 1.78 5/396 39699 15:41:19 Triggered by PID 1039 ("oeaware" ), OOM kill of PID 39696 ("stress-ng" ), 1704429 pages, loadavg: 4.31 2.89 1.78 5/396 39700 15:41:20 Triggered by PID 2121 ("ibus-ui-gtk3" ), OOM kill of PID 39697 ("stress-ng" ), 1704429 pages, loadavg: 4.31 2.89 1.78 6/396 39701 15:41:22 Triggered by PID 39699 ("stress-ng" ), OOM kill of PID 39698 ("stress-ng" ), 1704429 pages, loadavg: 4.29 2.91 1.80 5/396 39701 15:41:23 Triggered by PID 39700 ("stress-ng" ), OOM kill of PID 39700 ("stress-ng" ), 1704429 pages, loadavg: 4.29 2.91 1.80 6/396 39702 15:41:24 Triggered by PID 39701 ("stress-ng" ), OOM kill of PID 39699 ("stress-ng" ), 1704429 pages, loadavg: 4.29 2.91 1.80 5/396 39704 15:41:25 Triggered by PID 39702 ("stress-ng" ), OOM kill of PID 39701 ("stress-ng" ), 1704429 pages, loadavg: 4.29 2.91 1.80 5/396 39704 15:41:26 Triggered by PID 39703 ("stress-ng" ), OOM kill of PID 39702 ("stress-ng" ), 1704429 pages, loadavg: 4.59 3.00 1.83 5/396 39705 15:41:27 Triggered by PID 39705 ("stress-ng" ), OOM kill of PID 39703 ("stress-ng" ), 1704429 pages, loadavg: 4.59 3.00 1.83 5/396 39706 15:41:29 Triggered by PID 1304 ("Xvnc" ), OOM kill of PID 39704 ("stress-ng" ), 1704429 pages, loadavg: 4.59 3.00 1.83 6/396 39708 15:41:30 Triggered by PID 1492 ("lightdm-gtk-gre" ), OOM kill of PID 39705 ("stress-ng" ), 1704429 pages, loadavg: 4.59 3.00 1.83 5/395 39708 15:41:31 Triggered by PID 39709 ("stress-ng" ), OOM kill of PID 39706 ("stress-ng" ), 1704429 pages, loadavg: 4.94 3.10 1.87 8/395 39710
看下一下输出的指标信息,已第一条日志为例
1 15:41:14 Triggered by PID 1039 ("oeaware" ), OOM kill of PID 39693 ("stress-ng" ), 1704429 pages, loadavg: 4.34 2.87 1.77 6/396 39695。
字段
含义
Triggered by PID
触发 OOM 的进程 PID(如内存申请者)
OOM kill of PID
被 OOM Killer 终止的进程 PID
1704429 pages
被终止进程占用的物理内存页数(1页=4KB,换算为 6.8GB)
loadavg
系统负载(1分钟/5分钟/15分钟平均负载)
6/396
当前可运行进程数/总进程数
39695
最后被创建的进程 PID
当然上面的输出的功能有些简单,如果我们希望获取更多的数据信息,我们可以通过修改原来脚本的方式实现
bpftrace 对应的脚本
https://github.com/brendangregg/bpf-perf-tools-book/blob/master/originals/Ch07_Memory/oomkill.bt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 [root@liruilongs.github.io ~] /* * oomkill Trace OOM killer. * For Linux, uses bpftrace and eBPF. * * This traces the kernel out-of-memory killer, and prints basic details, * including the system load averages. This can provide more context on the * system state at the time of OOM: was it getting busier or steady, based * on the load averages? This tool may also be useful to customize for * investigations; for example, by adding other task_struct details at the * time of the OOM, or other commands in the system() call. * * This currently works by using kernel dynamic tracing of oom_kill_process(). * * USAGE: oomkill.bt * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License" ) * * 07-Sep-2018 Brendan Gregg Created this. */ BEGIN { printf ("Tracing oom_kill_process()... Hit Ctrl-C to end.\n" ); } kprobe:oom_kill_process { $oc = (struct oom_control *)arg0; time("%H:%M:%S " ); printf ("Triggered by PID %d (\"%s\"), " , pid, comm); printf ("OOM kill of PID %d (\"%s\"), %d pages, loadavg: " , $oc ->chosen->pid, $oc ->chosen->comm, $oc ->totalpages); cat("/proc/loadavg" ); } [root@liruilongs.github.io ~]
通过动态插桩内核函数 oom_kill_process
(),捕获 OOM Killer
触发事件,同时输出了一些其他的指标信息,自定义 OOM Killer 发生时的性能指标采集
下面是最上面脚本的基础上添加的一些他的指标数据采集,从而实现在 OOM Killer 发生时快速的定位问题
添加 /proc/meminfo
的全局内存指标信息,meminfo
提供了系统范围内内存统计数据
的超集,包括了vmstat、top、free和procinfo
的信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <linux/oom.h> BEGIN { printf ("Tracing oom_kill_process()... Hit Ctrl-C to end.\n" ); } kprobe:oom_kill_process { $oc = (struct oom_control *)arg0; $task = $oc->chosen; time("%H:%M:%S " ); printf ("Triggered by PID %d (\"%s\"), " , pid, comm); printf ("OOM kill of PID %d (\"%s\"), %d pages, loadavg: " , $oc->chosen->pid, $oc->chosen->comm, $oc->totalpages); cat("/proc/loadavg" ); print("当前系统内存性能统计信息:" ); cat("/proc/meminfo" ); }
修改脚本后的再次监控指标采集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 [root@developer tools] [root@developer tools] Attaching 2 probes... Tracing oom_kill_process()... Hit Ctrl-C to end. 19:06:46 Triggered by PID 1039 ("oeaware" ), OOM kill of PID 1528049 ("stress-ng" ), 1704429 pages, loadavg: 4.14 2.26 1.35 5/405 1528051 当前系统内存性能统计信息: MemTotal: 6817716 kB MemFree: 1494936 kB MemAvailable: 1442388 kB Buffers: 372 kB Cached: 155720 kB SwapCached: 0 kB Active: 31476 kB Inactive: 5156300 kB Active(anon): 31388 kB Inactive(anon): 5117004 kB Active(file): 88 kB Inactive(file): 39296 kB Unevictable: 6236 kB Mlocked: 76 kB SwapTotal: 0 kB SwapFree: 0 kB Dirty: 0 kB Writeback: 0 kB AnonPages: 5038288 kB Mapped: 21148 kB Shmem: 116328 kB KReclaimable: 21692 kB Slab: 66816 kB SReclaimable: 21692 kB SUnreclaim: 45124 kB KernelStack: 6528 kB PageTables: 22160 kB NFS_Unstable: 0 kB Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 3408856 kB Committed_AS: 12252200 kB VmallocTotal: 135290159040 kB VmallocUsed: 16152 kB VmallocChunk: 0 kB Percpu: 2800 kB HardwareCorrupted: 0 kB AnonHugePages: 4009984 kB ShmemHugePages: 0 kB ShmemPmdMapped: 0 kB FileHugePages: 0 kB FilePmdMapped: 0 kB CmaTotal: 0 kB CmaFree: 0 kB HugePages_Total: 0 HugePages_Free: 0 HugePages_Rsvd: 0 HugePages_Surp: 0 Hugepagesize: 2048 kB Hugetlb: 0 kB
对于内核态的内存分配,可以添加 /proc/slabinfo
相关指标的采集
上面是全局的内存信息的指标采集,当然也可以采集对被 kill 进程以及 创建的进程的相关的指标信息
下面的为提供的 BCC 版本的 oomkill 工具
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 [root@liruilongs.github.io ~] from bpfcc import BPFfrom time import strftimeloadavg = "/proc/loadavg" bpf_text = """ #include <uapi/linux/ptrace.h> #include <linux/oom.h> struct data_t { u32 fpid; u32 tpid; u64 pages; char fcomm[TASK_COMM_LEN]; char tcomm[TASK_COMM_LEN]; }; BPF_PERF_OUTPUT(events); void kprobe__oom_kill_process(struct pt_regs *ctx, struct oom_control *oc, const char *message) { unsigned long totalpages; struct task_struct *p = oc->chosen; struct data_t data = {}; u32 pid = bpf_get_current_pid_tgid() >> 32; data.fpid = pid; data.tpid = p->pid; data.pages = oc->totalpages; bpf_get_current_comm(&data.fcomm, sizeof(data.fcomm)); bpf_probe_read_kernel(&data.tcomm, sizeof(data.tcomm), p->comm); events.perf_submit(ctx, &data, sizeof(data)); } """ def print_event (cpu, data, size ): event = b["events" ].event(data) with open (loadavg) as stats: avgline = stats.read().rstrip() print (("%s Triggered by PID %d (\"%s\"), OOM kill of PID %d (\"%s\")" ", %d pages, loadavg: %s" ) % (strftime("%H:%M:%S" ), event.fpid, event.fcomm.decode('utf-8' , 'replace' ), event.tpid, event.tcomm.decode('utf-8' , 'replace' ), event.pages, avgline)) b = BPF(text=bpf_text) print ("Tracing OOM kills... Ctrl-C to stop." )b["events" ].open_perf_buffer(print_event) while 1 : try : b.perf_buffer_poll() except KeyboardInterrupt: exit() [root@liruilongs.github.io ~]
我们对这个工具做一些简单的修改, /proc/pid/status 用于展示当前进程的一些基本指标
1 2 3 4 5 6 7 8 9 10 11 12 def print_event (cpu, data, size ): event = b["events" ].event(data) with open (loadavg) as stats: avgline = stats.read().rstrip() with open ("/proc/" + str (event.fpid) +"/status" ) as statm: statmtable = statm.read().rstrip() print (("%s Triggered by PID %d (\"%s\"), OOM kill of PID %d (\"%s\")" ", %d pages, loadavg: %s" ) % (strftime("%H:%M:%S" ), event.fpid, event.fcomm.decode('utf-8' , 'replace' ), event.tpid, event.tcomm.decode('utf-8' , 'replace' ), event.pages, avgline)) print ("新进程指标信息: &s" ,statmtable)
下面为输出结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 [root@developer tools] Tracing OOM kills... Ctrl-C to stop. 19:42:24 Triggered by PID 1539774 ("stress-ng" ), OOM kill of PID 1539775 ("stress-ng" ), 1704429 pages, loadavg: 2.40 1.72 1.07 5/407 1539778 新进程指标信息: &s Name: stress-ng Umask: 0077 State: R (running) Tgid: 1539774 Ngid: 0 Pid: 1539774 PPid: 1539770 TracerPid: 0 Uid: 0 0 0 0 Gid: 0 0 0 0 FDSize: 64 Groups: 0 NStgid: 1539774 NSpid: 1539774 NSpgid: 1539769 NSsid: 32074 VmPeak: 2410404 kB VmSize: 2410404 kB VmLck: 0 kB VmPin: 0 kB VmHWM: 1415824 kB VmRSS: 1415824 kB RssAnon: 1415760 kB RssFile: 4 kB RssShmem: 60 kB VmData: 2369388 kB VmStk: 132 kB VmExe: 1416 kB VmLib: 2888 kB VmPTE: 2828 kB VmSwap: 0 kB HugetlbPages: 0 kB CoreDumping: 0 THP_enabled: 1 Threads: 1 SigQ: 0/26524 SigPnd: 0000000000000000 ShdPnd: 0000000000000000 SigBlk: 0000000000000000 SigIgn: 0000000008300a00 SigCgt: 000000002380e0af CapInh: 0000000000000000 CapPrm: 000001ffffffffff CapEff: 000001ffffffffff CapBnd: 000001ffffffffff CapAmb: 0000000000000000 NoNewPrivs: 0 Seccomp: 0 Seccomp_filters: 0 Speculation_Store_Bypass: vulnerable Cpus_allowed: f Cpus_allowed_list: 0-3 Mems_allowed: 00000000,00000000,00000000,00000001 Mems_allowed_list: 0 voluntary_ctxt_switches: 32 nonvoluntary_ctxt_switches: 837 Cpus_preferred: 0 Cpus_preferred_list:
我们可以同时采集到 VmRSS
等有用的指标信息。
云原生环境下 K8s 的 OOMKiller 观测 在 K8s 的生产环境中,我们可能会看到 Pod 状态为 OOM Killed 的情况,这里的K8s OOMKilled
实际上是分两种情况,
第一种为宿主节点行为,即 OOMKillde
是由宿主机内核直接触发,当 Pod 中没有进行资源限制,会无限制的使用宿主节点资源,触发的 OOMKillde
.
第二种即 K8s 行为,Pod 配置了资源限制,超过了资源限制,由Cgroups
触发的 OOMKillde
宿主节点行为 对于第一种我们简单看一个 Demo,创建一个没有限制资源的pod
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources] └─$cat pod-demo.yaml apiVersion: v1 kind: Pod metadata: creationTimestamp: null labels: run: pod-demo name: pod-demo spec: containers: - image: hub.c.163.com/library/centos imagePullPolicy: IfNotPresent name: pod-demo command: ['sh' ,'-c' ,'sleep 500000' ] resources: {} dnsPolicy: ClusterFirst restartPolicy: Always status: {}
pod创建成功后,我们可以看到调度到了vms83.liruilongs.github.io
1 2 3 4 5 6 7 8 9 10 11 12 ┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources] └─$kubectl get pods -o wide No resources found in resources namespace. ┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources] └─$vim pod-demo.yaml ┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources] └─$kubectl apply -f pod-demo.yaml pod/pod-demo created ┌──[root@vms81.liruilongs.github.io]-[~/ansible/resources] └─$kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod-demo 1/1 Running 0 42s 10.244.70.50 vms83.liruilongs.github.io <none> <none>
这里我们在pod里安装一个内存分配工具bigmem
,用于模拟pod中容器进程内存不回收的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$kubectl cp ./bigmem-7.0-1.r29766.x86_64.rpm pod-demo:/root/ ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$kubectl exec -it pod-demo -- bin/bash [root@pod-demo /] [root@pod-demo ~] anaconda-ks.cfg bigmem-7.0-1.r29766.x86_64.rpm original-ks.cfg [root@pod-demo ~] Preparing... Updating / installing... 1:bigmem-7.0-1.r29766 [root@pod-demo ~] Attempting to allocate 1000 Mebibytes of resident memory... Press <Enter> to exit ^C [root@pod-demo ~] Attempting to allocate 2000 Mebibytes of resident memory... Killed
通过上下内存信息可以发现,当分配1000M内存时,宿主机用户使用内存增加了1000M,可用内存为117M,当申请内存为2000M时,超出宿主机可用内存,bigmem 2000M
命令所在进程直接被kill了。
1 2 3 4 5 6 7 8 9 10 11 12 ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$ansible vms83.liruilongs.github.io -m shell -a "free -h" vms83.liruilongs.github.io | CHANGED | rc=0 >> total used free shared buff/cache available Mem: 4.4G 2.5G 583M 216M 1.4G 1.4G Swap: 0B 0B 0B ┌──[root@vms81.liruilongs.github.io]-[~/ansible] └─$ansible vms83.liruilongs.github.io -m shell -a "free -h" vms83.liruilongs.github.io | CHANGED | rc=0 >> total used free shared buff/cache available Mem: 4.4G 3.5G 117M 216M 857M 456M Swap: 0B 0B 0B
查看宿主机日志 /var/log/messages
,可以发现bigmem
所在进程造成OOM。被OOM killer 杀掉了。
1 2 3 4 ┌──[root@vms83.liruilongs.github.io]-[~] └─$cat /var/log /messages | grep -i memory Aug 10 20:37:27 vms83 kernel: [<ffffffff81186bd6>] out_of_memory+0x4b6/0x4f0 Aug 10 20:37:27 vms83 kernel: Out of memory: Kill process 25143 (bigmem) score 1347 or sacrifice child
K8s 行为 对于由 k8s 通过 Cgroup 促发的 OOMKilled
,我们可以在 describe
看到类似下面相关信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ┌──[root@vms100.liruilongs.github.io]-[~/ansible/oomkiller] └─$kubectl describe pods oom-killer-pod | grep -A 10 State State: Waiting Reason: CrashLoopBackOff Last State: Terminated Reason: OOMKilled Exit Code: 1 Started: Mon, 03 Jul 2023 12:17:09 +0800 Finished: Mon, 03 Jul 2023 12:17:10 +0800 Ready: False Restart Count: 5 Limits: memory: 200Mi Requests: memory: 200Mi ┌──[root@vms100.liruilongs.github.io]-[~/ansible/oomkiller] └─$
当 Kubernetes 集群中的容器超出其内存限制时,Kubernetes 系统可能会终止该容器,并显示“OOMKilled”错误,该错误表示该进程因内存不足而终止。此错误的退出代码为 1,
对于在生产环境的 Pod ,OOMKilled
常常 伴随这 CrashLoopBackOff
,触发 OOM 之后,被 Kill 掉,之后由于 Pod 重启机制,会陷入 CrashLoopBackOff
循环
测试环境准备
1 2 3 4 5 6 ┌──[root@vms100.liruilongs.github.io]-[~/ansible/oomkiller] └─$kubectl create ns oom namespace/oom created ┌──[root@vms100.liruilongs.github.io]-[~/ansible/oomkiller] └─$kubectl config set-context --current --namespace=oom Context "kubernetes-admin@kubernetes" modified.
Pod Yaml 文件,可以看到和上面不同是我们添加了内存限制 limits.memory: 200Mi
,使用 stress 工具来对内存进行OOM 模拟
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ┌──[root@vms100.liruilongs.github.io]-[~/ansible/oomkiller] └─$cat oom-killer-pod.yaml apiVersion: v1 kind: Pod metadata: name: oom-killer-pod spec: containers: - name: oom-killer-container image: polinux/stress command : ["stress" ] args: ["--vm" , "1" , "--vm-bytes" , "200M" , "--timeout" , "10s" ,"--verbose" ] resources: limits: memory: "200Mi"
应用测试,查看 OOMKilled
的情况
1 2 3 ┌──[root@vms100.liruilongs.github.io]-[~/ansible/oomkiller] └─$kubectl apply -f oom-killer-pod.yaml pod/oom-killer-pod created
可以很直观的看到 pod 的状态在 OOMKilled
之后,会进入 CrashLoopBackOff
循环
1 2 3 4 5 6 7 8 9 ┌──[root@vms100.liruilongs.github.io]-[~/ansible/oomkiller] └─$kubectl get pods oom-killer-pod -w NAME READY STATUS RESTARTS AGE oom-killer-pod 0/1 ContainerCreating 0 7s oom-killer-pod 1/1 Running 0 25s oom-killer-pod 0/1 OOMKilled 0 28s oom-killer-pod 0/1 OOMKilled 1 (18s ago) 45s oom-killer-pod 0/1 CrashLoopBackOff 1 (13s ago) 57s oom-killer-pod 0/1 OOMKilled 2 (29s ago) 73s
查看对应宿主节点的日志 /var/log/messages
,可以看到类似这样一条 OOMKilled
日志,即由 cgroup
的限制 促发了 OOMKilled
1 Aug 10 21:09:11 vms106 kernel: Memory cgroup out of memory: Kill process ........
这些需要说明在调整内存请求和限制时,当节点过载时,Kubernetes 会根据(Qos 等级)以下优先级顺序杀死 Pod
没有请求或限制的 Pod
有请求但没有限制的 Pod
使用 的 Pod 超过其内存请求值(指定的最小内存),但低于其内存限制
使用超过其内存限制的 Pod
博文部分内容参考 © 文中涉及参考链接内容版权归原作者所有,如有侵权请告知 :)
https://facebookmicrosites.github.io/cgroup2/docs/memory-controller.html
https://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup.git/tree/Documentation/admin-guide/cgroup-v2.rst
https://itnext.io/how-to-fix-oomkilled-kubernetes-error-exit-code-137-88352410f0d4
© 2018-至今 liruilonger@gmail.com , 保持署名-非商用-相同方式共享(CC BY-NC-SA 4.0)