Ubuntu Weekly Recipe

第557回systemdのユニットの関係を読む

systemdではユニット(Unit)という単位でサービスやソケット、あるいは他のユニットをまとめるターゲットなどを管理しています。ユニット設定ファイルはプレーンテキストで書かれていて、これをsystemdが解釈してサービスやシステムの起動・停止を管理しています。

ところで、サービス同士やサービスとその関連するソケットの間では、⁠あるサービスは別のサービスが先に起動していないと使えない」⁠このサービスはあるソケットを必要としており、これよりあとに起動する必要がある」など、アクティベート順序(起動順序)や依存関係があります[1]⁠。これらのアクティベート順序や依存関係もユニット設定ファイルに記載されていて、systemdが解釈し、適切に実行していきます。

今回は、このアクティベート順序や依存関係について、systemdのユニットの設定ファイルで使われる基本的なディレクティブとその設定を調べるsystemdのコマンドを紹介し、systemdユニットの世界へ皆様をご招待します。

ユニットの設定ファイルを特定する

それでは実際のユニット設定ファイルを眺めていきましょう。今回、中身を見ていくユニットは、名前解決のサービスであるsystemd-resolved.serviceです[2]⁠。

systemct catコマンドを使用すると、ユニットの設定ファイルを表示させることができます。

$ systemctl cat systemd-resolved.service

すると、設定ファイルのパス/lib/systemd/system/systemd-resolved.serviceとその中身が表示されます。すべてを載せると量が多いため、今回のテーマであるアクティベート順序と依存関係に関する部分だけを抜粋したのが以下になります。

# /lib/systemd/system/systemd-resolved.service
(中略)
[Unit]
Description=Network Name Resolution
(中略)
DefaultDependencies=no
After=systemd-sysusers.service systemd-networkd.service
Before=network.target nss-lookup.target shutdown.target
Conflicts=shutdown.target
Wants=nss-lookup.target

(中略)
[Install]
WantedBy=multi-user.target
(後略)

アクティベート順序

ユニットのアクティベート順序は、After=Before=で設定されます。これらのディレクティブはスペース区切りをすることで、複数のユニットを指定できます。

systemd-resolved.serviceでのアクティベート順序を見ていきます。After=の設定に従い、systemd-sysusers.serviceまたはsystemd-networkd.serviceもしくはその両方が同時にアクティベートされる場合においてsystemd-resolved.serviceは、これらのユニットがアクティブになるのを待って、アクティベートを開始します。Before=では同様に、同時にアクティベートされる場合においてnetwork.targetnss-lookup.targetshutdown.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.unitB.unitを必要としておりRequires=B.unit⁠、かつ、それより後にアクティベートする必要があるAfter=B.unit⁠」という指示を表現できます[5]⁠。

WantedBy=RequiredBy=を指定した場合のシンボリックリンク

A.unitWantedBy=B.unitRequiredBy=B.unitといった形で「受動態」のディレクティブにB.unitが指定されている場合にA.unitsystemctl enableコマンドなどで有効化すると、/etc/systemd/system/以下のB.unit.wantsまたはB.unit.requiresというディレクトリに、ユニット設定ファイルへのシンボリックリンクが張られます。指定したユニットからの見て「能動態」WantsRequiresとして理解されるわけです。

systemd-resolved.serviceではWantedBy=multi-user.targetとなっていますので、/etc/systemd/system/multi-user.target.wants/にシンボリックリンクがあることが確認できます。

$ readlink /etc/systemd/system/multi-user.target.wants/systemd-resolved.service 
/lib/systemd/system/systemd-resolved.service

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ファイルを出力するだけです。

$ systemd-analyze plot > systemd-analyze.svg

このSVGファイルをブラウザで開けば、システム起動時にいつ、どのような順番でユニットがアクティベートされたかを確認できます。

次の図は、筆者で用意した環境で出力したSVGファイルの一部です。すべてを掲載すると長くなってしまうので、必要な箇所を抜粋しました。

図1 systemd-analyze plotの出力結果の抜粋
画像

さて、今回取り上げたsystemd-resolved.serviceのアクティベート順序で注目すべきはAfter=systemd-sysusers.service systemd-networkd.serviceBefore=network.target nss-lookup.target shutdown.targetでした。

今回はアクティベート順序を機能させるため、After=で指定されているsystemd-networkd.serviceを有効にしています。これは通常のデスクトップ版では、有効にされていないユニットです。

図ではsystemd-networkd.serviceの濃い赤色で示されるActivatingのバーが終わったタイミングで、systemd-resolved.serviceのバーが始まっていることを確認できます。

一方、Before=については、systemd-resolved.serviceActiveとなった後から、nss-lookup.targetnetwork.targetのバーが始まっていることを確認できます。特にnss-lookup.targetは、systemd-resolved.serviceの赤いActivatingの部分が終わったタイミングでバーが始まっています。

依存関係を見る

最後に、依存関係をツリー形式で確認します。手順は簡単で、以下のコマンドで依存関係が出力されます。

$ systemctl list-dependencies

systemd-resolved.serviceに注目し、関連しない部分を省略すると、次のようなツリーとなることがわかります。

default.target
(中略)
● └─multi-user.target
●   ├─systemd-resolved.service
(後略)

「おや、Wants=nss-lookup.targetはどこへ?」と思った人は、先のコマンドに--allをつけてみてください[7]⁠。-all付きは再帰的に実行されるため出力結果が多くなってしまいますが、systemd-resolved.serviceにしぼると次のようなツリーとなっているのがわかります。

$ systemctl list-dependencies systemd-resolved.service --all
systemd-resolved.service
● ├─-.mount
● │ └─system.slice
● │   └─-.slice
● ├─system.slice
● │ └─-.slice
● └─nss-lookup.target

アクティベート順序や依存関係については、多くのユニットで今回紹介した範囲の設定が使われています。これらの設定がわかれば、今回紹介したコマンドでかなり遊べるはずです。気になるサービスのユニットを調べてみるのも面白いでしょう。

おすすめ記事

記事・ニュース一覧