hunamizawa’s blog

無い物は作りたい人のメモ帳

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 にもしておいた。何かあった時にメールが来ないのも結構困るからね。

参考記事