⛑️

cronの構造問題と、なぜsystemd.timerに移行すべきか

に公開

はじめに

私はIT実務未経験です。但し、9年間程自宅でLinuxサーバーを保守運用してきた実績があります。その経験に基づいて分かりやすく簡単なsystemd.timerを使うべき理由を書かせていただきます。

cron障害シナリオ

1つ想定障害シナリオを作ってみました。以下は私の経験ではありませんが、十分起こりうる障害シナリオです。

Linuxサーバーでcronスクリプトに軽微な障害が発生、障害探求のために実務経験者3年の先輩と1年目の新人が対応。
先輩「cronのスクリプトの調子が悪そうだからいったんcronを止めてくれ。」
新人「わかりました!」

$ sudo systemctl disable cron
$ sudo systemctl status cron

新人「あれ止まってないぞ・・・あそっか"stop"か。」

$ sudo systemctl stop cron
$ sudo systemctl status cron

新人「止まりましたー!」
先輩「よし、こんどは起動してくれ。」

$ sudo systemctl start cron
$ sudo systemctl status cron

新人「動きましたー!」

その1週間後に、サーバーを再起動掛けたときにcronはエラーも何も一切出さずに30本近くあるcronで管理しているコマンド・スクリプトは一切動かずバックアップも取られない状態がしばらく続いた・・・。

人間は失敗をする生き物

まず問題として挙げられるのは、この新人君が"disable"と"stop"の違いを理解していなかったことと、"disable"というコマンドを打ったことを報告していなかったことです。"disable"はシステム起動時に自動実行しなくなるコマンドで、"stop"はいますぐそのサービスを停止するコマンドで新人君はこの違いをまだ身体で覚えていなかった。先輩も新人の手元を確認していなかったことも問題ですが、特に障害探求の緊張状態ではそのような細かな配慮がしにくい環境になります。

大前提として人間は必ず失敗をするという生き物です。失敗をするのであれば、その失敗の影響範囲を設計の段階で局所化するという考えが必要になります。

cronの構造問題

このシナリオで最も重要な点は

$ sudo systemctl disable cron

このコマンド一つでcronデーモン配下の30近くある全ての定期実行コマンド・スクリプトが次回サーバー再起動時に自動起動しなくなったという点です。これはsystemd的にも正しい動作なのがやっかいでエラーも一切出ません。再起動時に初めて発動するという遅効性も、問題が発覚したときに原因の探求も困難にさせます。いわば新人君は遅効性の大きな爆弾を悪意なくエラーも出さずに設置してしまったことになります。

30もあるコマンド・スクリプトが同時にこのような事が起きてしまう原因はcronデーモンに全てコマンドとスクリプトが管理されていることが原因です。

今回止まってしまったのはcronデーモンで、巻き添えになったのがそこに乗っていた数多のコマンドとスクリプトです。つまり、影響範囲が広くなってしまった原因はこのcronデーモンという中間管理者がいるがうえに、影響範囲が非常に広くなってしまった構造問題として捉えられます。

影響範囲最小化のためのsystemd.timer

systemd.timerの利便性はログの統一性と実行結果の表示が明瞭であることがありますが、ここで最も重要なのはsystemd.timerの最大の特徴としてcronのような中間管理者が存在しないということです。systemdの上に直接コマンド・スクリプトを置きsystemdが直接管理するという考え方です。この考え方をデーモンレスといいます。

元からcronデーモンという中間管理者が存在しないため、サーバー運用者はそれぞれのコマンドとスクリプトごとにコマンドを実行することを強制します。cronデーモンという中間管理者を止めるという大雑把なコマンドをそもそも出来なくすることで障害の広範囲への波及を防ぐという考え方です。そして、それぞれのコマンドとスクリプトがsystemdの配下に直接入りデーモン化します。

systemd.timerのデメリット

systemd.timerのデメリットとしてしばしば上がるのがファイル数が増える・コマンドが増えて不便というのがあります。しかし、これはsystemdが1つのことをうまくやるというunix哲学を反映した結果であり、これが誤操作による影響範囲を局所化することに繋がっています。たとえば、systemd.timerも書いているスクリプトをテストとして一回だけ実行したいときは

$ systemctl --user start hogehoge.service

と入力すればスクリプトが動き出します。timer自体に触る必要がなくなりタイマーを停止するリスクが減ります。
上記コマンドサンプルはユーザーユニットを使っていますが、そのメリットに関しては拙著である下記の記事を参照していただければと思います。
https://zenn.dev/ryosuke_infra/articles/8f4ac0975b9854

結びに

インターネットにはcronの表面的な問題点とsystemd.timerの利便性を書いている記事はたくさんありますが、今回の問題点を述べている記事を一切読んだことがなかったので記事にさせていただきました。長年cronを使っていて今もずっと愛用していて現場でも使っている方も多いと思います。ですが、cronとsystemd.timerを比べるとこのような構造的な違いが有るということが今回分かっていただけたかと思います。

私自身はLinuxを触りたての段階でsystemd.timerにすぐに移行しました。移行する理由がうまく言語化できませんでしたが、長年触ってようやくこの構造の違いに気が付きました。皆様の学びになれば幸いです。

Discussion