systemdに入門する

## はじめに
少し前ではありますが、isucon13に出ました。
初めてだったので、わからないことだらけでした。
今回は、そのうちの一つである`systemd`についてまとめます。

## 環境
https://github.com/matsuu/aws-isucon

こちらのisucon13で、Ruby実装を使って検証しています。

## systemdとは
> systemd is a system and service manager for Linux operating systems. When run as first process on boot (as PID 1), it acts as init system that brings up and maintains userspace services.

https://man7.org/linux/man-pages/man1/systemd.1.html

Linuxにおけるシステム管理やサービス管理の仕組みです。
PIDが1のプロセスとして起動するinitシステムであり、プロセス階層の根元になります。

<details><summary>プロセスツリーの例(isucon13 Ruby実装)</summary><div>


```sh
$ pstree -p
systemd(1)─┬─acpid(369)
           ├─agetty(410)
           ├─agetty(421)
           ├─amazon-ssm-agen(392)─┬─{amazon-ssm-agen}(610)
           │                      ├─{amazon-ssm-agen}(611)
           │                      ├─{amazon-ssm-agen}(612)
           │                      ├─{amazon-ssm-agen}(613)
           │                      ├─{amazon-ssm-agen}(653)
           │                      ├─{amazon-ssm-agen}(956)
           │                      └─{amazon-ssm-agen}(1898)
           ├─bundle(2793)─┬─bundle(2794)─┬─{bundle}(2800)
           │              │              ├─{bundle}(2840)
           │              │              ├─{bundle}(2841)
           │              │              ├─{bundle}(2842)
           │              │              ├─{bundle}(2843)
           │              │              └─{bundle}(2844)
           │              ├─bundle(2795)─┬─{bundle}(2801)
           │              │              ├─{bundle}(2810)
           │              │              ├─{bundle}(2811)
           │              │              ├─{bundle}(2812)
           │              │              ├─{bundle}(2813)
           │              │              └─{bundle}(2814)
           │              ├─bundle(2796)─┬─{bundle}(2802)
           │              │              ├─{bundle}(2835)
           │              │              ├─{bundle}(2836)
           │              │              ├─{bundle}(2837)
           │              │              ├─{bundle}(2838)
           │              │              └─{bundle}(2839)
           │              ├─bundle(2797)─┬─{bundle}(2804)
           │              │              ├─{bundle}(2825)
           │              │              ├─{bundle}(2826)
           │              │              ├─{bundle}(2827)
           │              │              ├─{bundle}(2828)
           │              │              └─{bundle}(2829)
           │              ├─bundle(2798)─┬─{bundle}(2803)
           │              │              ├─{bundle}(2815)
           │              │              ├─{bundle}(2816)
           │              │              ├─{bundle}(2817)
           │              │              ├─{bundle}(2818)
           │              │              └─{bundle}(2819)
           │              ├─bundle(2799)─┬─{bundle}(2807)
           │              │              ├─{bundle}(2830)
           │              │              ├─{bundle}(2831)
           │              │              ├─{bundle}(2832)
           │              │              ├─{bundle}(2833)
           │              │              └─{bundle}(2834)
           │              ├─bundle(2805)─┬─{bundle}(2808)
           │              │              ├─{bundle}(2845)
           │              │              ├─{bundle}(2846)
           │              │              ├─{bundle}(2847)
           │              │              ├─{bundle}(2848)
           │              │              └─{bundle}(2849)
           │              └─bundle(2806)─┬─{bundle}(2809)
           │                             ├─{bundle}(2820)
           │                             ├─{bundle}(2821)
           │                             ├─{bundle}(2822)
           │                             ├─{bundle}(2823)
           │                             └─{bundle}(2824)
           ├─chronyd(414)───chronyd(424)
           ├─cron(373)
           ├─dbus-daemon(374)
           ├─multipathd(152)─┬─{multipathd}(167)
           │                 ├─{multipathd}(169)
           │                 ├─{multipathd}(170)
           │                 ├─{multipathd}(171)
           │                 ├─{multipathd}(172)
           │                 └─{multipathd}(173)
           ├─mysqld(709)─┬─{mysqld}(721)
           │             ├─{mysqld}(722)
           │             ├─{mysqld}(723)
           │             ├─{mysqld}(724)
           │             ├─{mysqld}(725)
           │             ├─{mysqld}(726)
           │             ├─{mysqld}(727)
           │             ├─{mysqld}(728)
           │             ├─{mysqld}(729)
           │             ├─{mysqld}(732)
           │             ├─{mysqld}(740)
           │             ├─{mysqld}(741)
           │             ├─{mysqld}(742)
           │             ├─{mysqld}(743)
           │             ├─{mysqld}(744)
           │             ├─{mysqld}(745)
           │             ├─{mysqld}(748)
           │             ├─{mysqld}(749)
           │             ├─{mysqld}(750)
           │             ├─{mysqld}(751)
           │             ├─{mysqld}(752)
           │             ├─{mysqld}(753)
           │             ├─{mysqld}(754)
           │             ├─{mysqld}(755)
           │             ├─{mysqld}(756)
           │             ├─{mysqld}(757)
           │             ├─{mysqld}(761)
           │             ├─{mysqld}(762)
           │             ├─{mysqld}(763)
           │             ├─{mysqld}(764)
           │             ├─{mysqld}(765)
           │             ├─{mysqld}(766)
           │             ├─{mysqld}(767)
           │             ├─{mysqld}(768)
           │             ├─{mysqld}(770)
           │             ├─{mysqld}(771)
           │             ├─{mysqld}(778)
           │             ├─{mysqld}(779)
           │             ├─{mysqld}(783)
           │             ├─{mysqld}(790)
           │             ├─{mysqld}(792)
           │             ├─{mysqld}(797)
           │             └─{mysqld}(798)
           ├─networkd-dispat(386)
           ├─nginx(567)───nginx(568)
           ├─pdns_server(773)─┬─{pdns_server}(780)
           │                  ├─{pdns_server}(781)
           │                  ├─{pdns_server}(782)
           │                  ├─{pdns_server}(784)
           │                  ├─{pdns_server}(785)
           │                  ├─{pdns_server}(786)
           │                  ├─{pdns_server}(788)
           │                  ├─{pdns_server}(789)
           │                  └─{pdns_server}(791)
           ├─polkitd(451)─┬─{polkitd}(473)
           │              └─{polkitd}(475)
           ├─rsyslogd(390)─┬─{rsyslogd}(426)
           │               ├─{rsyslogd}(427)
           │               └─{rsyslogd}(428)
           ├─snapd(1064)─┬─{snapd}(1071)
           │             ├─{snapd}(1072)
           │             ├─{snapd}(1073)
           │             ├─{snapd}(1074)
           │             ├─{snapd}(1078)
           │             ├─{snapd}(1079)
           │             ├─{snapd}(1230)
           │             ├─{snapd}(1231)
           │             └─{snapd}(1235)
           ├─sshd(565)───sshd(826)───sshd(925)───bash(926)───sudo(937)───sudo(938)───bash(939)───pstree(2903)
           ├─systemd(843)───(sd-pam)(844)
           ├─systemd-journal(112)
           ├─systemd-logind(397)
           ├─systemd-network(323)
           ├─systemd-resolve(325)
           ├─systemd-udevd(154)
           └─unattended-upgr(439)───{unattended-upgr}(484)
```
</div></details>

