systemdユニットの順序を制御する
あまり理解していないのでまとめる。
Unitセクション
Section titled “Unitセクション”Wants=
Section titled “Wants=”弱い依存関係を示す。a.service の実行に b.service が必要なとき Wants=b.service と記述する。ユニットファイルに記述する他に、a.service へのシンボリックリンクを b.service.wants/ ディレクトリ以下に配置することでも依存関係を注入できる。
複数の前提が必要なら Wants= を複数記述するかスペース区切りで指定する。また、Wants= で指定した依存先ユニットの起動に失敗しても、要求したユニット自体は起動する。依存関係はサービスの起動や停止の順序に影響しない。After= または Before= を設定しない限り依存元と依存先は同時に起動する。
Requires=
Section titled “Requires=”強い依存関係を示す。Wants= に似て、ユニットファイルに記述する他に a.service へのシンボリックリンクを b.service.requires/ ディレクトリ以下に配置することでも依存関係を注入できる。
Requires= と After= を同時に指定している場合、依存先ユニットの起動に失敗すると要求したユニットも起動しない。ただし ConditionXxx= による条件チェックに失敗した場合、依存先ユニットが正常に停止した場合などは要求したユニットに影響しない。
Requisite=
Section titled “Requisite=”Requires= と似ているが、依存先ユニットが実行中でなければすぐに失敗する。
BindsTo=
Section titled “BindsTo=”Requires= よりも強い依存を意味する。Requires= の挙動に加えて、依存ユニットが突然非アクティブ状態になった場合に、要求したユニットも停止する。
After= と同時に使うと、上記に加えて ConditionXxx= の条件チェックに失敗した場合でも要求したユニットを停止する。
PartOf=
Section titled “PartOf=”Requires= と同様の依存関係となるが、影響はユニットの停止と再起動に限定される。依存先→依存元の単方向な依存となる。
Upholds=
Section titled “Upholds=”Wants= に似ているが、これを設定したユニットが動作している間、Wants= にリストされたユニットも維持される。Wants= は起動時だけに影響するがだが、これは要求したユニットが実行中の間は永続的に影響する。
Before=
Section titled “Before=”ユニットに Before=b.service と記述された場合、このユニットが起動完了するまで b.service の起動を遅延する。複数のユニットより先に実行したい条件がある場合はスペース区切りで記述するらしい。マニュアルでは Before= を複数書くことには言及されてなかった。
ユニットを停止する際には順序が逆になる。a.service の後に b.service を起動した場合、停止するときは b.service の後に a.service を停止する。
After=
Section titled “After=”Before= の順序が逆になる。
実際の動作検証
Section titled “実際の動作検証”Wants= と Requires= は明らかに違うので分かりやすいが、それ以外はどれも Requires= に似ていてとても混乱するので調べた。前提として以下のユニットを考える。
app.service は db.service に依存をする。
[Unit]Description=appWants=db.service
[Service]Type=simpleExecStart=/bin/sleep infdb.service は単にスリープするだけのユニット。
[Unit]Description=db
[Service]Type=simpleExecStart=/bin/sleep infDifference between PartOf and BindsTo in a systemd unitでは起動と停止の挙動を調べていたので、それに習ってここでも起動と停止を調べる。
ユニットを起動した結果
Section titled “ユニットを起動した結果”| ディレクティブ | 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=simpleExecStart=/bin/sleep infExecStart=/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分待ってみたけどリトライが止まらないので、それ以上は確認していない。
ユニットを停止する
Section titled “ユニットを停止する”停止するときの動作は次のようになる。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= など順序を制御するオプションがいっぱいある。
Serviceセクション
Section titled “Serviceセクション”ここには ExecStart= の前後で実行される ExecStartPre= または ExecStartPost= や、停止前後で実行される ExecStopPost= などが用意されている。詳細はSystemdにおけるservice unitの起動フロー入門が詳しいと思う。
Installセクション
Section titled “Installセクション”Install セクションは systemctl enable または systemctl disable に作用する。
WantedBy=
Section titled “WantedBy=”WantedBy= で指定したユニットが、これを記述するユニットに Wants= で依存するようになる。例えば acme.service に WantedBy=graphical-session.target を記述した場合、acme.service を有効化すると graphical-session.target.wants/ ディレクトリに acme.service へのシンボリックリンクが作成されて、結果的に graphical-session.target へ Wants=acme.service を記述した状態と同等になる。
[Unit]PartOf=graphical-session.target
[Install]WantedBy=graphical-session.targetgraphical-session.target が acme.service に依存するの依存関係として逆じゃないかと思ったが、末尾が .target となっているユニットは「あるべき状態」を定義するものとなる。なので「graphical-session.target としてあるべき状態は acme.service を含む」を意味していて、それに満たない場合は必要なユニットを起動させる動作をする。
ここで、acme.service のユニットを次のように記述した場合を考える。
[Unit]Wants=graphical-session.target
[Install]WantedBy=graphical-session.target上記の例は acme.sservice が graphical-session.target に依存して、かつ WantedBy= により graphical-session.target が Wants=acme.service 相当となる。循環参照しているようにみえるが、上でみたように systemd はユニットを並列に起動するので、Wants= で要求するからといって起動の順序が保たれる訳ではない。順序が重要な場合は前述した Before= や After= で順序を保証する必要があるけれど、Install セクションからは指定できない。
RequiredBy=
Section titled “RequiredBy=”WantedBy= と似ているが Requires= 相当となる。上記の例でいえば、acme.service の起動に失敗した場合に graphical-session.target も起動しなくなるので、おそらく滅多に使わないと思う。
BoundBy=
Section titled “BoundBy=”BoundBy= は Unit セクションだけで、Install セクションには無い。