systemd timer にちゃんと向き合ってみる

記事タイトルとURLをコピーする

みなさん、こんにちは。今日は 3月9日 ミクの日ですね。
こういった日は、Twitter のタイムラインがミクで溢れがちになるのがいいですよね(私個人に依存するんでしょうが…)そんなこんなでテクサポの市野です。

以前のブログで、所属する課の名前が捜査一課みたいになって響きやニュアンスが気に入っていると記載したのですが、また課の名前が変わってしまい「AWSサポート課」の市野となりました。(名称的にその筋から怒られないか、ちょっと心配しています。)

今日は、Amazon Linux 2023 でデフォルトとなったジョブ管理の仕組みの systemd timer について向き合ってみる機会があったので、ちょっとまとめました。

クリックで目次が表示されます。

本エントリで書かないこと

  • スケジュールタイマー実行後のメール送信について
    • cron と異なり MAILTO に相当する機能の用意がないため、自身で実装する必要があるとドキュメントレベルで確認できています。
    • 今回、実行環境を整えることができなかったため、検証は省いていますが、概ね以下のアプローチで実装する必要があると記載がありました。
      1. 電子メールを送信するスクリプトを作成する
      2. この電子メール送信スクリプトを実行する systemd サービスファイルを作成する
      3. この電子メールサービスファイルをテストする
      4. タイマーで制御するサービスから、OnFailure を使用して、<2> で作成した電子メールサービスファイルを呼び出すよう連携させる
  • cron との違いや比較、メリット、デメリット
  • Amazon Linux 2023 への cron(cronie)のインストール方法や cron の場合の設定方法
  • systemd 自体について
  • systemd timer ひいては systemd が UNIX 哲学にそぐわないのではないか?という思想の問題

そもそも systemd timer とは

従来の SysVinit に変わるシステムのシステムおよびサービスマネージャとして Linux OS 用に設計された systemd では、宣言型の構文によって定義される設定ファイル=ユニットファイルを用いてシステムが操作および管理方法を認識しているリソースを定義します。

このユニットファイルの型としては拡張子によって区別されるいくつかの分類があり、代表的なユニットファイルにはサービスの開始または停止方法、リロード方法を記述した .service がありますが、.timer 拡張子を持つユニットファイルでは、systemd で管理するタイマーを定義することができるユニットとして用意されています。
このユニットファイルによってタイマー定義された設定値に達したときに、同じ名称を持つ .service ユニットファイルを起動する仕組みで動作し、cron ジョブに似たスケジュール機能として systemd に備わっているものとなります。

なぜいま systemd timer か?

あたらしく AWS から提供されている Amazon Linux 2023 では従来の Amazon Linux 2 といくつかの差異がありますが、その一つとして cron(cronie)が標準インストールされなくなりました。

現時点での Amazon Linux 2023 では引き続き任意に cronie をインストールすることが可能となっていますが、systemd に同様の機能が備わっていることで sytemd timer への移行を推奨されている背景があります。

docs.aws.amazon.com

なお、Amazon Linux 2023 と Amazon Linux 2 の差異については、弊社の新谷(にいや)が以下でまとめていますので、こちらもご参照ください。

blog.serverworks.co.jp

そもそも、AWS では EventBridge などでスケジュール実行できる機能もあるため、OS レイヤーでジョブをタイマー実行させなければならないケースも減らせそうではありますが、個人的に管理しているサーバで cron を実行したかったものの、Amazon Linux 2023 で動作させていたので、この際だから systemd timer を使ってみよう、というのが今回向き合うモチベーションとなっています。

