systemdのお勉強


#はじめに
Linuxのサービスやデーモンを管理する仕組みであるsystemdについて勉強したので、そのまとめです。

systemdはRHEL7から採用されていて、RHEL8でもそこは同じです。
それ以前はSysVinit(init)/Upstartと呼ばれる仕組みが使われていましたが、そこは本記事では触れません。

基本的にRedHat公式ドキュメント「[第3章 SYSTEMD によるサービス管理](https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/8/html/configuring_basic_system_settings/managing-services-with-systemd_configuring-basic-system-settings)」に沿っています。

### 環境
[Shimer-System: vagrantでrhel8を試してみる](https://shimer-system.com/archives/674)を参考に、vagrant&virtualboxでrhel8を起動しました。

# 起動処理
>Linuxの起動はざっくりと以下の4段階によって行われます。
>
>電源投入によりBIOSが起動する。
BIOSからブートローダーが呼び出される。
ブートローダーがLinuxカーネルを起動する。
Linuxカーネルがinitプロセス(PID 1)を起動する。

出典：[Developers.IO by Classmethod: systemd超入門](https://dev.classmethod.jp/articles/systemd-getting-started/)

上記のinitプロセスが従来、`/sbin/init`だったのが、systemdでは`/usr/bin/systemd`となる。

```bash
[vagrant@rhel8 ~]$ ps u -p 1
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.2  0.7 179556 13924 ?        Ss   07:23   0:01 /usr/lib/systemd/systemd --switched-root --system --deserialize 18
```

# Unit
>systemdでは、「Unit」という単位で処理を管理します。これまで、rc.sysinitやサービス起動スクリプトが実施していた処理の内容は、すべて、Unitとして定義されます。

出典：[Systemd入門(1) - Unitの概念を理解する](https://enakai00.hatenablog.com/entry/20130914/1379146157)

[RedHatのページ](https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/8/html/configuring_basic_system_settings/managing-services-with-systemd_configuring-basic-system-settings#introduction-to-systemd_managing-services-with-systemd)に書いてありますが、ユニット設定ファイルは以下のディレクトリにあります。下に行くほど設定内容は優先されます。

ディレクトリ|説明
---|---
/usr/lib/systemd/system/|インストール済みのRPMパッケージで配布された設定ファイル
/run/systemd/system/|ランタイム時に自動作成されたsystemd ユニットファイル
/etc/systemd/system/|systemctl enableで作成された systemd ユニットファイル、およびサービス拡張向けに追加されたユニットファイル

ユニットにはタイプ（種類）があり、主なものは以下になります。

タイプ|説明|備考
---|---|---
service|システムサービス|主にデーモンの起動・管理
target|ユニットをグループ化するためのもの
mount|ファイルシステムのマウントポイント|/etc/fstabから自動作成される
automount|ファイルシステムの自動マウントポイント
device|デバイスファイル|udevがデバイスを認識すると　自動作成される
socket|プロセス間の通信ソケット|systemdがsocketをListenして、接続があると指定のデーモンを起動してプロセスに受け渡す
path|ファイルシステム内のファイルまたはディレクトリー|指定のファイルが作成されると、指定されたサービスを起動する

# デフォルト設定
systemdのデフォルト設定は`/etc/systemd/system.conf`で確認で居ます。
このデフォルト設定はコンパイル中に定義されたものであり、このファイルを編集することでデフォルト値を上書きできます。
このファイルの中にも以下のように書かれています。
> Entries in this file show the compile time defaults.
 You can change settings by editing this file.
 Defaults can be restored by simply deleting this file.


# サービスの管理
SysVinit/Upstartでは、`/etc/rc.d/init.d/`ディレクトリーにあるinitスクリプト(Bashのスクリプト)がサービスの起動・管理で利用されていましたが、systemdではサービスユニットの設定ファイル(xxx.service)に置き換わります。

###起動・停止系
サービスの**起動**・**停止**・**再起動**・**実行中のみ再起動**・**設定再読み込み**・**状態確認**は、それぞれ以下の通り`systemctl`コマンドで実行します。サービスに限っては、ユニットファイルの`.service`は省略できます。

```
systemctl start|stop|restart|try-restart|reload|status(is-active) xxx.service(サービスユニットファイル)
```

**全てのサービスの状態確認**は以下のコマンドで確認できます。

```
systemctl list-units --type service --all
```

#### systemctl statusの表示内容
```terminal:example
[vagrant@rhel8 ~]$ systemctl status chronyd.service
● chronyd.service - NTP client/server
   Loaded: loaded (/usr/lib/systemd/system/chronyd.service; enabled; vendor preset: enabled) ←ユニット設定ファイルが読み込まれているか、そのファイルパス、有効かどうか
   Active: active (running) since Sun 2021-01-31 07:24:00 UTC; 2h 7min ago ←起動状態、タイムスタンプ
     Docs: man:chronyd(8)
           man:chrony.conf(5)
 Main PID: 839 (chronyd) ←サービスのPIDとその名前
    Tasks: 1 (limit: 11394)
   Memory: 2.4M
   CGroup: /system.slice/chronyd.service ←コントロールグループ (cgroup) の情報
           └─839 /usr/sbin/chronyd
```

### 有効化・無効化系（自動起動設定）
サービスの**有効化（自動起動設定）**・**無効化**・**設定確認**は、それぞれ以下のコマンドで実行します。

```
systemctl enable|disable|status(is-enabled) xxx.service(サービスユニットファイル)
```

**全てのサービス設定確認**・**サービスの開始タイミングの依存関係（他ユニットの前/後）の確認**は以下のコマンドで確認できます。

```
systemctl list-unit-files --type service
systemctl list-dependencies --after
systemctl list-dependencies --before
```

#### 有効化すると何が起きるか

enableを行うと、選択したサービスユニット設定ファイルのの`[Install]`セクションを読み取り、`/etc/systemd/system/`配下（もしくはそのサブディレクトリ）に`/usr/lib/systemd/system/name.service`ファイルへのシンボリックリンクを作成します。
実際、enable、disableを行うと以下の通りメッセージが出力される。

```terminal:example
[vagrant@rhel8 ~]$ sudo systemctl disable rsyslog.service
Removed /etc/systemd/system/syslog.service.
Removed /etc/systemd/system/multi-user.target.wants/rsyslog.service.
[vagrant@rhel8 ~]$
[vagrant@rhel8 ~]$ sudo systemctl enable rsyslog.service
Created symlink /etc/systemd/system/multi-user.target.wants/rsyslog.service → /usr/lib/systemd/system/rsyslog.service.
[vagrant@rhel8 ~]$
[vagrant@rhel8 ~]$ ls -l /etc/systemd/system/multi-user.target.wants/ | grep syslog
lrwxrwxrwx. 1 root root 39 Jan 31 09:46 rsyslog.service -> /usr/lib/systemd/system/rsyslog.service

```

### エイリアス
ユニットにはエイリアス名も設定することができ、特定のユニットのエイリアスは以下のコマンドで確認できます。

```
systemctl show nfs-server.service -p Names
```

# systemdのターゲット
ターゲットユニットファイル(xxx.target)は依存関係の連鎖で他のsystemdユニットをグループ化するために使われます。
Linuxを起動する際はモードというものが数種類あり、systemd以前はその仕組みをランレベルと呼んでいました。systemdでは従来のランレベルをターゲットユニットで定義しており、互換性の観点から、以下の通りマッピングされています（これも[RedHatのページ](https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/8/html/configuring_basic_system_settings/managing-services-with-systemd_configuring-basic-system-settings#difference-between-sysv-runlevels-and-systemd-targets_working-with-systemd-targets)に載っています）

ランレベル|ターゲットユニット|説明
---|---|---|
0|runlevel0.target, poweroff.target|システムをシャットダウンし、電源を切ります。
1|runlevel1.target, rescue.target|レスキューシェルを設定します。
2|runlevel2.target, multi-user.target|非グラフィカルなマルチユーザーシステムを設定します。
3|runlevel3.target, multi-user.target|非グラフィカルなマルチユーザーシステムを設定します。
4|runlevel4.target, multi-user.target|非グラフィカルなマルチユーザーシステムを設定します。
5|runlevel5.target, graphical.target|グラフィカルなマルチユーザーシステムを設定します。
6|runlevel6.target, reboot.target|システムをシャットダウンして再起動します。

### デフォルトターゲット

`/etc/systemd/system/default.target`のファイル、もしくは`systemctl get-default`コマンドで確認できます。
↓の例では、multi-user.targetがデフォルトになっています。

```terminal:example
[vagrant@rhel8 ~]$ systemctl get-default
multi-user.target
[vagrant@rhel8 ~]$ ls -l /etc/systemd/system/default.target
lrwxrwxrwx. 1 root root 41 Jan 20 22:51 /etc/systemd/system/default.target -> /usr/lib/systemd/system/multi-user.target
```

デフォルトターゲットは`systemctl set-default xxxx.target`コマンドで変更できます。
これによりdefault.targetのシンボリックリンクが置き換わります。

### レスキューモード
レスキューモードはシステムの修復などで利用するシングルユーザー環境を提供するモードで、ネットワーク機能や他のユーザのログインは不可のまま、ローカルファイルシステムのマウントと、一部のシステムサービスのみを開始します。
現行のセッションでレスキューモードに入るには以下の通り実行します。現在ログインしているユーザにメッセージが送信されます。

```
# systemctl rescue

Broadcast message from root@localhost on pts/0 (Fri 2013-10-25 18:23:15 CEST):

The system is going down to rescue mode NOW!
```

# systemdでの電源管理
systemdでは`systemctl`コマンドでシステムのシャットダウン・再起動・サスペンドなども行えます。
詳細は[RedHatのページ](https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/8/html/configuring_basic_system_settings/managing-services-with-systemd_configuring-basic-system-settings#difference-between-sysv-runlevels-and-systemd-targets_working-with-systemd-targets)を参照ください。
こちら↓も参考になりました。
[haltを実行してもSystem haltedで止まり電源が落ちない（halt, shutdown, poweroffの違いまとめ）](https://kuroeveryday.blogspot.com/2016/11/shutdown-vs-halt-vs-poweroff.html)

# systemdユニットファイルの設定
例として、chronyd(ntpの後継)のユニット設定ファイル(デフォルト)が以下になります。
（参考）[Systemd入門(4) - serviceタイプUnitの設定ファイル](https://enakai00.hatenablog.com/entry/20130917/1379374797)

```
[vagrant@rhel8 ~]$ sudo cat /usr/lib/systemd/system/chronyd.service
[Unit] ←typeに依存しない一般的な設定
Description=NTP client/server ←ユニットの説明。`systemctl status`で出力される
Documentation=man:chronyd(8) man:chrony.conf(5) ←ドキュメントを参照するURI
After=ntpdate.service sntp.service ntpd.service ←起動順序。指定したユニットの後に開始する
Conflicts=ntpd.service systemd-timesyncd.service ←同時に有効化してはいけないユニット
ConditionCapability=CAP_SYS_TIME ←
##他には
##Requires: 同時に有効化するユニット。この中のいずれかが開始しないと、アクティブにならない
##Wants: 同時に有効化するユニット。Requireよりも弱い依存性で、前提Unitが失敗しても起動処理を行う

[Service] ←サービスtype固有の設定
Type=forking ←起動タイプ。forkingの場合は、ExecStartで起動するプロセスが、サービスのメインとなる子プロセスを起動する。デフォルトはsimple(ExeｃStartの起動プロセスがメインプロセスとなる)
PIDFile=/run/chrony/chronyd.pid ←PIDファイルのパス
EnvironmentFile=-/etc/sysconfig/chronyd ←環境設定ファイル
ExecStart=/usr/sbin/chronyd $OPTIONS ←ユニットの開始時に実行するコマンド
ExecStartPost=/usr/libexec/chrony-helper update-daemon ←サービス起動後の追加コマンド
PrivateTmp=yes ← プロセスが使う一時ディレクトリをそのプロセスにしか見えないようにする
ProtectHome=yes ←Unitのプロセスが/home, /root, /run/userに一切アクセスできなくなる
ProtectSystem=full ←trueの場合、実行するUnitのジョブから見た/usr, /bootが読み取りのみ(書き込み禁止)になる。fullだと、/etcも読み取りのみになる。

[Install] ←有効/無効化された時の設定
WantedBy=multi-user.target ←このユニットに依存するユニット
##他には
##Required: このユニットに強く依存するユニット
```

# カスタムユニットファイルの作成
自前のカスタムデーモンを実行する場合、既存のサービスの2つ目のインスタンスを作成する場合の2パターンを試してみます。

## カスタムデーモンの作成
※さくっとやりたい方は、[[Systemdを使ってさくっと自作コマンドをサービス化してみる](https://qiita.com/DQNEO/items/0b5d0bc5d3cf407cb7ff)]を参照いただくと良いかもしれません。

dateコマンドの結果をファイルに書き込むだけのデーモン用scriptを作成しました。

```bash:date.sh
#!/bin/bash

while true;
do
	echo $(date) >> /opt/date.txt
	sleep 2
done
```

これを使って、以下のようなUnit定義ファイルを用意します。

```:/etc/systemd/system/date.service
[Unit]
Description = Date Log(Custom daemon)

[Service]
Type = simple
ExecStart = /opt/date.sh

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

すると、以下の通りUnit一覧に追加されています。

```
[root@rhel8 ~]# systemctl list-unit-files --type=service | grep date.service
date.service                               disabled
```

以下のコマンドで、scriptの実行権限を追加後、新しいUnitファイルの読み込み・起動が出来ます。

```
# chmod a+x /opt/date.sh
# ls -l /opt/date.sh
-rwxr-xr-x. 1 root root 67 Feb  1 15:12 /opt/date.sh
# systemctl daemon-reload
# systemctl start date.service
# systemctl status date.service
● date.service - Date Log(Custom daemon)
   Loaded: loaded (/etc/systemd/system/date.service; disabled; vendor preset: disabled)
   Active: active (running) since Mon 2021-02-01 15:24:42 UTC; 3s ago
 Main PID: 26275 (date.sh)
    Tasks: 2 (limit: 11394)
   Memory: 864.0K
   CGroup: /system.slice/date.service
           ├─26275 /bin/bash /opt/date.sh
           └─26279 sleep 2

Feb 01 15:24:42 rhel8.localdomain systemd[1]: Started Date Log(Custom daemon).
#
# tail -f /opt/date.txt
Mon Feb 1 15:28:53 UTC 2021
Mon Feb 1 15:28:55 UTC 2021
Mon Feb 1 15:28:57 UTC 2021
...
# ps aux | grep date
root       26851  0.0  0.1  25952  3408 ?        Ss   15:33   0:00 /bin/bash /opt/date.sh
```

## 既存サービスの2つ目のインスタンス作成
[こちら](https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/8/html/configuring_basic_system_settings/managing-services-with-systemd_configuring-basic-system-settings#creating-custom-unit-files_working-with-systemd-unit-files)の「3.5.3.1. sshd サービスの 2 番目のインスタンスを使用したカスタムユニットファイルの作成」に`sshd`の2つ目のサービスを起動する方法が載っています。
詳細はここでは割愛しますが、主に以下の内容を行っています。
サービスに依って必要な内容は変わると思います。

* サービスの設定ファイル、Unit定義ファイルをコピーして編集（主な変更点は↓のとおり）
* 元々のサービスと重なってしまうサービス設定(ポート番号、PIDファイルなど)の変更
* ↑の変更に伴うUnit定義ファイルの変更（設定ファイル名, Afterオプション(元々のインスタンスの後にする)など）
* 2つ目のインスタンスでは実行不要なもの(鍵の生成など)はUnit定義ファイルから削除


#参考
[RedHatカスタマポータル: 第3章 SYSTEMD によるサービス管理](https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/8/html/configuring_basic_system_settings/managing-services-with-systemd_configuring-basic-system-settings)
[Linux女子部 systemd徹底入門](https://www.slideshare.net/enakai/linux-27872553)
[Developers.IO by Classmethod: systemd超入門](https://dev.classmethod.jp/articles/systemd-getting-started/)
[Qiita: いまだにsysvinitの手癖が抜けない人のためのsystemd](https://qiita.com/n-i-e/items/9beffea6e0bc5f08036b)
[Qiita: これからSystemd入門する](https://qiita.com/bluesDD/items/eaf14408d635ffd55a18)
[めもめも： Systemd入門(1) - Unitの概念を理解する](https://enakai00.hatenablog.com/entry/20130914/1379146157)
[めもめも: Systemd入門(4) - serviceタイプUnitの設定ファイル](https://enakai00.hatenablog.com/entry/20130917/1379374797)