きっかけ

前回の記事でsystemdのユニットファイルを書いた時に、どうすべきか悩んだ点が1つ。
NetworkManager-wait-online.serviceをenableにすべきと伝えるか否か、である。

NetworkManager-wait-online.serviceは簡単に言えば、ネットワークデバイスがIPアドレスを取得するまで待つという処理を行うサービスである。
この「ネットワークデバイスがIPアドレスを取得するまで待つ」のを必須としたかったため、NetworkManager-wait-online.serviceをenableにする必要があると最初は思ったのだが、ドキュメントを見ると、NetworkManager-wait-online.serviceをenableにする代わりにユニットファイルの[Unit]セクションに「After=network-online.target」と「Wants=network-online.target」を指定しておけば良い、と書かれているのを見つけたためそのようにし、NetworkManager-wait-online.serviceはdisableのままで良いとした。

(実際にはWantsではなくてRequiresにした。WantsもRequiresも依存関係を表し、指定されたユニットは指定した側のユニットが起動する場合に共に起動するよう指示するための設定項目だが、Wantsの場合は依存対象のユニットがエラーになっても特に気にせず起動し、Requiresの場合は依存対象のユニットがエラーになればこちらも起動せずエラーとなる、という違いがある。なおnetwork-online.targetがエラーになることがあるのかどうかは確認していない)

調査

さて、ユニットファイルのAfterとWants/Requiresに指定した「network-online.target」とは何者だろうか。
targetタイプのユニットは他のユニットをグループ化するという役割を持つ。
network-online.targetが何をグループ化しているかは /usr/lib/systemd/system/network-online.target.wants/ の中に何がシンボリックリンクになっているかで知ることができる。
CentOS 7.1においては、このディレクトリにはシンボリックリンクが1つあり、それは件のNetworkManager-wait-online.serviceへのものである。
従ってnetwork-online.targetはNetworkManager-wait-online.serviceのみをグループ化したものであることがわかる。

# ll /usr/lib/systemd/system/network-online.target.wants/
total 0
lrwxrwxrwx. 1 root root 58 Apr  6 06:03 NetworkManager-wait-online.service -> /usr/lib/systemd/system/NetworkManager-wait-online.service

「グループ化」と言ったが、シンボリックリンクが置かれるのがnetwork-online.target.wantsというディレクトリ名であることから、シンボリックリンクが貼られているというのはそのtargetからのWantsとして指定された、という意味であることがわかる(man systemd.unit)。
なのでこのtargetが別のユニットにWants/Requiresされた場合に「グループ化されたそれぞれのサービス」はtargetにWantsされているために引きずられて一緒くたに起動することになる、というのがグループ化の仕組みの実際である。

NetworkManager-wait-online.service自体は、そのユニットファイルのAfterに「NetworkManager.service」が、Beforeに「network.target」と「network-online.target」が指定されているため、NetworkManager.serviceより後に起動し、network.targetとnetwork-online.targetより先に起動することになる(この中で起動しないものがあればその順番は飛ばされる)。

# cat /usr/lib/systemd/system/NetworkManager-wait-online.service
[Unit]
Description=Network Manager Wait Online
Requisite=NetworkManager.service
After=NetworkManager.service
Wants=network.target
Before=network.target network-online.target

[Service]
Type=oneshot
ExecStart=/usr/bin/nm-online -s -q --timeout=30

[Install]
WantedBy=multi-user.target

結局のところ「network-online.target」をユニットファイルのAfterとWants/Requiresに指定するという行為は、network-online.targetが起動した後、すなわちnetwork-online.targetより前に起動することが定まっているNetworkManager-wait-online.serviceの処理が完了した後にそのユニットを起動するように指示することに他ならない。
確かにsystemctl status NetworkManager-wait-online.serviceで確認すると、NetworkManager-wait-online.serviceはdisableのままなのに起動していることがわかる。
これでnetwork-online.targetをAfterとWants/Requiresに指定しておけば、NetworkManager-wait-online.serviceをenableにしなくて良い理由が判明した。

# systemctl status NetworkManager-wait-online.service
NetworkManager-wait-online.service - Network Manager Wait Online
   Loaded: loaded (/usr/lib/systemd/system/NetworkManager-wait-online.service; disabled)
   Active: inactive (dead) since Tue 2015-04-21 21:31:01 JST; 24h ago
 Main PID: 654 (code=exited, status=0/SUCCESS)
   CGroup: /system.slice/NetworkManager-wait-online.service

