Systemd でサーバを作る

Systemd は Linux 起動時にあるコマンドを自動実行する仕組みだが、どうにも良くわからないので、自分で簡単なサーバを作って動作を確かめた。

Systemd の公式ドキュメントは [Manual Pages](https://www.freedesktop.org/wiki/Software/systemd/#manualsanddocumentationforusersandadministrators) なので、分かりやすい情報を探すのに非常に苦労した。マニュアルを辿っていくには [systemd.unit (5)](https://www.freedesktop.org/software/systemd/man/systemd.unit.html) にある unit ファイルの書き方から始めてリンクを参考にするのが良いと思う。

## サービスの例

Systemd には system と user という二つのモードがある。この例では system モードで動くサービスを作る。こんな感じの簡単なタイムサーバを /usr/bin/timeserver.py に書く。ポート 50007 にアクセスすると時刻を返し、標準出力にログを出す。([python のマニュアル](https://docs.python.jp/2.7/library/socket.html#example) から改造)

```python
#!/usr/bin/env python
# Time server program

import socket
import datetime
import sys

HOST = ''                 # Symbolic name meaning all available interfaces
PORT = 50007              # Arbitrary non-privileged port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen(1)

while True:
    conn, addr = s.accept()
    now = datetime.datetime.now().isoformat()
    print('Connected by %s at %s\n' % (addr, now))
    conn.sendall(now.encode())
    conn.close()
```

Systemd では、シェルスクリプトの代わりに unit というファイルに起動時の設定を書いてゆく。Systemd は unit を [以下の優先順位](https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Unit%20File%20Load%20Path) で読み込む。

* /etc/systemd/system 一番優先順位が高いローカル設定
* /run/systemd/system
* /usr/lib/systemd/system rpm などが使うことを想定

そこで /etc/systemd/system/timeserver.service に timeserver.py を実行する設定を以下のように書く。

```
[Unit]
Description=Some Time server                        # おおまかな説明を書く。
After=network.target                                # この unit の前に開始になっておくべき unit。並行して実行して良いかの判断に使う。

[Service]
ExecStart=/usr/bin/python -u /usr/bin/timeserver.py # 実行コマンド。-u を付けないと stdout のバッファがきつい
Restart=always                                      # デーモン化のため何かエラーが起きても再起動するように設定。
StandardOutput=journal                              # 標準出力を journal というロガーに出力。
StandardError=journal                               # エラー出力を journal というロガーに出力。

[Install]
WantedBy=multi-user.target                          # Enable 時に multi-user.target の依存に設定する。
```

[systemctl](https://www.freedesktop.org/software/systemd/man/systemctl.html) コマンドでちゃんと読み込めているか確認。

```
# systemctl status timeserver.service
● timeserver.service - Some Time server
   Loaded: loaded (/etc/systemd/system/timeserver.service; disabled; vendor preset: enabled)
   Active: inactive (dead)
```

いくつかの備考。

* [network.target](https://www.freedesktop.org/wiki/Software/systemd/NetworkTarget/): 気休め? シャットダウン時にはこの unit が停止してからネットワークを停止する。
* [Restart](https://www.freedesktop.org/software/systemd/man/systemd.service.html#Restart=) を設定すると、kill しても自動的に復旧するように出来る。デーモンの作成に使える。
* StandardOutput で標準出力の記録先を選択出来る。この例のように `journal` を指定すると Systemd の Journal に記録される。ログの確認は `journalctl` を使う。ここで Python が標準出力をバッファリングしてログが表示されない事に気づかず時間を無駄にした。Python の場合 `-u` でバッファリングを避けると上手く動いた。
* もしも unit を書き換えた場合 [systemctl daemon-reload](https://www.freedesktop.org/software/systemd/man/systemctl.html#daemon-reload) で明示的に unit の再読込が必要なので注意。


起動

    # systemctl start timeserver.service

ログ出力

    # journalctl -f -u timeserver.service
    Aug 01 08:44:02 raspberrypi3 systemd[1]: Started Some Time server.
    Aug 01 08:44:07 raspberrypi3 python[6548]: Connected by ('10.31.0.15', 51267) at 2017-08-01T08:44:07.057612

## 依存関係による自動起動。Wants と Requires の違い。

システム起動時に Systemd は、[default.target](https://www.freedesktop.org/software/systemd/man/systemd.special.html#default.target) という特別な unit が依存している unit を再帰的に起動してゆく。普通 default.target は [multi-user.target](https://www.freedesktop.org/software/systemd/man/systemd.special.html#multi-user.target) のシンボリックリンクなので、自動起動を行いたい場合は multi-user.target によって依存される unit を作成すれば良いという事になる。この依存関係の作成には [systemctl enable](https://www.freedesktop.org/software/systemd/man/systemctl.html#enable%20NAME%E2%80%A6) が使われる。

`systemctl enable` コマンドは `[Install]` セクションに書かれたとおりに依存関係を作る。この場合 timeserver.service は multi-user.target に依存されるようになるので、multi-user.target が active の時に timeserver.service も active になる。

    # systemctl enable timeserver.service
    Created symlink /etc/systemd/system/multi-user.target.wants/timeserver.service → /etc/systemd/system/timeserver.service.

依存関係には Wants と Requires があるが、普通は Wants を使う。

* [Wants=](https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Wants=)
    * この unit が有効になると指定された unit も有効になる。
    * もし指定された unit が無効になっても、**この unit に影響が無い**。
* [Requires=](https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requires=)
    * この unit が有効になると指定された unit も有効になる。
    * もし指定された unit が無効になるとこの unit も無効になる。

依存関係の作成には unit ファイルに記述する方法と、シンボリックリンクを使う方法がある。`systemctl enable` は依存関係を作るためにシンボリックリンクを使う。

* foo.service ファイルと foo.service.wants/ フォルダがある時、このフォルダから symlink された unit file は foo.service の Wants= と同じように依存リストに登録される。
* foo.service ファイルと foo.service.requires/ フォルダがある時、このフォルダから symlink された unit file は foo.service の Requires= と同じように依存リストに登録される。

`systemctl disable` はシンボリックリンクを削除するので、自動起動をやめたい時に使う。

## System mode と User mode

紹介した System mode では、Systemd はシステムの起動と終了を管理する。[User mode](https://wiki.archlinuxjp.org/index.php/Systemd/%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC) を使うと、あるユーザが最初にセッションを開始した時から全てのセッションを終了した時まで動作するサービスを作成する事が出来る。[User mode では System mode と異なる位置に unit ファイルを置く](https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Unit%20File%20Load%20Path)。リストの上の方が高い優先順位を持つ。

* $XDG_CONFIG_HOME/systemd/user
* $HOME/.config/systemd/user
* /etc/systemd/user
* $XDG_RUNTIME_DIR/systemd/user
* /run/systemd/user
* $XDG_DATA_HOME/systemd/user
* $HOME/.local/share/systemd/user
* /usr/lib/systemd/user


systemctl に `--user` をつければ User mode のサービスを制御出来る。もしもあるはずのサービスの status が見つからないなーと思ったら `--user` を付けると見つかるかもしれない。例:

User mode で動く hoge.service の状態を見る。

    systemctl --user status hoge.service

User mode で動く hoge.service のログを表示。(ダサい事に --user --unit では動かない)

    journalctl --user-unit hoge.service

## その他の Unit ファイル

* [.path](https://www.freedesktop.org/software/systemd/man/systemd.path.html)
    * あるパスが存在する時にサービスを起動する。無い場合は起動を待つ。
    * デフォルトでは、foo.path は foo.service の起動を制御する。
* [.slice](https://www.freedesktop.org/software/systemd/man/systemd.slice.html)
    * [cgroup で管理するスコープを定義](https://www.freedesktop.org/wiki/Software/systemd/ControlGroupInterface/)
* [.socket](https://www.freedesktop.org/software/systemd/man/systemd.socket.html)
    * socket を使うサービスはこっちを使う? foo.socket は foo@.service と関連している。
    * inetd のような物らしい。
    * あとで調べる

## 便利なコマンド

```
systemd-cgls # cgroup 表示
systemctl # リスト表示
systemctl list-unit-files # unit ファイル表示
sudo systemctl start samba # 起動
sudo systemctl stop samba # 停止
sudo systemctl mask httpd # 起動自体をできなくする
sudo systemctl unmask httpd # マスク解除
systemctl list-dependencies # 依存関係を表示
journalctl # ログ表示
```

## Unit ファイルの基本性質

* foo.service ファイルと foo.service.d/ フォルダがある時、このフォルダ内の *.conf ファイルの設定は foo.service に追加される。
* getty@tty3.service というサービスが必要なのにファイルが存在しない場合、getty@.service ファイルから作られる (@ から . の間を省略したファイル名)

## 参考

* [systemd.unit (5)](https://www.freedesktop.org/software/systemd/man/systemd.unit.html)
* [systemd.service (5)](https://www.freedesktop.org/software/systemd/man/systemd.service.html)
* [How to Create and Run New Service Units in Systemd Using Shell Script](https://www.tecmint.com/create-new-service-units-in-systemd/)
* [はじめてのsystemdサービス管理ガイド](http://dev.classmethod.jp/cloud/aws/service-control-use-systemd/)
* [systemd/user](https://wiki.archlinuxjp.org/index.php/Systemd/%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC)
* [CentOS 7システム管理ガイド](https://books.google.co.jp/books?isbn=4798044911)
    * 読んでないが、Google Books でみると、Systemd について結構詳しく書かれてあるようだ。