## Linuxの起動プロセス
電源を入れてからの起動プロセスは以下のようになります。
この`init`のところが`systemd`に該当します。

![スクリーンショット 2024-02-27 23.53.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849988/b150571f-def5-4454-8cb7-b0b188a84e1a.png)

## unit
`systemd`において、各種のリソースは`unit`として管理されます。
`unit`の状態を切り替えたり、`unit`間の依存関係を計算したりすることによって、リソースを管理することができます。
ここでいうリソースとは、「サーバーの管理」や「ネットワーク接続要求の管理」や「ファイルマウントシステムの管理」などさまざまです。

`unit`には複数のタイプが存在しており、現在では11種類あります。

https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html

起動中のLinuxにおいて、現在`systemd`が扱っている`unit`の一覧は以下のコマンドで確認することができます。
```sh
$ systemctl list-units
```

### unitの状態
`unit`は状態をもち、基本的には`active`もしくは`inactive`です。
中間状態として`activating`、`deactivating`, `reloading`があります。
unitの状態は`systemctl status`で確認することができます。
```sh
$ systemctl status UNIT_NAME

# ex.
$ systemctl status isupipe-ruby.service 
● isupipe-ruby.service - isupipe-ruby
     Loaded: loaded (/etc/systemd/system/isupipe-ruby.service; enabled; vendor preset: enabled)
     Active: active (running) since Tue 2024-02-27 18:29:30 UTC; 4min 45s ago
   Main PID: 2793 (bundle)
      Tasks: 57 (limit: 1121)
     Memory: 203.5M
        CPU: 2.000s
     CGroup: /system.slice/isupipe-ruby.service
             ├─2793 "puma 6.4.0 (tcp://0.0.0.0:8080) [ruby]" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" >
             ├─2794 "puma: cluster worker 0: 2793 [ruby]" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" >
             ├─2795 "puma: cluster worker 1: 2793 [ruby]" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" >
             ├─2796 "puma: cluster worker 2: 2793 [ruby]" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" >
             ├─2797 "puma: cluster worker 3: 2793 [ruby]" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" >
             ├─2798 "puma: cluster worker 4: 2793 [ruby]" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" >
             ├─2799 "puma: cluster worker 5: 2793 [ruby]" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" >
             ├─2805 "puma: cluster worker 6: 2793 [ruby]" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" >
             └─2806 "puma: cluster worker 7: 2793 [ruby]" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" >
```