Apr 21 21:30:55 consul-server-node1 systemd[1]: Starting Network Manager Wait Online...
Apr 21 21:31:01 consul-server-node1 systemd[1]: Started Network Manager Wait Online.

ところで、「disableだが他のサービスやtargetにWants/Requiresされたために起動した」場合と「enableだから起動した」場合に違いはあるだろうか。
実際のところサービスをenableにすると、ユニットファイルの[Install]セクションのWantedBy項に指定されたtargetの.wantsディレクトリにシンボリックリンクを貼る処理が行われるのみである。
つまりenableにすればWantedByに指定されたtargetのWantsになり、disableならそうではない、というだけのことだ。
ゆえに、「disableだが他のサービスやtargetにWants/Requiresされたために起動した」場合と「enableだから起動した」場合とでは結局どちらもWants/Requiresで呼ばれたことに変わりなく、特に違いはないと言える。

(厳密にはtargetにWants/RequiresされるとAfterも一緒についてくるので違いが出る場合もありえるが、それはレアケースだろう)

結論

ここまでで、NetworkManager-wait-online.serviceはどのような場合にenableにすれば良いかは次のように決定できると結論付けられる。

  • OS起動時に起動するサービスのうちどれかがnetwork-online.targetをWants/Requiresしていることがわかっていれば、もはやNetworkManager-wait-online.serviceをenableにする意味はない。
  • その環境でIPアドレスを取得できていない状態で起動しているためにエラーになっているサービスがあって、そのサービスが「After=network.target」である場合はenableにする意味がある。

ただし前者について、network-online.targetをWants/Requiresしているものがあるかを調べるのは面倒そうだ。
今のところそれを調べずにsystemctl status NetworkManager-wait-online.serviceを実行して、起動していたかを見るのが一番簡単だと思っている。

もしIPアドレスを取得できていない状態で起動しているためにエラーになっているサービスがあるが、「After=network.target」ではない場合、ということがあればドロップインスニペットを使うと良い。

ドロップインスニペットの作成にはsystemctl edit <サービス名>を実行する。
エディタが起動するのでそのサービスに対する追加設定を記述して保存すると、そのサービスのユニット設定に追加されることになる。
今回の目的を達成するにはWants/RequiresとAfterにnetwork-online.targetを追加してやれば良いので次のように書く。

[Unit]
Wants=network-online.target
After=network-online.target

もっともCentOS 7などではsystemctl editは使えないので、同じことをするには以下となる。

# mkdir /etc/systemd/system/<サービス名>.service.d
# cat > /etc/systemd/system/<サービス名>.service.d/override.conf <<EOD
[Unit]
Wants=network-online.target
After=network-online.target
EOD
# systemctl daemon-reload

付記

1

enableにしたからと言って、それがOS起動時に必ず起動するかというとそうではない。
NetworkManager-wait-online.serviceのWantedByに指定されたtargetはmulti-user.targetである。そのためmulti-user.targetがdefault targetになっている時か、default targetになっているtargetが直接あるいは間接にmulti-user.targetをWants/Requiresしている時にしかenableにした効果はない。
また同じ理由で他のユニットにWants/RequiresされていてもOS起動時に必ず起動するわけではない。

SysVによる旧来のランレベルに相当する処理は、systmedではこのtargetとdefault targetの仕組みによって行われている。
multi-user.targetは旧来のランレベル3に相当するものであり、またランレベル5に相当するgraphical.targetはmulti-user.targetをRequiresしているのでgraphical.targetをdefault targetにした場合でもmulti-user.targetは起動することになる。

2

自作のユニットファイルのAfterとWants/Requiresに「NetworkManager-wait-online.service」ではなく「network-online.target」を指定するのは、network-online.targetはsystemdに標準的に用意されたtargetでありsystmedを使っているなら必ず存在すると考えられる一方、NetworkManager-wait-online.serviceはNetworkManagerを使っていなければ存在せず、結果network-online.targetを指定した方がより汎用性が高いと考えられるためである。
NetworkManagerではなくsystemd-networkdを使っているシステムではNetworkManager-wait-online.serviceの代わりにsystemd-networkd-wait-online.serviceが存在する。

もっとも、初期状態でnetwork-online.targetの.wantsディレクトリにNetworkManager-wait-online.serviceあるいはsystemd-networkd-wait-online.serviceのシンボリックリンクがあることは保証できない。
実際に適当にAntergosをインストールしてみたが、systemd-networkd-wait-online.serviceのWantedByがnetwork-online.targetになっていて、かつdisableされた状態だった。

TOP