OOM Killer に関連するカーネルパラメータまとめ

Written by @ryysud

Oct 8, 2018 17:26 · 2390 words · 5 minutes read #linux

TL;DR

  • OOM Killer は /proc/${pid}/oom_score の値が大きいものから kill 対象を決定する
  • oom_score は /proc/${pid}/oom_score_adj と /proc/${pid}/oom_adj とその他プロセス情報を元に算出される
  • oom_score_adj = -1000 もしくは oom_adj = -17 とすると OOM Killer の kill 対象から除外することが出来る

OOM Killer とは

Linux カーネルから提供されている機能の一つで、OS のメモリー領域が枯渇することを避けるために、プロセスを強制終了する仕組みが OOM Killer です。ただし、サービスとして稼働させるために必要不可欠なプロセスもあるので、どのプロセスをどのようなルールで強制終了するかは、設定により制御することが可能です。

OOM Killer でプロセスが kill される様子

以下スペックのサーバーで実際に OOM Killer でプロセスが kill される様子を確認していきます。

  • CentOS 7.5
  • CPU 2core
  • Memory 2GB
$ cat /etc/redhat-release
CentOS Linux release 7.5.1804 (Core)

$ grep processor /proc/cpuinfo | wc -l
2

$ free -h
              total        used        free      shared  buff/cache   available
Mem:           3.7G         97M        3.3G         16M        337M        3.4G
Swap:            0B          0B          0B

stress コマンドを使ってメモリに負荷をかけてみると、stress コマンドの worker process である pid 1477 のプロセスに SIGKILL シグナルが投げられ kill されたことで、stress プロセス全体がエラーで終了することがわかります。

# stres コマンドをインストール
$ sudo yum install -y stress

# 4プロセスがそれぞれ1GBのメモリを確保する(積んである実メモリ相当の負荷)
$ stress --vm 4 --vm-bytes 1G --vm-hang 0
stress: info: [1474] dispatching hogs: 0 cpu, 0 io, 4 vm, 0 hdd
stress: FAIL: [1474] (415) <-- worker 1477 got signal 9
stress: WARN: [1474] (417) now reaping child worker processes
stress: FAIL: [1474] (451) failed run completed in 2s

実際にプロセスが kill されたので /var/log/messages を確認すると、OOM Killer により stress コマンドの worker process である pid 1477 のプロセスが kill されたことがわかります。

# カーネルダンプでログが多いので一部抜粋
$ tail -f /var/log/messages
Oct  8 09:27:09 ip-172-31-27-18 kernel: stress invoked oom-killer: gfp_mask=0x280da, order=0, oom_score_adj=0
Oct  8 09:27:09 ip-172-31-27-18 kernel: Out of memory: Kill process 1477 (stress) score 270 or sacrifice child
Oct  8 09:27:09 ip-172-31-27-18 kernel: Killed process 1477 (stress) total-vm:1055888kB, anon-rss:1048656kB, file-rss:0kB, shmem-rss:0kB

このように OS のメモリー領域が枯渇することを避けるために、OOM Killer がプロセスを強制終了する役割を担っていることがわかりました。

OOM Killer に関連するカーネルパラメータ

以下コマンドで、目ぼしいパラメータを洗い出した上で列挙していきます。なお、オーバコミット関連のカーネルパラメータ vm.overcommit_* は OOM Killer を制御する上で直接的には関係がないので、今回は割愛します。

$ sysctl -a | grep oom
$ ls -l /proc/* | grep oom

また、OS によっても微妙にカーネルパラメータが異なる可能性があるので、今回は RedHat系 と Debian系 にフォーカスしてまとめていきたいと思います。なお RedHat系 は CentOS 7.5 で Debian系 は Debian GNU/Linux 9 (stretch) を使用して確認していきます。

vm.oom_dump_tasks

OOM Killer が実行される際に、システム全体のタスク・ダンプ (カーネルスレッドを除く) を生成するかを制御するパラメータ。

説明 RedHat系デフォルト値(CentOS 7.5) Debian系デフォルト値(Debian GNU/Linux 9)
0 ダンプ情報を出力しない
1 ダンプ情報を出力する

vm.oom_kill_allocating_task

OOM Killer が実行される際に、メモリを獲得しようとしたプロセスのみを kill 対象とするかを制御するパラメータ。

説明 RedHat系デフォルト値(CentOS 7.5) Debian系デフォルト値(Debian GNU/Linux 9)
0 起動済みプロセスを kill 対象とする
1 メモリーを獲得しようとしたプロセスを kill 対象とする

vm.panic_on_oom

OOM Killer が実行される際に、カーネルパニックを起こさせるかを制御するパラメータ。カーネルパニック関連のカーネルパラメータである kernel.panic と組み合わせることで、OOM Killer 発生時に OS 再起動をキックしたりすることも可能です。

説明 RedHat系デフォルト値(CentOS 7.5) Debian系デフォルト値(Debian GNU/Linux 9)
0 カーネルパニックしない
1 カーネルパニックする。但し cgroup 制限により物理メモリがまだ残っている場合にはカーネルパニックしない。
2 必ずカーネルパニックする

プロセス毎に適用できるパラメータ

カーネルパラメータとは別に、プロセス毎に付与されるパラメータがあるので、そちらも列挙していきます。

/proc/${pid}/oom_adj

OOM Killer によって kill される優先度を制御するパラメータ。なお、子プロセスは親プロセスの値を引き継ぐ形となります。-17 から 15 までの整数値を格納することが可能となっており、値が大きいものから kill 対象とされていきます。但し -17 は例外的な値で -17 を適用することで OOM Killer の kill 対象から除外することが出来ます。

説明 RedHat系デフォルト値(CentOS 7.5) Debian系デフォルト値(Debian GNU/Linux 9)
1 から 15
0 初期値
-16 から -1
-17 OOM Killer によって kill されない

Linux 2.6.36 以降ではこちらのパラメータは非推奨となっており、後述の /proc/${pid}/oom_score_adj が推奨されています。

/proc/${pid}/oom_score_adj

/proc/${pid}/oom_adj と同じで OOM Killer によって kill される優先度を制御するパラメータ。-1000 から 1000 までの整数値を格納することが可能となっており、値が大きいものから kill 対象とされていきます。但し -1000 は例外的な値で -1000 を適用することで OOM Killer の kill 対象から除外することが出来ます。

説明 RedHat系デフォルト値(CentOS 7.5) Debian系デフォルト値(Debian GNU/Linux 9)
1 から 1000
0 初期値
-999 から -1
-1000 OOM Killer によって kill されない

/proc/${pid}/oom_score

OOM Killer が kill する対象を定めるパラメータ。/proc/${pid}/oom_adj 及び /proc/${pid}/oom_score_adj の値を元に算出され、値が大きいものから kill 対象となっていきます。

細かい計算式は追えていませんが、実行時間やnice値などの値も使って算出されているようです。なお /proc/${pid}/oom_adj に -17 もしくは /proc/${pid}/oom_score_adj に -1000 を適用すると、/proc/${pid}/oom_score は 0 となる式のようです。

すなわち /proc/${pid}/oom_score が 0 の場合には、そのプロセスは OOM Killer の kill 対象から除外されていることになります。

こちらの計算式に興味がある人はソースを見るなり、以下記事を眺めると理解が進むかもしれません。(私は心が折れました…。

まとめ

潤沢なリソースでサービスを運用していると、意外と盲点になるかもしれませんが、OS が機能するように OOM Killer が縁の下の力持ちな役割を担っていることがわかりました。また、今回で自らのカーネル知識の浅さが露呈したので、今後も学習していこうと思います。

参考資料