systemd timer での設定概要

  • 前提として、起動したいサービス・ジョブ定義した .service 拡張子を持つユニットファイルとして作成しておく必要がある(例:hello.service
  • 起動したい .service ユニットファイルと同じベースファイル名となるように .timer 拡張子を持つユニットファイルを作成する(例:hello.timer
    • 要するに拡張子だけが異なり hello の部分は揃えておいた二つのファイルを用意する必要がある。
  • .timer 拡張子を持つユニットファイルに定義したいタイマー(トリガー)を記載する

やってみる

動作させるジョブ

今回は単純なシェルスクリプトを実行させてみます

前提:実行環境について

[ssm-user@ip-10-0-1-13 ~]$ cat /etc/os-release
NAME="Amazon Linux"
VERSION="2023"
ID="amzn"
ID_LIKE="fedora"
VERSION_ID="2023"
PLATFORM_ID="platform:al2023"
PRETTY_NAME="Amazon Linux 2023.6.20250303"
ANSI_COLOR="0;33"
CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2023"
HOME_URL="https://aws.amazon.com/linux/amazon-linux-2023/"
DOCUMENTATION_URL="https://docs.aws.amazon.com/linux/"
SUPPORT_URL="https://aws.amazon.com/premiumsupport/"
BUG_REPORT_URL="https://github.com/amazonlinux/amazon-linux-2023"
VENDOR_NAME="AWS"
VENDOR_URL="https://aws.amazon.com/"
SUPPORT_END="2029-06-30"

単純なシェルスクリプト:/usr/local/bin/hello.sh

#!/bin/bash
echo "$(date) Hello!" > /home/ssm-user/hello.txt

.service ファイル:/etc/systemd/system/hello.service

[Unit]
Description="Script for Hello"

[Service]
ExecStart=/usr/local/bin/hello.sh

.timer ファイル:/etc/systemd/system/hello.timer

[Unit]
Description="Timer config for hello.service"

[Timer]
OnCalendar=*-*-* 06:00:00
Unit=hello.service

[Install]
WantedBy=multi-user.target

起動設定

hello.timer の起動
sudo systemctl start hello.timer
hello.timer の自動起動の有効化
sudo systemctl enable hello.timer
(オプション)上記 2 手順を以下で一つにまとめることも可能です
sudo systemctl enable hello.timer --now

動作確認

アウトプット用ファイルの中身の確認
[ssm-user@ip-10-0-1-13 ~]$ date && cat hello.txt && date
Sun Mar  9 05:56:24 UTC 2025
Sun Mar  9 05:56:24 UTC 2025

hello.txt の中身は何も書かれていない状態です

タイマー設定一覧の確認

systemctl list-timers コマンドでアクティブとなっているタイマー設定を一覧で確認できます。
UNIT hello.timer については 2行目の出力で NEXT のフィールドにあるように次回 Sun 2025-03-09 06:00:00 UTC に実行されると記載があります。

また、出力にもあるとおり systemctl list-timers コマンドでは -all オプションをつけることでアクティブでないタイマー設定も一覧に含めることが可能です。

[ssm-user@ip-10-0-1-13 ~]$ sudo systemctl list-timers
NEXT                        LEFT          LAST                        PASSED       UNIT                >
Sun 2025-03-09 05:58:39 UTC 1min 0s left  Sun 2025-03-09 05:57:39 UTC 332ms ago    refresh-policy-route>
Sun 2025-03-09 06:00:00 UTC 2min 20s left -                           -            hello.timer         >
Sun 2025-03-09 06:00:00 UTC 2min 20s left Sun 2025-03-09 05:50:47 UTC 6min ago     sysstat-collect.time>
Sun 2025-03-09 16:20:19 UTC 10h left      -                           -            update-motd.timer   >
Sun 2025-03-09 23:27:56 UTC 17h left      Sat 2025-03-08 23:27:56 UTC 6h ago       systemd-tmpfiles-cle>
Mon 2025-03-10 00:00:00 UTC 18h left      Sun 2025-03-09 00:00:47 UTC 5h 56min ago logrotate.timer     >
Mon 2025-03-10 00:00:00 UTC 18h left      -                           -            fstrim.timer        >
Mon 2025-03-10 00:07:00 UTC 18h left      Sun 2025-03-09 00:07:47 UTC 5h 49min ago sysstat-summary.time>

8 timers listed.
Pass --all to see loaded but inactive timers, too.
実行確認

少し待機して Sun 2025-03-09 06:00:00 UTC 以降に、アウトプット用ファイルの中身を再確認してみます。

[ssm-user@ip-10-0-1-13 ~]$ date && cat hello.txt && date
Sun Mar  9 06:01:53 UTC 2025
Sun Mar  9 06:00:33 UTC 2025 Hello!
Sun Mar  9 06:01:53 UTC 2025

hello.timer ユニットに記載した通り、06:00 UTC に実行されて「Hello!」の記述がシェルスクリプトによって行われています。

タイマー設定一覧の再確認

再度確認してみると hello.timer ユニットの NEXT は Mon 2025-03-10 06:00:00 UTC となっており、こちらも想定通り翌日の実行予定が組まれていることが確認できました。

[ssm-user@ip-10-0-1-13 ~]$ sudo systemctl list-timers
NEXT                        LEFT      LAST                        PASSED       UNIT                    >
Sun 2025-03-09 06:03:19 UTC 48s left  Sun 2025-03-09 06:02:18 UTC 13s ago      refresh-policy-routes@en>
Sun 2025-03-09 06:10:00 UTC 7min left Sun 2025-03-09 06:00:33 UTC 1min 58s ago sysstat-collect.timer   >
Sun 2025-03-09 16:20:19 UTC 10h left  -                           -            update-motd.timer       >
Sun 2025-03-09 23:27:56 UTC 17h left  Sat 2025-03-08 23:27:56 UTC 6h ago       systemd-tmpfiles-clean.t>
Mon 2025-03-10 00:00:00 UTC 17h left  Sun 2025-03-09 00:00:47 UTC 6h ago       logrotate.timer         >
Mon 2025-03-10 00:00:00 UTC 17h left  -                           -            fstrim.timer            >
Mon 2025-03-10 00:07:00 UTC 18h left  Sun 2025-03-09 00:07:47 UTC 5h 54min ago sysstat-summary.timer   >
Mon 2025-03-10 06:00:00 UTC 23h left  Sun 2025-03-09 06:00:33 UTC 1min 58s ago hello.timer             >

8 timers listed.
Pass --all to see loaded but inactive timers, too.
(余談).timer ユニットファイル自体の更新を行った場合

実行日時などユニットファイル自体の変更を行った場合は systemctl daemon-reload を要求されました。

深掘ってみる

[Timer] ディレクティブで設定できるタイマー設定について

参考文献として参照していた SUSE のドキュメントでは、以下、3つのタイマータイプについて解説がありました。(カッコ内は日本語版での訳)

  • Real-time timer(リアルタイムタイマ)
    • リアルタイム(カレンダー)に基づいて、現実時間の日時ベースで定義できるトリガー設定となります。
  • Monotonic timers(単調タイマ)
    • システムの起動やユニットがアクティベーションされたタイミングなど、OS 内部で起こったイベントを起点としたトリガー設定となります。
  • Transient timers(過渡タイマ)
    • 実行時点のセッションのみで有効なトリガー設定となります。「過渡」と訳すよりも「一時的な」の方がしっくりくるかもしれませんね。

Real-time timer での設定方法

OnCalendar オプションを用いて以下の要領で記載する、とあり、順に 曜日 → 日付 → 時刻 の要領で記載する形となります。

OnCalendar=DayofWeek Year-Month-Day Hour:Minute:Second
引数 取りうる値
DayofWeek Sun、Mon、Tue、Wed、Thu、Fri、Sat
記載を省くことで曜日判定をしないことも可能となります。
Year-Month-Day yyyy-mm-dd の形式で日付を記載します。
ワイルドカード(*)で置き換えることも可能となっています。
日付の記載方法として直感的でわかりやすいですね。
Hour:Minute:Second HH:MM:DD の形式で時刻を記載でき、日付と同様にワイルドカードを利用可能です。
こちらも時刻表記として慣れている形式なので直感的でわかりやすい印象です。

また、.(ドット)を二つ続けて連続する範囲(例:Mon..Fri と書くことで、月曜から金曜)、,(カンマ)で区切ることで複数の値を列記可能です。(例:Mon,Wed,Fri と書くことで 月・水・金 となります。)

Monotonic timers での設定方法

以下のオプションを取ることができ、何らかのイベントからの経過時間を定義し、設置値に達した際に .timer ユニットに呼応するサービスの実行を行うことが可能です。

単位として usecmsecsecondsminuteshoursdaysweeksmonthsyears を取ることができ、半角スペースで区切って続けて記載することが可能です。(例:5minutes 20seconds と記載することで「5分20秒後」を定義することが可能です。)

オプション 解説
OnActiveSec ユニット起動後の経過時間
OnBootSec システム起動後の経過時間
OnStartupSec サービス マネージャーが起動してからの経過時間
OnActiveSec との違いとしてユーザー ログイン時にサービス マネージャーが起動されるユーザー サービスに使用することが想定されているようです
OnUnitActiveSec 対象とするサービスが最後にアクティブ化されてからの経過時間
OnUnitInactiveSec 対象とするサービスが最後に非アクティブ化されてからの経過時間

Transient timers での設定方法

systemd-run コマンドにトリガーのオプションを付与し、直接実行するユニットや実行ファイルを直接指定して実行することで、現在のセッションでのみ有効な実行トリガーを設定することが可能です。

# 記載方法
systemd-run <トリガーのオプション> <実行するユニットやファイルパス>

# 実行例
sudo systemd-run --on-active="2hours" --unit="helloworld.service"
sudo systemd-run --on-active="2hours" /usr/local/bin/helloworld.sh

またトリガーのオプションには、前述までの Real-time timer での OnCalendar や、Monotonic timers での OnBootSec 以外の経過時間を表す以下のオプションを設定可能です。
OnBootSec が使えないのは、該当セッションでは当然起動状態で、かつ、開始点を取れないからかと推察します。)

  • --on-activeMonotonic timers での OnActiveSec に相当します。
  • --on-startupMonotonic timers での OnStartupSec に相当します。
  • --on-unit-activeMonotonic timers での OnUnitActiveSec に相当します。
  • --on-unit-inactiveMonotonic timers での OnUnitInactiveSec に相当します。
  • --on-calendarReal-time timer での OnCalendar に相当します。

OnCalendar オプションの記法チェック

systemd-analyze calendar コマンドに、設定しようとしている OnCalendar オプション文字列を投入することで、記法の誤りのチェックと実行日時の確認をすることが可能です。

実行例
[ssm-user@ip-10-0-1-13 bin]$ date
Sun Mar  9 06:57:26 UTC 2025

[ssm-user@ip-10-0-1-13 bin]$ sudo systemd-analyze calendar "*-*-* 06:00:00"
Normalized form: *-*-* 06:00:00
    Next elapse: Mon 2025-03-10 06:00:00 UTC
       From now: 23h left

上記のように判定され、記法として問題なく、次回は Mon 2025-03-10 06:00:00 UTC に実行されることが確認できます。

(余談2)実行例

当初、曜日も含めて全曜日の 06:00:00 としたかったので、OnCalendar=Sun..Sat *-*-* 06:00:00 のように記載していましたが、うまく動作しませんでした。
.. で範囲を指定できるはずでしょ?と思ってなんでだろう?と思っていました)

