⏲️

cron の代わりに systemd Timers で定期処理を実現する

2025/02/18に公開

Linux には cron しか定期実行システムがないと思っているあなた、実は代打が居ます。ただし systemd 環境限定なのでそこはご注意。

systemd-timer is 何

systemd 上で動く cron みたいなやつ。それまでと言ったらそれまでだけど、腐っても systemd なので宣言的に色々書けるため cron と比べたら見通しが良い(かもしれない)。

cron と比べたときの利点

  • ログを journalctl に載せられる。cron だとログをどっかに吐き出す必要があったりして不便。
  • 失敗時のリカバリーがしやすい。cron だとリトライ機構自体がないので、スクリプト側でリトライの対応を仕込むしかなくてちょっと大変。

cron と比べたときの欠点

  • 非 systemd 環境では使いようがない。昔の WSL とか Alpine Linux な環境では無理。
  • cron より記述が面倒くさい。柔軟さと手軽さは今回の場合はトレードオフになる。

使い方

アーキテクチャの解説

そもそも systemd にはユニットと言う概念がある。ユニットは systemd 上である種のサービスやデバイス、ソケットなどなどを表現したファイルのことである。systemd Timers では主に以下のユニットが関わってくる。

  • .service ユニット: サービス(アプリやスクリプト)を systemd 的に表現する。
  • .timer ユニット: 特定のユニットを定期実行するという処理を systemd 的に表現する。

つまり、systemd 的に言うと .service.timer ユニットを組み合わせれば、特定のサービスを systemd 上で定期実行することができるようになる。

サービスを systemd に登録する

今回は「ハローワールド」サービスを systemd に登録する。このサービスは単純に Kaixo, mundua! という文字列を echo するだけの単純なものである。

sudo vim /etc/systemd/system/hello_bsq.service
[Unit]
Description=HelloWorld in Basque Service

[Service]
Type=oneshot
ExecStart=/bin/bash -c 'echo "Kaixo, mundua!"'

ちなみに Kaixo, mundua! はバスク語であり「Hello, world!」程度の意味である。バスク語が好きなので遊び心でそうしている。

サービスユニットの説明
  • Type=oneshot → サービスで実行するコマンドがデーモンとして動作せず、コマンドが完了次第サービスが終了する物を指す。他には simple や forking がある(説明は省略)。
  • ExecStart=... → サービスとして実行するコマンドないしアプリを指定する。今回は echo コマンド。

サービスに対応するタイマーを systemd に登録する

タイマーの実行タイミングは OnCalendar で指定できる。Y-M-d H:M:S みたいな書式で起動タイミングを指定する。ちなみに OnCalendar は複数指定できる。

sudo vim /etc/systemd/system/hello_bsq.timer
[Unit]
Description=Runs hello_bsq.service every minute

[Timer]
OnCalendar=*-*-* *:*:00
Persistent=false

[Install]
WantedBy=timers.target
タイマーユニットの説明
  • OnCalendar=... → 実行タイミングを指定する。
  • Persistent=... → システムがシャットダウンされている等して指定した時間にユニットを実行できなかった場合、次回起動時に即座に実行予定分を実行する場合は true にする。
  • WantedBy=timers.target → 当該ユニットをタイマーターゲットから呼び出される = 自動実行されるようにする。何もターゲットを指定しないと、当該ユニットを起動する要因が存在しなくなるので完全に手動でユニットを実行しなければならなくなる。
OnCalendar について

時間の設定は以下の2タイプがある。

  • モノトニックタイマー: タイマーの開始タイミングが不定。例えば「システム起動10分後」みたいに設定ができるが、システムの起動時刻なんて起動するまでわかるわけがなく、その意味で不定。
  • リアルタイムタイマー: タイマーの開始タイミングが一定。例えば「毎朝10時」とするとシステムさえ起動していれば必ずその時間にユニットが動くので、その意味で一定。

詳細は ArchWiki の「systemd/タイマー」を見るのが一番。

サービスを systemd に認識させる

おなじみのコマンドを実行する。

sudo systemctl daemon-reload
sudo systemctl enable --now hello_bsq.timer

タイマーが動いているか確認する

systemctl list-timers で systemd に登録されているタイマーを一覧に出せる。

$ systemctl list-timers --all
NEXT                            LEFT LAST                              PASSED UNIT                         ACTIVATES
Mon 2025-02-17 23:14:00 JST      23s Mon 2025-02-17 23:13:00 JST      34s ago hello_bsq.timer              hello_bsq.service

NEXT/LEFT 列が次の実行予定日時(実行されるまでの残り時間)、LAST/PASSED が最後の実行日時(最後の実行からの経過時間)である。UNIT 列がタイマーユニット、ACTIVATES がタイマーユニットに対応するサービスユニットを指す。

実行ログを取る

冒頭の通り、ユニットの出力は journalctl から取れる。

journalctl -u hello_bsq
Feb 17 23:22:01 DESKTOP-QPFUMJI systemd[1]: Starting hello_bsq.service - HelloWorld in Basque Service...
Feb 17 23:22:01 DESKTOP-QPFUMJI bash[5047]: Kaixo, mundua!
Feb 17 23:22:01 DESKTOP-QPFUMJI systemd[1]: hello_bsq.service: Deactivated successfully.
Feb 17 23:22:01 DESKTOP-QPFUMJI systemd[1]: Finished hello_bsq.service - HelloWorld in Basque Service.
Feb 17 23:23:01 DESKTOP-QPFUMJI systemd[1]: Starting hello_bsq.service - HelloWorld in Basque Service...

終わりに

crontab -r があまりにも怖すぎるので、それを回避するためだけに systemd Timers を使う意義はあるかもしれない。

Discussion