### Job
`systemd`において、状態を変更するために`unit`に対して行う操作を`Job`と呼んでいます。
`start`, `stop`, `restart`, `reload`があります。
(`systemctl start UNIT_NAME`, `systemctl stop UNIT_NAME`...etc) 

### unitの依存関係
`systemd`では`unit`間に依存関係を構築することができます。
アプリケーションサーバを立ち上げる際に、DBサーバが先に立ち上がっていて欲しいなどの依存関係などです。
特定の`unit`が、どのような依存関係を持っているのかは`systemctl list-dependencies`で確認することができます。
```sh
$ systemctl list-dependencies isupipe-ruby.service 
isupipe-ruby.service
● ├─-.mount
● ├─mysql.service
● ├─system.slice
● └─sysinit.target
●   ├─apparmor.service
●   ├─blk-availability.service
●   ├─dev-hugepages.mount
●   ├─dev-mqueue.mount
●   ├─finalrd.service
# ...
```

## unit file
`unit`は`unit file`という設定ファイルによって管理されます。

### unit fileの見方
例として、isucon13のRuby実装の`unit file`を見てみます。
`/etc/systemd/system/isupipe-ruby.service`にあり、こちらでは、Rubyのアプリケーションサーバ(puma)の`unit`が管理されています。

`systemctl cat`を使うと、特定の`unit`に対応する`unit file`を表示してくれるので便利です。

```sh
$ systemctl cat isupipe-ruby.service 
# /etc/systemd/system/isupipe-ruby.service
[Unit]
Description=isupipe-ruby
After=syslog.target
After=mysql.service
Requires=mysql.service

[Service]
WorkingDirectory=/home/isucon/webapp/ruby
Environment=RUBY_YJIT_ENABLE=1
EnvironmentFile=/home/isucon/env.sh

User=isucon
Group=isucon
ExecStart=/home/isucon/.x bundle exec puma --bind tcp://0.0.0.0:8080 --workers 8 --threads 0:8 --environment production
ExecStop=/bin/kill -s QUIT $MAINPID

Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
```
`[Unit]`, `[Service]`, `[Install]`はセクションで、各ディレクティブをグルーピングして整理するためのものです。

#### [Unit]
##### Description
`unit`に対してつける短いタイトル・ラベルです。
`systemctl status`を実行したときなどに表示されます。

##### Requires
`unit`の依存関係を示すためのディレクティブです。
対象の`unit`を有効化する場合、`Requires`に指定した`unit`も同時に有効化するようになります。