そこで、systemd-analyze calendar コマンドを実行してみると以下のように判定され引数が正しくないと判断されました。
公式ドキュメントでは見つけられなかったのですが、英語圏では月曜日始まりなのかもしれないですね(次回調べてみたいと思います)

[ssm-user@ip-10-0-1-13 bin]$ sudo systemd-analyze calendar "Sun..Sat *-*-* 06:00:00"
Failed to parse calendar specification 'Sun..Sat *-*-* 06:00:00': Invalid argument

参考文献

おわりに

cron と異なりジョブスケジュールの定義に <basename>.service<basename>.timer の二つのファイルを用意しなければならないのは少々面倒だと思う反面、systemctl list-timers コマンドでタイマーの一覧表示ができることや、systemd-analyze calendar コマンドでリアルタイムタイマー用のカレンダー形式の評価を行えることは有用で、cron 時代よりもわかりやすく設定ミスが起こりにくいのではないかと感じました。

運用に慣れノウハウが蓄積されている、いわゆる枯れた技術の安定性も十分理解できますし同感ではありますが、他方で、次期 Amazon Linux でも引き続き cronie のインストールが可能かも未知数ですので、今は cronie を適宜インストールできるから、と先延ばしにせず、少しずつ導入検討を進めていくのも良いのではないかと思われました。

本エントリがどなたかのお役に立てば幸いです。

ではまた。

市野 和明 (記事一覧)

マネージドサービス部・AWS サポート課

お客様から寄せられたご質問や技術検証を通じて得られた気づきを投稿していきます。

情シスだった前職までの経験で、UI がコロコロ変わる AWS においては GUI で手順を残していると画面構成が変わってしまって後々まごつくことが多かった経験から、極力変わりにくい AWS CLI での記事が多めです。

X(Twitter):@kazzpapa3(AWS Community Builder)