OS起動時にsystemdで行われていること

ここ数週間 systemd について学んできたので、トピックごとにまとめておきます。

第一回目は、OS起動時に systemd で実行される `default.target` を起点とした依存関係のツリーについてです。

なお、本記事の内容は Ubuntu 16.04, Debian Stretch で動作確認しています。

## systemctl list-dependencies

まずはおもむろに `sudo systemctl list-dependencies` を実行してみましょう！

![image.png](https://qiita-image-store.s3.amazonaws.com/0/41336/ab2eea19-654b-cc4c-b9d3-a4247157bf1d.png)

`default.target` を起点として、それから要求される形でどのようなユニットが起動されたかが見られます。緑は成功、赤は失敗、白はその他(依存関係が満たされず、起動されなかった、など)です。

他のことは忘れて良いので、今回、このコマンドだけでも覚えて帰ってください！


## default.target とは？

`default.target` とは、OS の起動シーケンスの起点となるユニットで、systemd は `default.target` に記述されている依存関係を満たすように、各種ユニットを立ちあげます。

`defualt.target` の実体はシンボリックリンクで、具体的な処理の中身はリンク先のターゲットによって定められています。


## default.target に紐づくターゲットとは？

一般には、GUI のシステムでは `graphical.target` に、GUI 無しのシステムでは `multi-user.target` に紐付いています。

`default.target` に何が紐付いているかは `systemctl get-default` で確認できます。

```
$ systemctl get-default
graphical.target
```

また、 `systemctl set-default` によって `default.target` に紐づくターゲットを変更できます。

```
$ sudo systemctl set-default multi-user.target
Removed symlink /etc/systemd/system/default.target.
Created symlink from /etc/systemd/system/default.target to /lib/systemd/system/multi-user.target.
```

## graphical.target の中身は？

`multi-user.target` + `display-manager.service` です。マルチユーザ環境の構築をしつつ、 display manager を立ちあげます。

詳しく知りたい人は `graphical.target` の中身を読んでみてください。

```
$ cat /lib/systemd/system/graphical.target
```


## multi-user.target は何をするのか？

`multi-user.target` は複数ユーザ環境のベースとなるターゲットで、多くのサービスがこのターゲットに紐付いて起動されます。

`multi-user.target` 自身で指定されている依存関係は `basic.target` への Requires だけですが、外部のユニットから指定された `multi-user.target.wants` が重要です。

- /lib/systemd/system/multi-user.target.wants/
- /etc/systemd/system/multi-user.target.wants/

これらのディレクトリの下に `multi-user.target` に依存するユニットのシンボリックリンクが置かれており、これらが集まって `multi-user.target` を起点とした依存関係のグラフが形成され、OS 起動時に実行されるわけです。

## どうやってユニットを multi-user.target に紐付けるのか？

systemd で管理された各種ユニットは、定義ファイルに

```
[Install]
WantedBy=multi-user.target
```

のように記述することで、`multi-user.target` の起動時に一緒に起動するよう、要求することができます。

なお、この記述だけがあっても自動起動はしません。`systemctl enable <unit>` コマンドで有効にして初めて、 `multi-user.target.wants` にシンボリックリンクが生成され、 `multi-user.target` の実行時にユニットがあわせて起動されるようになります。

また `systemctl disable <unit>` をすれば、シンボリックリンクが削除されて、 `multi-user.target` の依存関係のグラフからユニットが外されます。

ただし、enable にしていても、依存ツリーの中で自身が要求されなければ起動されないですし、disable であっても、何かしらのルートで要求があると起動します。このあたりの依存関係のルールの詳細は、別の記事で説明しようと思います。


## 「起動にかかった時間」とは？

「OSの起動にかかる時間」とは、「`defualt.target` の依存関係ツリーを立ち上げるのにかかった時間」とほぼ同じ意味で、 `systemd-analyze` コマンドで確認できます。

```
$ sudo systemd-analyze
Startup finished in 5.483s (firmware) + 4.044s (loader) + 1.658s (kernel) + 6.310s (userspace) = 17.496s
```

同様のログは、`journalctl` にも記録されています。

```
$ sudo journalctl -b
...
Apr 11 14:07:24 somehost systemd[1]: Reached target Multi-User System.
Apr 11 14:07:24 somehost systemd[1]: Reached target Graphical Interface.
Apr 11 14:07:24 somehost systemd[1]: Starting Update UTMP about System Runlevel Changes...
Apr 11 14:07:24 somehost systemd[1]: Started Update UTMP about System Runlevel Changes.
Apr 11 14:07:24 somehost systemd[1]: Startup finished in 2.185s (kernel) + 9.827s (userspace) = 29.116s.
...
```

## default.target にカスタムのターゲットを紐付けたい

この仕組みを利用すれば、カスタムのターゲットも簡単に作成できます。

`graphical.target` を参考にするのが良いでしょう。

例えば、自社ロボット専用の起動シーケンスを作りたい場合、下記のようなターゲットを作り、

```
$ cat /lib/systemd/system/robot.target
[Unit]
Description=Robot Mode
Requires=multi-user.target
Conflicts=rescue.service rescue.target
After=multi-user.target rescue.service rescue.target
AllowIsolate=yes
```

起動時に立ちあげたい systemd のユニットに

```
[Install]
WantedBy=robot.target
```

のような依存関係を定義し、

```
sudo systemctl set-default robot.target
```

を実行すれば、OS起動時に自動でユニットを立ちあげられるようになります。また、

```
sudo systemctl set-default multi-user.target
```

などとすれば、元の起動シーケンスに戻せます。


## AllowIsolate とは何か？

`AllowIsolate` や有効になっているユニットは、 `systemctl isolate` コマンドで起動することができます。`systemctl isolate` コマンドでターゲットを起動すると、 **指定したターゲットが(明示的に)依存するユニット以外は全て停止してから**、ターゲットが起動されます。set-default で指定するターゲットは、基本的には `AllowIsolate` が指定されたターゲットです。

ただし、下記の Issue で議論されているように、 `systemctl isolate` が実行された瞬間に、 **明示的に** 定義されている依存関係以外はいったんすべて終了してしまうので、モードの切替に `systemctl isolate` コマンドは使いづらいです。例えば robot.target に切り替えたい場合、 `set-default` してからシステムを再起動する方が確実です。

- [softer version of systemctl isolate?](https://github.com/systemd/systemd/issues/2607)


## 関連資料

全体の体系としては分かりづらいですが、個々の仕様を確認するには systemd のオンラインマニュアルが有用です。

- [systemd.index](https://www.freedesktop.org/software/systemd/man/index.html)
    - [systemctl](https://www.freedesktop.org/software/systemd/man/systemctl.html)
    - [systemd.unit](https://www.freedesktop.org/software/systemd/man/systemd.unit.html)