cron だって oom-killer に殺される
最近マイクラにドハマリして、1日12時間とかプレイしてしまうし、blog もサボってたので、マイクラを消して封印した。
前提条件
- 自宅 NAS の構成: ODROID HC2 + Armbian 5.90 stable (Ubuntu Bionic with Armbian Linux 4.14.133-odroidxu4)
- sysctl で
vm.oom_kill_allocating_task = 1
している(アホみたいな量のメモリを要求してきたプロセスがいたら、他のプロセスではなくメモリを要求してきたプロセスに死んでもらう)
何があった
自宅 NAS から毎朝来るはずの logwatch のメールが来なかった。これまで原因不明のハングアップに何度か遭遇してきたので、「また落ちてんのか~」と思った。が、普通に SSH でログインできるし、samba も生きている。しかし node.js のプロセスが1つ Killed されていたので、oom-killer の仕業を疑った。
メールが来ないということは、postfix が死んだか?と思ったが大丈夫、メールもちゃんと飛んでくる。……ということは、logwatch が実行されていない?
logwatch は cron.daily によって実行されているので、まさかと思いながら systemctl status cron
してみると
root@odroidhc2:/var/log# systemctl status cron ● cron.service - Regular background program processing daemon Loaded: loaded (/lib/systemd/system/cron.service; enabled; vendor preset: enabled) Active: failed (Result: signal) since Sat 2019-11-16 18:31:01 JST; 1 day 18h ago Docs: man:cron(8) Process: 712 ExecStart=/usr/sbin/cron -f $EXTRA_OPTS (code=killed, signal=KILL) Main PID: 712 (code=killed, signal=KILL) CGroup: /system.slice/cron.service (中略) Nov 16 18:31:01 odroidhc2 systemd[1]: cron.service: Main process exited, code=killed, status=9/KILL Nov 16 18:31:01 odroidhc2 systemd[1]: cron.service: Failed with result 'signal'.
ここで /var/log/syslog
を確認すると
Nov 16 18:31:01 localhost kernel: [107505.152146] cron invoked oom-killer: gfp_mask=0x14000c0(GFP_KERNEL), nodemask=(null), order=2, oom_sc ore_adj=0 (中略) Nov 16 18:31:01 localhost kernel: [107505.152526] [ pid ] uid tgid total_vm rss nr_ptes nr_pmds swapents oom_score_adj name Nov 16 18:31:01 localhost kernel: [107505.152586] [ 712] 0 712 1700 514 7 0 0 0 cron Nov 16 18:31:01 localhost kernel: [107505.152869] [14739] 0 14739 2049 516 7 0 0 0 cron (中略) Nov 16 18:31:01 localhost kernel: [107505.152887] Out of memory (oom_kill_allocating_task): Kill process 712 (cron) score 0 or sacrifice child Nov 16 18:31:01 localhost kernel: [107505.152908] Killed process 14739 (cron) total-vm:8196kB, anon-rss:380kB, file-rss:1684kB, shmem-rss:0kB
マジか…… cron ぐらい重要なプロセスはデフォルトで oom_score_adj = -1000 になってると思ってたぜ……
対策
systemd には、Unit の status の変化をトリガーに、他の Unit を起動したり、poweroff
/ reboot
を叩く機能がついている。これを利用して、万が一 cron が落ちてしまった場合に NAS 自体を再起動させてしまおう。
やり方は、systemctl edit cron
するとエディタが開くので
[Unit] FailureAction=reboot
と書いておく(大文字小文字の区別に注意)。こうすると、既存の conf ファイルを書き換えることなく、Unit に設定を追加したり上書きすることができる(ファイルの実体は/etc/systemd/system/cron.service.d/override.conf
にある)。
Drop-In: /etc/systemd/system/cron.service.d └override.conf
もちろん oom_score_adj を弄るのも有効な手段。
[Service] OOMScoreAdjust=-1000
ただし注意点があって、cron によって起動される全ての子プロセスも同様に oom_score_adj = -1000 が設定されてしまう。したがって、crontab でメモリ食いのプロセスを起動させてしまうとカオスを招くかもしれない。なので筆者はこれを使わなかった。
ついでに同じ設定を postfix にもしておいた。何かあった時にメールが来ないのも結構困るからね。