systemdのユニットファイルを作成してみる。
rcスクリプト育ちだったのでsystemctl
コマンドは慣れるのに時間はかかったといえさすがにもう息を吸うように使うようになったけど、systemdのユニットファイル周りは基本的な部分があまりわかってないのでお試し。
記載については、Red Hatのドキュメントを確認すれば必要な情報は揃う。
本エントリ中の作業ホストは意表をついてUbuntu 20.04で確認。
root@ubuntu-node:~# cat /etc/lsb-release DISTRIB_ID=Ubuntu DISTRIB_RELEASE=20.04 DISTRIB_CODENAME=focal DISTRIB_DESCRIPTION="Ubuntu 20.04.2 LTS"
お題
daemonで動く適当なアプリケーションとして、以下で作ったPerlのソケットサーバーを使う。
ベースのソースはこちら。
実行するとTCP/8080でListenしてなんちゃってHTTPを喋るサーバーとして動作する。
コードの修正
systemdで停止を行う際に使用するための、実行プロセスのPIDをファイル出力するようにする。
open W, "> /var/run/sockserv.pid" or die; print W $$; close W;
Perlでは$$
でプロセスIDを取得できる。
この値をファイル出力するコードを、for()
で接続待ちのループに入る前に追加。
(fork
しないスクリプトなので、実はこのファイルは不要だった)
ユニットファイルの作成
コアな部分の最低限必要そうな記述を行うと以下の通り。
[Unit] Description=Perl Socket Server Sample After=network.target [Service] Type=simple PIDFile=/var/run/sockserv.pid ExecStart=/usr/local/tcp-probe-example/src/sockserv.pl [Install] WantedBy=multi-user.target
パラメタ
man systemd.directives
で(どのman
を見ればいいかが)確認できる。
Description
概要。
systemctl status hogehoge
とかの出力に使用される。
After
依存するユニット。
ここで指定したユニットがactiveになってから起動される。
ネットワークを使うプログラムであればnetwork
を指定しておけばよさげ。
Type
今回のお題のスクリプトのように、実行するとフォアグラウンドで動作するスクリプトであればsimple
を指定する。
スクリプト自体がバックグラウンドで動作するようなものはforking
を使う。
詳しくは表10.10 [Service] セクションの重要なオプションを参照。
PIDFile
ユニットファイルで指定するプログラムが出力するPIDが記録されるファイルのパス。
停止時にこのPIDに対してkill
する。
なんだけど、fork
しない1プロセスで処理するスクリプトだとドキュメント見る限り不要っぽい。
ExecStart
プログラムの起動コマンド。
引数がある場合もここに全部書ける模様。
WantedBy
WantedBy
というか、[Install]
セクションの設定は、systemctl enable
を使った時の動作を定義。
WantedBy=multi-user.target
だと/etc/systemd/system/multi-user.target.wants/
以下にsymlinkを作成する動作になる。
ユニットファイルの作成場所
どこにファイルを作ればいいかはman systemd
で確認できる。
System unit directories
The systemd system manager reads unit configuration from various directories. Packages that want to install unit files shall place them in the directory returned by pkg-config systemd --variable=systemdsystemunitdir. Other directories checked are /usr/local/lib/systemd/system and /usr/lib/systemd/system. User configuration always takes precedence. pkg-config systemd --variable=systemdsystemconfdir returns the path of the system configuration directory. Packages should alter the content of these directories only with the enable and disable commands of the systemctl(1) tool. Full list of directories is provided in systemd.unit(5).
平たく言うと、「どこのディレクトリに置くと有効かはman systemd.unit
で確認できる。主に使うのは/usr/lib/systemd/system
と/usr/local/lib/systemd/system
」とのこと(超意訳)。
ということでパッケージインストールするやつじゃなくて自前のプログラムなので、/usr/local/lib/systemd/system
以下に作ってみる。
# mkdir -p /usr/local/lib/systemd/system # vi /usr/local/lib/systemd/system/sockserv.service
ここに前述の内容のユニットファイルを作成。
今回はsockserv
という名前のサービスとして登録してみるということで、sockserv.service
というファイル名でファイル作成。
systemctl
status
root@ubuntu-node:~# systemctl status sockserv ● sockserv.service - Perl Socket Server Sample Loaded: loaded (/usr/local/lib/systemd/system/sockserv.service; disabled; vendor preset: enabled) Active: inactive (dead)
この時点でstatus
で内容確認できる。
start
root@ubuntu-node:~# systemctl start sockserv root@ubuntu-node:~# echo $? 0 root@ubuntu-node:~# systemctl status sockserv ● sockserv.service - Perl Socket Server Sample Loaded: loaded (/usr/local/lib/systemd/system/sockserv.service; disabled; vendor preset: enabled) Active: failed (Result: exit-code) since Sat 2021-04-10 11:39:21 UTC; 6s ago Process: 198244 ExecStart=/usr/local/tcp-probe-example/src/sockserv.pl (code=exited, status=203/EXEC) Main PID: 198244 (code=exited, status=203/EXEC) Apr 10 11:39:21 ubuntu-node systemd[1]: Started Perl Socket Server Sample. Apr 10 11:39:21 ubuntu-node systemd[1]: sockserv.service: Main process exited, code=exited, status=203/EXEC Apr 10 11:39:21 ubuntu-node systemd[1]: sockserv.service: Failed with result 'exit-code'.
あら?
journalctl -xe
で確認すると以下の通り。
スクリプトをchmod 755
してなかった笑
Apr 10 11:39:21 ubuntu-node systemd[1]: Started Perl Socket Server Sample. -- Subject: A start job for unit sockserv.service has finished successfully -- Defined-By: systemd -- Support: http://www.ubuntu.com/support -- -- A start job for unit sockserv.service has finished successfully. -- -- The job identifier is 7009. Apr 10 11:39:21 ubuntu-node systemd[198244]: sockserv.service: Failed to execute command: Permission denied Apr 10 11:39:21 ubuntu-node systemd[198244]: sockserv.service: Failed at step EXEC spawning /usr/local/tcp-probe-example/src/sockserv.pl: Permission denied
修正
root@ubuntu-node:~# chmod 755 /usr/local/tcp-probe-example/src/sockserv.pl
再実行
root@ubuntu-node:~# systemctl start sockserv root@ubuntu-node:~# echo $? 0
root@ubuntu-node:~# systemctl status sockserv ● sockserv.service - Perl Socket Server Sample Loaded: loaded (/usr/local/lib/systemd/system/sockserv.service; disabled; vendor preset: enabled) Active: active (running) since Sat 2021-04-10 11:42:29 UTC; 17s ago Main PID: 198358 (sockserv.pl) Tasks: 1 (limit: 4619) Memory: 3.0M CGroup: /system.slice/sockserv.service └─198358 /usr/bin/perl /usr/local/tcp-probe-example/src/sockserv.pl Apr 10 11:42:29 ubuntu-node systemd[1]: Started Perl Socket Server Sample. Apr 10 11:42:29 ubuntu-node sockserv.pl[198358]: [DBG] server listening ... 0.0.0.0:8080
root@ubuntu-node:~# curl http://localhost:8080 Running Socket Server.
root@ubuntu-node:~# cat /var/run/sockserv.pid 198358
systemctl stop sockserv
することでこのプロセスIDを見てkill
してくれる。
停止時はこのファイルは削除される。
ちなみにPIDFile
の指定が無い場合でも、systemctl
が把握してるPIDは面倒みてくれるっぽくてちゃんと停止する模様。
今回はsimple
なので不要だったけど、forking
タイプだと、無いとkill
されないかも。(未確認)
enable
デフォルトでは以下の通り。
root@ubuntu-node:~# systemctl is-enabled sockserv disabled
OSブート時にも起動するにはenable
を使う。
root@ubuntu-node:~# systemctl enable sockserv Created symlink /etc/systemd/system/multi-user.target.wants/sockserv.service → /usr/local/lib/systemd/system/sockserv.service.
この通り、/etc/systemd/system/multi-user.target.wants/sockserv.service
にsymlinkが作成される。
daemon-reload
動作に影響しないけど、ユニットファイルのDescription
の内容をちょっと変更してみる。
(末尾に!
追加)
[Unit] Description=Perl Socket Server Sample!
この状態で、systemctl status sockserv
を実行すると、
root@ubuntu-node:~# systemctl status sockserv Warning: The unit file, source configuration file or drop-ins of sockserv.service changed on disk. Run 'systemctl daemon-reload' to reload units. ● sockserv.service - Perl Socket Server Sample Loaded: loaded (/usr/local/lib/systemd/system/sockserv.service; enabled; vendor preset: enabled) : :
このように「ディスク上のunitファイルの内容が更新されとるで」と、systemctl daemon-reload
するように警告が表示さる。
root@ubuntu-node:~# systemctl daemon-reload root@ubuntu-node:~# systemctl status sockserv ● sockserv.service - Perl Socket Server Sample! Loaded: loaded (/usr/local/lib/systemd/system/sockserv.service; enabled; vendor preset: enabled) : :
更新されました。
ということで、自前のプログラムを自作のユニットファイルでdaemonとして動作させることができた。
というか実は、このユニットファイルのパスがどこかを知れたのが今回の最大の収穫だった。
今までなんとなく/etc/systemd/system/multi-user.target.wants/
にあるファイルをチェックしてたけど、これはenable
になってる分だけで、実体は(基本的に)/lib/systemd/system/
にあって、他の場所もman systemd.unit
で検索パスのリストを確認できる。