Skip to content

systemdユニットの順序を制御する

あまり理解していないのでまとめる。

弱い依存関係を示す。a.service の実行に b.service が必要なとき Wants=b.service と記述する。ユニットファイルに記述する他に、a.service へのシンボリックリンクを b.service.wants/ ディレクトリ以下に配置することでも依存関係を注入できる。

複数の前提が必要なら Wants= を複数記述するかスペース区切りで指定する。また、Wants= で指定した依存先ユニットの起動に失敗しても、要求したユニット自体は起動する。依存関係はサービスの起動や停止の順序に影響しない。After= または Before= を設定しない限り依存元と依存先は同時に起動する。

強い依存関係を示す。Wants= に似て、ユニットファイルに記述する他に a.service へのシンボリックリンクを b.service.requires/ ディレクトリ以下に配置することでも依存関係を注入できる。

Requires=After= を同時に指定している場合、依存先ユニットの起動に失敗すると要求したユニットも起動しない。ただし ConditionXxx= による条件チェックに失敗した場合、依存先ユニットが正常に停止した場合などは要求したユニットに影響しない。

Requires= と似ているが、依存先ユニットが実行中でなければすぐに失敗する。

Requires= よりも強い依存を意味する。Requires= の挙動に加えて、依存ユニットが突然非アクティブ状態になった場合に、要求したユニットも停止する。

After= と同時に使うと、上記に加えて ConditionXxx= の条件チェックに失敗した場合でも要求したユニットを停止する。

Requires= と同様の依存関係となるが、影響はユニットの停止と再起動に限定される。依存先→依存元の単方向な依存となる。

Wants= に似ているが、これを設定したユニットが動作している間、Wants= にリストされたユニットも維持される。Wants= は起動時だけに影響するがだが、これは要求したユニットが実行中の間は永続的に影響する。

ユニットに Before=b.service と記述された場合、このユニットが起動完了するまで b.service の起動を遅延する。複数のユニットより先に実行したい条件がある場合はスペース区切りで記述するらしい。マニュアルでは Before= を複数書くことには言及されてなかった。

ユニットを停止する際には順序が逆になる。a.service の後に b.service を起動した場合、停止するときは b.service の後に a.service を停止する。

Before= の順序が逆になる。

Wants=Requires= は明らかに違うので分かりやすいが、それ以外はどれも Requires= に似ていてとても混乱するので調べた。前提として以下のユニットを考える。

app.servicedb.service に依存をする。

[Unit]
Description=app
Wants=db.service
[Service]
Type=simple
ExecStart=/bin/sleep inf

db.service は単にスリープするだけのユニット。

[Unit]
Description=db
[Service]
Type=simple
ExecStart=/bin/sleep inf

Difference between PartOf and BindsTo in a systemd unitでは起動と停止の挙動を調べていたので、それに習ってここでも起動と停止を調べる。

ディレクティブappを起動(After=なし)appを起動(After=あり)dbを起動
Wants=db も同時に起動db, app の順に起動app は影響しない
Requires=db も同時に起動db, app の順に起動app は影響しない
Requisite=app だけ起動app は起動に失敗app は影響しない
BindsTo=db も同時に起動db, app の順に起動app は影響しない
PartOf=app だけ起動app だけ起動app は影響しない
Upholds=db も同時に起動db, app の順に起動app は影響しない

Requires= + BindsTo= + PartOf= のように複数記述した場合、制約の強いものが反映される。

この結果から、After= 未設定の場合は Requisite=PartOf= は依存先が起動していなくても app を実行中にする。それ以外は db を同時に起動する。ここでは After= を設定していないので、Requisite= であってもユニットは実行中となる。この場面では、db より app のプロセスIDが小さい値となっているので、順序としては app, db の順に起動していると思う。

上記の動作は After= を設定することで明確に変化する。After= を設定すると、実行順序が保証されるのと同時に Requisite= では失敗に変わる。

db 単体で起動した場合は app に影響を与えないが、db の起動と同時に app も起動したい場合はどうするのか。試していないけど Install セクションで RequiredBy=db.service または WantedBy=db.service と記述するのではないか。そうすると上記の表で書いた条件が逆になるので、db 起動により app も起動できると思う。

