気づくとこの連載も60回目です。
最近は、Linuxカーネルに実装されるコンテナ関連の機能が高度になり、機能を調べて理解するのが難しくなってきました。さらに、どのように紹介すればよいかというところも悩ましいところです。そのため、連載のペースが落ちっぱなしですが、連載自体は続けていきたいと思っていますので、引き続きよろしくお願いいたします。
OOM Killerについて
これまで第56回や第57回で見たように、cgroupに所属するタスクが消費するメモリがmemory.で設定した制限値を超えた場合、OOM Killerが呼ばれ、プロセスがkillされました。
OOM Killerによってkillされるプロセスは、プロセスが持つOOMスコアをもとに選ばれ、もっともOOMスコアが高いプロセスが選ばれます。
このOOMスコアは、procファイルシステム内のプロセスディレクトリーである/proc/[PID]/内にあるoom_ファイルから取得できます。また、oom_ファイルで優先度を調整できます。OOMスコアを計算する際には、このoom_の値が考慮されます。
| ファイル名 | 説明 |
|---|---|
oom_ |
プロセスの現在のOOMスコア |
oom_ |
OOMスコアの計算の際に足される調整値 |
oom_ |
昔のカーネルで使われていた調整値 |
本連載では、OOMスコアの計算についてはこれ以上説明しません。詳細を知りたい方は、カーネル付属文書の"The /proc Filesystem"をご覧ください。
cgroupにおいてOOM Killerが発動する際は、デフォルトではこのOOMスコアによって、cgroupに所属するプロセスからkillするプロセスが選ばれます。OOMスコアによって選ばれた、単一のプロセスがkillの候補となります。
OOM Killerの動きは、システム全体とcgroupでは変わりません。違いは、システム全体のプロセスから選ぶか、cgroupに所属するプロセスのみを対象にするかの違いです。
memory.oom.group
前述のように、cgroup内でmemory.で設定した制限値を超えるメモリが使用されると、OOM Killerが発動し、高いOOMスコアを持つプロセスがkillされます。
ところが、アプリケーションコンテナ環境では、コンテナ内のタスクをすべて同一のcgroupに所属させることが多く、コンテナ内では必要なタスクのみが動いているはずです。この状態でOOM Killerが発動し、コンテナ内のタスクが1つだけkillされると、コンテナ内で実行されているワークロードの動作に不都合が生じる可能性が高くなります。
そこで4.memory.という設定ファイルがcgroupに追加されました。
memory.oom.groupの値と動作memory.の値 |
動作 |
|---|---|
| 0 | デフォルト値。従来どおりのOOM Killerの動き。OOMスコアに応じてcgroupに所属するプロセスを選び、killする |
| 1 | 1に設定されているcgroupにプロセスが所属している場合はそのcgroupに所属するプロセスすべてを、子孫のcgroupを持つ場合は、その子孫cgroupに所属するプロセスすべてをまとめてkillする |
表2のように、このファイルの内容は、デフォルトでは0であり、この機能は無効です。ファイルに1を書き込むと、機能が有効になります。
機能を有効にすると、cgroup内で実行されているプロセスすべてを分割できないワークロードとして扱い、制限値を超えた場合はcgroupに所属するプロセスをまとめてkillします。
memory.を有効にしたcgroupに子孫cgroupが存在する場合は、その子孫cgroupに所属するプロセスすべてがまとめてkillされます。
0 の場合
memory.が0に設定されている場合は、これまでの回で紹介したような動きになります。比較のために今回も動作を確認しておきましょう。
cgroupでメモリ制限値を設定し、プロセスをいくつか所属させた上で、メモリを制限値以上に消費するプログラムを実行してcgroupに所属するプロセスの様子を確認します。
次のように、root cgroup直下にtest01 cgroupを、その子cgroupとしてtest011 cgroupを作成し、test011 cgroupでmemoryコントローラが使えるように設定します。
$ sudo mkdir -p /sys/fs/cgroup/test01/test011 (cgroupを作成) $ echo $$ | sudo tee /sys/fs/cgroup/test01/test011/cgroup.procs (シェルをtest011 cgroupに登録) 1041 $ echo "+memory" | sudo tee /sys/fs/cgroup/cgroup.subtree_control (子cgroupでmemoryコントローラを使えるようにroot cgroupを設定) +memory $ echo "+memory" | sudo tee /sys/fs/cgroup/test01/cgroup.subtree_control (子cgroupでmemoryコントローラを使えるようにtest01 cgroupを設定) +memory
次に、メモリ制限値を設定します。
$ echo 192M | sudo tee /sys/fs/cgroup/test01/test011/memory.max (test011 cgroupで192MBのメモリ制限値を設定) 192M $ sudo swapoff -a (スワップの使用を停止)
test011 cgroupで192MBにメモリ制限値を設定し、OOM Killerを発動しやすくするためにスワップの使用を停止しました。
メモリを大量に消費するプロセス以外はkillされないことを確認するために、あらかじめsleepコマンドを2つ実行しておき、test011 cgroupに所属させます。
$ sleep 7200 & [1] 1083 $ sleep 7200 & [2] 1084 $ echo 1083 | sudo tee /sys/fs/cgroup/test01/test011/cgroup.procs 1083 $ echo 1084 | sudo tee /sys/fs/cgroup/test01/test011/cgroup.procs 1084 (sleepプロセスを起動し、test011 cgroupにプロセスを追加)
これで準備ができました。test011 cgroupに所属するmemory.の値が0であることを確認して、ここまでの実行例と同様にstress-ngコマンドを実行し、メモリを制限値以上に消費させます。
$ cat /sys/fs/cgroup/test01/test011/memory.oom.group 0 (memory.oom.groupの値が0であることを確認) $ stress-ng --vm 1 --vm-bytes 256M --vm-hang 0 -v :(略) stress-ng: debug: [1107] vm: child died: signal 9 'SIGKILL' (instance 0) stress-ng: debug: [1107] vm: assuming killed by OOM killer, restarting again (instance 0) stress-ng: debug: [1107] vm: child died: signal 9 'SIGKILL' (instance 0) stress-ng: debug: [1107] vm: assuming killed by OOM killer, restarting again (instance 0) :(略)
すると、このように制限値を超えたためにOOM Killerが発動し、stress-ngコマンドのプロセスがkillされます。このとき、test011 cgroupに所属するその他のプロセスがどうなっているかを確認します。
$ cat /sys/fs/cgroup/test01/test011/cgroup.procs
1041
1083
1084
1120
(test011 cgroupに所属するstress-ng以外のプロセスは所属したまま)
$ ps -p 1083,1084
PID TTY TIME CMD
1083 pts/1 00:00:00 sleep
1084 pts/1 00:00:00 sleep
(起動していたsleepプロセスは実行中のまま)
test011 cgroupで実行していたシェルとsleepコマンドはいずれも実行中で、killされていないことが確認できました。
1 の場合
それでは、memory.に1を設定して、この機能を有効にしたときの動きを見ていきましょう。
まずは、さきほど起動したsleepコマンドによるプロセスを終了させます。そして、再度sleepコマンドをnohupで実行します。
$ killall sleep $ nohup sleep 7200 & [1] 1135 $ nohup sleep 7200 & [2] 1136 $ cat /sys/fs/cgroup/test01/test011/cgroup.procs 1041 1135 1136 1137
これで、シェルと、シェルがkillされても停止しないsleepコマンドがtest011 cgroupに所属しました。
次のようにmemory.に1を設定し、stress-ngで制限値以上にメモリを消費させます。
$ echo 1 | sudo tee /sys/fs/cgroup/test01/test011/memory.oom.group (memory.oom.groupに1を設定) 1 $ cat /sys/fs/cgroup/test01/test011/memory.oom.group 1 (設定された) $ stress-ng --vm 1 --vm-bytes 256M --vm-hang 0 -v (実行していたシェルが kill される)
stress-ngを実行した途端に、stress-ngを実行していたシェルごと強制終了させられました。
別シェルからsleepコマンドのプロセスを調べると存在しませんし、test011 cgroupに所属するプロセスもありません。
$ ps -p 1135,1136 PID TTY TIME CMD (存在しない) $ cat /sys/fs/cgroup/test01/test011/cgroup.procs (test011 cgroupにもプロセスがいない)
dmesgでカーネルメッセージを確認すると、次のように、Tasks in /test01/という説明とともに、test011 cgroupに所属していたプロセスがすべてkillされていることがわかります。
[ 655.137094] oom-kill:constraint=CONSTRAINT_MEMCG,nodemask=(null),cpuset=/,mems_allowed=0,oom_memcg=/test01/test011,task_memcg=/test01/test011,task=stress-ng-vm,pid=1160,uid=1000 [ 655.137110] Memory cgroup out of memory: Killed process 1160 (stress-ng-vm) total-vm:525564kB, anon-rss:176556kB, file-rss:640kB, shmem-rss:0kB, UID:1000 pgtables:444kB oom_score_adj:1000 [ 655.137740] Tasks in /test01/test011 are going to be killed due to memory.oom.group set [ 655.137753] Memory cgroup out of memory: Killed process 1041 (bash) total-vm:9008kB, anon-rss:1920kB, file-rss:3968kB, shmem-rss:0kB, UID:1000 pgtables:56kB oom_score_adj:0 [ 655.137973] Memory cgroup out of memory: Killed process 1135 (sleep) total-vm:5684kB, anon-rss:0kB, file-rss:2048kB, shmem-rss:0kB, UID:1000 pgtables:64kB oom_score_adj:0 [ 655.138205] Memory cgroup out of memory: Killed process 1136 (sleep) total-vm:5684kB, anon-rss:0kB, file-rss:2048kB, shmem-rss:0kB, UID:1000 pgtables:52kB oom_score_adj:0 [ 655.138495] Memory cgroup out of memory: Killed process 1153 (stress-ng) total-vm:263416kB, anon-rss:8832kB, file-rss:6784kB, shmem-rss:16512kB, UID:1000 pgtables:364kB oom_score_adj:0 [ 655.138716] Memory cgroup out of memory: Killed process 1159 (stress-ng-vm) total-vm:263420kB, anon-rss:9000kB, file-rss:1280kB, shmem-rss:0kB, UID:1000 pgtables:116kB oom_score_adj:0 [ 655.138929] Memory cgroup out of memory: Killed process 1160 (stress-ng-vm) total-vm:525564kB, anon-rss:176556kB, file-rss:640kB, shmem-rss:0kB, UID:1000 pgtables:444kB oom_score_adj:1000
次に、memory.を有効にしたcgroupで、子孫のcgroupに所属するプロセスすべてがまとめてkillされることも確認しておきましょう。
図1のようなツリーを作成します。
memory.oom.groupで子孫のプロセスがkillれる動きを確認するためのツリー
そして、test011とtest012 cgroupそれぞれにシェルとsleepコマンドのプロセスを所属させ、親cgroupであるtest01 cgroupでmemory.を1に設定します。
$ sudo mkdir -p /sys/fs/cgroup/test01/test01{1,2}
$ echo "+memory" | sudo tee /sys/fs/cgroup/cgroup.subtree_control
+memory
$ echo "+memory" | sudo tee /sys/fs/cgroup/test01/cgroup.subtree_control
+memory
$ echo 192M | sudo tee /sys/fs/cgroup/test01/memory.max
192M
$ sudo swapoff -a
$ nohup sleep 7200 &
[1] 1117
$ nohup sleep 7200 &
[2] 1118
$ nohup sleep 7200 &
[3] 1119
$ echo 1117 | sudo tee /sys/fs/cgroup/test01/test011/cgroup.procs
1117
$ echo 1118 | sudo tee /sys/fs/cgroup/test01/test012/cgroup.procs
1118
$ echo 1119 | sudo tee /sys/fs/cgroup/test01/test012/cgroup.procs
1119
$ echo $$ | sudo tee /sys/fs/cgroup/test01/test011/cgroup.procs
(シェルをtest011 cgroupに所属させる)
1053
準備ができたところで、test011 cgroupに所属しているシェル上で、これまでと同様にstress-ngコマンドにより制限値を超えるメモリを消費させます。
$ stress-ng --vm 1 --vm-bytes 256M --vm-hang 0 -v (実行していたシェルが kill される)
さきほどの例と同様にシェルがkillされます。その他、起動させていたプロセスを確認すると、test011、test012 cgroupに所属しているプロセスはありませんし、プロセスも存在しません。
$ cat /sys/fs/cgroup/test01/test011/cgroup.procs
(test011 cgroupに所属するプロセスはない)
$ cat /sys/fs/cgroup/test01/test012/cgroup.procs
(test012 cgroupに所属するプロセスはない)
$ ps -p 1117,1118,1119,1053
PID TTY TIME CMD
(いずれのプロセスも存在しない)
dmesgで確認すると、まずstress-ngコマンドのプロセスtest01 cgroup内で設定されているmemory.が理由で、その他のtest01 cgroupの子孫に所属するプロセスがkillされていることがわかります。
[ 937.581321] oom-kill:constraint=CONSTRAINT_MEMCG,nodemask=(null),cpuset=/,mems_allowed=0,oom_memcg=/test01,task_memcg=/test01/test011,task=stress-ng-vm,pid=1176,uid=1000 [ 937.581335] Memory cgroup out of memory: Killed process 1176 (stress-ng-vm) total-vm:525564kB, anon-rss:177760kB, file-rss:512kB, shmem-rss:0kB, UID:1000 pgtables:452kB oom_score_adj:1000 [ 937.581732] Tasks in /test01 are going to be killed due to memory.oom.group set [ 937.581739] Memory cgroup out of memory: Killed process 1117 (sleep) total-vm:5684kB, anon-rss:0kB, file-rss:2048kB, shmem-rss:0kB, UID:1000 pgtables:52kB oom_score_adj:0 [ 937.581966] Memory cgroup out of memory: Killed process 1053 (bash) total-vm:8880kB, anon-rss:1792kB, file-rss:3840kB, shmem-rss:0kB, UID:1000 pgtables:64kB oom_score_adj:0 [ 937.582191] Memory cgroup out of memory: Killed process 1174 (stress-ng) total-vm:263416kB, anon-rss:8960kB, file-rss:6016kB, shmem-rss:16512kB, UID:1000 pgtables:368kB oom_score_adj:0 [ 937.582301] Memory cgroup out of memory: Killed process 1175 (stress-ng-vm) total-vm:263420kB, anon-rss:9052kB, file-rss:1280kB, shmem-rss:0kB, UID:1000 pgtables:120kB oom_score_adj:0 [ 937.582510] Memory cgroup out of memory: Killed process 1176 (stress-ng-vm) total-vm:525564kB, anon-rss:177760kB, file-rss:512kB, shmem-rss:0kB, UID:1000 pgtables:452kB oom_score_adj:1000 [ 937.582730] Memory cgroup out of memory: Killed process 1118 (sleep) total-vm:5684kB, anon-rss:0kB, file-rss:2048kB, shmem-rss:0kB, UID:1000 pgtables:52kB oom_score_adj:0 [ 937.582994] Memory cgroup out of memory: Killed process 1119 (sleep) total-vm:5684kB, anon-rss:0kB, file-rss:2048kB, shmem-rss:0kB, UID:1000 pgtables:56kB oom_score_adj:0
上位のcgroupでmemory.を設定すると、メモリ制限値の超過が起こったcgroupだけでなく、その子孫に所属するプロセスすべてがkillされることが確認できました。
まとめ
今回は4.memory.ファイルについて紹介しました。
このファイルに設定を書き込むことで、cgroup v2のメモリコントローラによるOOM Killerの動きを変えられます。cgroup自身や子孫に所属するプロセスをすべてOOM Killerの対象にすることで、コンテナで実行するワークロードの整合性を保てるようになりました。
次回も引き続きメモリコントローラの機能について紹介する予定です。