似たディレクティブに`Wants`がありますが、これは依存度の違いです。
`Wants`は、依存関係にあるunitの有効化が失敗しても対象のunitに影響はありませんが、`Requires`の場合、依存関係にあるunitの有効化が失敗すると対象のunitの有効化も失敗します。

`Wants`, `Require`は依存関係を指定するためのもので、順序関係を保証するものではありません。
次のAfterを指定することによって、順序関係を構築することができます。

##### After
`After`ディレクティブには、先に有効化されていて欲しいunitを指定します。
アプリケーションサーバを立ち上げる前に、dbサーバが立ち上がっていて欲しいので、この例では、`mysql.service`が指定されています。

#### [Service]
##### ExecStart
serviceがスタートした時(`systemctl start`の実行時)に実行されるコマンドです。
この例では、pumaのアプリケーションサーバを起動しています。

##### ExecStop
serviceを終了した時(`systemctl stop`の実行時)に実行されるコマンドです。

##### Restart
serviceがexitした時、killされた時、timeoutした時などにリスタートするかどうかを指定します。
各値と、いつリスタートするかの関係は以下になります。(`X`がリスタートするの意味)

```sh
┌─────────────────────────────┬────┬────────┬────────────┬────────────┬─────────────┬──────────┬─────────────┐
│Restart settings/Exit causes │ no │ always │ on-success │ on-failure │ on-abnormal │ on-abort │ on-watchdog │
├─────────────────────────────┼────┼────────┼────────────┼────────────┼─────────────┼──────────┼─────────────┤
│Clean exit code or signal    │    │ X      │ X          │            │             │          │             │
├─────────────────────────────┼────┼────────┼────────────┼────────────┼─────────────┼──────────┼─────────────┤
│Unclean exit code            │    │ X      │            │ X          │             │          │             │
├─────────────────────────────┼────┼────────┼────────────┼────────────┼─────────────┼──────────┼─────────────┤
│Unclean signal               │    │ X      │            │ X          │ X           │ X        │             │
├─────────────────────────────┼────┼────────┼────────────┼────────────┼─────────────┼──────────┼─────────────┤
│Timeout                      │    │ X      │            │ X          │ X           │          │             │
├─────────────────────────────┼────┼────────┼────────────┼────────────┼─────────────┼──────────┼─────────────┤
│Watchdog                     │    │ X      │            │ X          │ X           │          │ X           │
└─────────────────────────────┴────┴────────┴────────────┴────────────┴─────────────┴──────────┴─────────────┘
```
##### RestartSec
serviceをリスタートする際に、どれだけスリープを入れるかを指定します。

#### [Install]
##### WantedBy
`systemctl enable`を実行したときに、指定した`unit`の`.wants`ディレクトリにシンボリックリンクが貼られます。
```sh
$ sudo systemctl enable isupipe-ruby.service 
Created symlink /etc/systemd/system/multi-user.target.wants/isupipe-ruby.service → /etc/systemd/system/isupipe-ruby.service.

$ ls -l /etc/systemd/system/multi-user.target.wants/isupipe-ruby.service 
lrwxrwxrwx 1 root root 40 Feb 27 20:01 /etc/systemd/system/multi-user.target.wants/isupipe-ruby.service -> /etc/systemd/system/isupipe-ruby.service
```
`multi-user.target`はLinuxの起動時に実行される`unit`であるため、これに対して依存関係を持つことで、起動時にこの`unit`も自動で有効化されます。

### unit fileのpath
特定のpathに`unit file`を配置することによって`systemd`に認識させることができます。

- `/lib/systemd/system`
  - パッケージマネージャによってインストールされたユニット
- `/etc/systemd/system`
    - 管理者の操作によって作成されるユニット
- `/run/systemd/system`
    - 実行時に自動的に作成されるユニット

↑ の3箇所ありますが、直接編集するユニットは`/etc/systemd/system`に配置します。

## 参考
https://www.oreilly.co.jp/books/9784814400218/

https://tatsu-zine.com/books/superuser-linux-system

https://gihyo.jp/book/2024/978-4-297-13893-6

https://github.com/isucon/isucon13