依存先ユニットの起動に失敗したときの結果

Section titled “依存先ユニットの起動に失敗したときの結果”

db をすぐに失敗するようなユニットにしておく。

[Unit]
Description=db
[Service]
Type=simple
ExecStart=/bin/sleep inf
ExecStart=/bin/false

上記ユニットの Wants=Requires= などに変更して、依存サービスの起動結果でどのように振る舞うかを確認する。最初はどちらも起動していない状態とする。

ディレクティブdbの起動に失敗
Wants=app だけ起動する
Requires=app だけ起動するが数分後に app を停止する(1)
Requisite=app もすぐに失敗する
BindsTo=app もすぐに失敗する
PartOf=app だけ起動する
Upholds=app を起動したまま db もリトライし続ける(2)

(1)で数分後というのは、db.service が失敗してすぐに app.service も停止する訳ではない。手元の環境ではしばらく app.service だけで実行し続けて、だいたい2分後に app.service も停止した。このときは db.service のリトライをしない。試していないが Restart= などの有無によってリトライの有無や2分という時間が変わるかもしれない。

どうしてすぐに失敗しないのかについては、systemdのユニットにおけるrequiresが機能してないように見えますによると、この挙動は Type=simple だから「起動と同時に成功した」扱いとなっているらしい。systemd.unit(5)を読む。

(2)では、Upholds=db.service をリトライし続けている。10分待ってみたけどリトライが止まらないので、それ以上は確認していない。

停止するときの動作は次のようになる。After= の有無によって停止時の動作は変わらない。

ディレクティブappの正常/異常終了dbの正常終了dbの異常終了
Wants=db は継続するapp は継続するapp は継続する
Requires=db は継続するapp も停止するapp は継続する
Requisite=db は継続するapp も停止するapp は継続する
BindsTo=db は継続するapp も停止するapp も停止する
PartOf=db は継続するapp も停止するapp は継続する
Upholds=db は継続するdb が再起動されるdb が再起動される

異常終了とはsystemdにおける異常終了の定義に書いた。

上記以外にも RequiresMountsFor=WantsMountsFor= など順序を制御するオプションがいっぱいある。

ここには ExecStart= の前後で実行される ExecStartPre= または ExecStartPost= や、停止前後で実行される ExecStopPost= などが用意されている。詳細はSystemdにおけるservice unitの起動フロー入門が詳しいと思う。

Install セクションは systemctl enable または systemctl disable に作用する。

WantedBy= で指定したユニットが、これを記述するユニットに Wants= で依存するようになる。例えば acme.serviceWantedBy=graphical-session.target を記述した場合、acme.service を有効化すると graphical-session.target.wants/ ディレクトリに acme.service へのシンボリックリンクが作成されて、結果的に graphical-session.targetWants=acme.service を記述した状態と同等になる。

[Unit]
PartOf=graphical-session.target
[Install]
WantedBy=graphical-session.target

graphical-session.targetacme.service に依存するの依存関係として逆じゃないかと思ったが、末尾が .target となっているユニットは「あるべき状態」を定義するものとなる。なので「graphical-session.target としてあるべき状態は acme.service を含む」を意味していて、それに満たない場合は必要なユニットを起動させる動作をする。

ここで、acme.service のユニットを次のように記述した場合を考える。

[Unit]
Wants=graphical-session.target
[Install]
WantedBy=graphical-session.target

上記の例は acme.sservicegraphical-session.target に依存して、かつ WantedBy= により graphical-session.targetWants=acme.service 相当となる。循環参照しているようにみえるが、上でみたように systemd はユニットを並列に起動するので、Wants= で要求するからといって起動の順序が保たれる訳ではない。順序が重要な場合は前述した Before=After= で順序を保証する必要があるけれど、Install セクションからは指定できない。

WantedBy= と似ているが Requires= 相当となる。上記の例でいえば、acme.service の起動に失敗した場合に graphical-session.target も起動しなくなるので、おそらく滅多に使わないと思う。

BoundBy=Unit セクションだけで、Install セクションには無い。