systemdではユニット(Unit)という単位でサービスやソケット、あるいは他のユニットをまとめるターゲットなどを管理しています。ユニット設定ファイルはプレーンテキストで書かれていて、これをsystemdが解釈してサービスやシステムの起動・停止を管理しています。
ところで、サービス同士やサービスとその関連するソケットの間では、「あるサービスは別のサービスが先に起動していないと使えない」「このサービスはあるソケットを必要としており、これよりあとに起動する必要がある」など、アクティベート順序(起動順序)や依存関係があります[1]。これらのアクティベート順序や依存関係もユニット設定ファイルに記載されていて、systemdが解釈し、適切に実行していきます。
今回は、このアクティベート順序や依存関係について、systemdのユニットの設定ファイルで使われる基本的なディレクティブとその設定を調べるsystemdのコマンドを紹介し、systemdユニットの世界へ皆様をご招待します。
ユニットの設定ファイルを特定する
それでは実際のユニット設定ファイルを眺めていきましょう。今回、中身を見ていくユニットは、名前解決のサービスであるsystemd-resolved.service
です[2]。
systemct cat
コマンドを使用すると、ユニットの設定ファイルを表示させることができます。
すると、設定ファイルのパス/lib/systemd/system/systemd-resolved.service
とその中身が表示されます。すべてを載せると量が多いため、今回のテーマであるアクティベート順序と依存関係に関する部分だけを抜粋したのが以下になります。
アクティベート順序
ユニットのアクティベート順序は、After=
とBefore=
で設定されます。これらのディレクティブはスペース区切りをすることで、複数のユニットを指定できます。
systemd-resolved.service
でのアクティベート順序を見ていきます。After=
の設定に従い、systemd-sysusers.service
またはsystemd-networkd.service
、もしくはその両方が同時にアクティベートされる場合において、systemd-resolved.service
は、これらのユニットがアクティブになるのを待って、アクティベートを開始します。Before=
では同様に、同時にアクティベートされる場合において、network.target
、nss-lookup.target
、shutdown.target
の3つは、systemd-resolved.service
がアクティブになるのを待ってアクティベートを開始します。複数のユニットが同時に非アクティブ化される際は、逆の順序で実施されます。つまり、After=
で指定されたユニット、当該ユニット、Before=
で指定されたユニットの順に非アクティブ化されます。
ここでAfter=
もBefore=
もユニットのアクティベートの順序を示すだけで、「このサービスには別のこのサービスが必要」というユニット同士の依存関係を示すわけではない点に注意してください。この点で、System V系のinitでは基本的には起動順序でサービス同士の依存関係をコントロールしていたのと大きく異なります[3]。
「同時にアクティベートされる場合において」と太字で強調したのは、同時でなければ、当該ユニットはAfter=
やBefore=
の設定に関係なく、アクティベートされるためです。
依存関係
次に、ユニットの依存関係を見ています。systemd-resolved.service
でも、依存関係がいくつか設定されていますので、具体的に説明をしていきます。
なお、ここまで「同時にアクティベートされる場合において」と説明をしてきましたが、この「同時にアクティベートされる」という状況を作るのが、この項で説明する「依存関係」と理解しても、ひとまず問題ないでしょう[4]。
Wants=
とWantedBy=
Wants=
を見てください。Wants=
では対象としてnss-lookup.target
が設定されています。これはsystemd-resolved.service
がアクティベートされるときに、指定されたユニットもアクティベートしてほしい(wants)ということを表現しています。
また、[Install]
セクションではWantedBy=multi-user.target
と指定されています。これはmulti-user.target
がアクティベートされるときに、systemd-resolved.service
もアクティベートされてほしいということを表現しています。
ここで「ほしい」と太字で強調しているのは、これらはゆるい依存関係であるためです。
つまり、Wants=
だと、指定されたユニット(今回の場合はnss-lookup.target
)のアクティベートを試みますが、その結果、何らかの事情でこれが失敗した場合でも、依存関係として指定した側(systemd-resolved.service
)には影響はなく、アクティベートを続行します。
WantedBy=
でも同じです。当該ユニット(systemd-resolved.service
)のアクティベートが試みられ、結果的に失敗しても、指定したユニット(multi-user.target
)のアクティベートには影響しません。
Requires=
とRequiredBy=
あいにく、systemd-resolved.service
にはありませんが、Wants=
やWantedBy=
よりも強力な依存関係に、Requires=
とRequiredBy=
があります。Requires=
で指定したユニットもアクティベートがトリガーされますが、Wants=
とは異なり、こちらはそのユニットを必要(requires)としています。そのため何らかの事情でアクティベートに失敗すると、当該ユニットもアクティベートに失敗します。
RequiredBy=
はRequires=
を受動態としたものです。A.unit
の設定でRequiredBy=B.unit
となっている場合にB.unit
がアクティベートされると、A.unit
もアクティベートされます。この場合もA.unit
のアクティベートが失敗すると、RequiredBy=
で指定されたB.unit
のアクティベートは失敗します。
これらの依存関係を設定するディレクティブと、アクティベート順序を設定するディレクティブとを組み合わせることで、「A.unit
はB.unit
を必要としており(Requires=B.unit
)、かつ、それより後にアクティベートする必要がある(After=B.unit
)」という指示を表現できます[5]。
WantedBy=
とRequiredBy=
を指定した場合のシンボリックリンク
A.unit
でWantedBy=B.unit
やRequiredBy=B.unit
といった形で「受動態」のディレクティブにB.unit
が指定されている場合にA.unit
をsystemctl enable
コマンドなどで有効化すると、/etc/systemd/system/
以下の「B.unit.wants
」または「B.unit.requires
」というディレクトリに、ユニット設定ファイルへのシンボリックリンクが張られます。指定したユニットからの見て「能動態」のWants
やRequires
として理解されるわけです。
systemd-resolved.service
ではWantedBy=multi-user.target
となっていますので、/etc/systemd/system/multi-user.target.wants/
にシンボリックリンクがあることが確認できます。
Conflicts=
systemd-resolved.service
にはConflicts=
の指定があります。これは一風変わった依存関係で、Conflicts=
で指定されたユニットか、その指定しているユニットのどちらか一方がアクティブになるというものです。
systemd-resolved.service
ではConflicts=shutdown.target
となっています。よって、systemd-resolved.service
がアクティブならshutdown.target
は非アクティブ、その逆もまた然りとなります。
shutdown.target
がアクティブになるときは、システムがシャットダウンへ向かっているということ[6]ですので、systemd-resolved.service
も停止へ向かうと考えればさほど違和感なく理解できるはずです。
アクティベート順序・依存関係を見る
systemd-analyze
コマンドを使用することで、システム起動時に限って、実際のアクティベート順序を図(SVGファイル)として出力できます。また、依存関係もsystemctl list-dependencies
コマンドを利用することで、ツリー形式で見ることができます。
アクティベート順序を見る
systemd-analyze
コマンドを使って、systemd-resolved.service
と関連ユニットのアクティベート順序を見てみます。
手順としては簡単で以下のコマンドを実行し、リダイレクトでSVGファイルを出力するだけです。
このSVGファイルをブラウザで開けば、システム起動時にいつ、どのような順番でユニットがアクティベートされたかを確認できます。
次の図は、筆者で用意した環境で出力したSVGファイルの一部です。すべてを掲載すると長くなってしまうので、必要な箇所を抜粋しました。
さて、今回取り上げたsystemd-resolved.service
のアクティベート順序で注目すべきはAfter=systemd-sysusers.service systemd-networkd.service
とBefore=network.target nss-lookup.target shutdown.target
でした。
今回はアクティベート順序を機能させるため、After=
で指定されているsystemd-networkd.service
を有効にしています。これは通常のデスクトップ版では、有効にされていないユニットです。
図ではsystemd-networkd.service
の濃い赤色で示されるActivating
のバーが終わったタイミングで、systemd-resolved.service
のバーが始まっていることを確認できます。
一方、Before=
については、systemd-resolved.service
がActive
となった後から、nss-lookup.target
とnetwork.target
のバーが始まっていることを確認できます。特にnss-lookup.target
は、systemd-resolved.service
の赤いActivating
の部分が終わったタイミングでバーが始まっています。
依存関係を見る
最後に、依存関係をツリー形式で確認します。手順は簡単で、以下のコマンドで依存関係が出力されます。
systemd-resolved.service
に注目し、関連しない部分を省略すると、次のようなツリーとなることがわかります。
「おや、Wants=nss-lookup.target
はどこへ?」と思った人は、先のコマンドに--all
をつけてみてください[7]。-all
付きは再帰的に実行されるため出力結果が多くなってしまいますが、systemd-resolved.service
にしぼると次のようなツリーとなっているのがわかります。
アクティベート順序や依存関係については、多くのユニットで今回紹介した範囲の設定が使われています。これらの設定がわかれば、今回紹介したコマンドでかなり遊べるはずです。気になるサービスのユニットを調べてみるのも面白いでしょう。