systemdでExecStopが働かない

はじめに

とあるゲームのサーバーの起動・停止などをsystemdで制御していて、サーバーを停止する際には下記のようにコマンドを使用して停止していました。

systemctl stop hoge-server

ただ、停止した際にサーバーのデータが正常に保存されていないことに気付き、一応解決したお話です。

前提

OSはCentOS7.6を使用してます

/etc/systemd/systemの中に自作Unitであるhoge-server.serviceを置いていて、中身はこんな感じです。

[Unit]
Description=Hoge Server
After=network.target local-fs.target

[Service]
Type=forking
User=hoge
WorkingDirectory=/home/hoge/server-dir
ExecStart=/home/hoge/server-dir/start.sh
ExecStop=/home/hoge/server-dir/stop.sh # 今回の問題点

[Install]
WantedBy=multi-user.target

今回重要なのは10行目のこれで、サーバーを停止しようとすると停止用のスクリプトが走るワケです。

ExecStop=/home/hoge/server-dir/stop.sh

ちなみにスクリプトの中身の簡単な説明をすると
①サーバーデータの保存
②サーバーの停止

という順序で処理が行われます。

症状

$ systemctl stop hoge-server

上記のコマンドを実行するとスクリプトが走り、サーバーデータの保存とサーバーの停止処理が走ってほしいのに、処理が走らずにhoge-server.serviceに関するプロセスがKillされている

結果として、サーバーデータが正常に保存されていない(正常に停止されていない)という状況の陥ったわけです。

原因

通常、ExecStopで停止用スクリプトが指定されていると「スクリプトが走って、終わったら残ったサービスのプロセスをKillする」というように同期的?に処理が進んでいきます。

ただ、今回はスクリプトの処理が終わるのを待たずしてサービスのプロセスがKillされているワケですが、その理由を解説していきます。

screenコマンドを使用し、screen内でサーバーの実体を動かしていた

ざっくり説明すると今回問題となったサービスの仕様は「バックグラウンドで、非同期的にサーバーを起動していた」ということです。

そんで、systemd目線で考えるとsystemdさんはフォアグラウンドの状況しか見ません。つまり、バックグラウンドで何してようがsystemdさん的には関係がないってことです。

サーバーの実体はバックグラウンドで非同期的に起動されているので当然停止処理もバックグラウンドへ送られ、非同期的に処理が走っていきます。この際フォアグラウンド側では停止スクリプトが走り始めたと同時に(バックグラウンドでの停止処理が終わっていなくても)
「停止スクリプトの処理が全て終わった」といったような判定になります。

これで何が起きるかというと、
systemdはフォアグランドしか見ないのでバックグラウンドで処理がまだ終わってないのにも関わらず「停止スクリプトの処理が終了した」と誤認するような形でどんどんサービスの残存プロセスをKillしていってしまいます。

つまり、停止スクリプト目線で言うと「systemdさんまだ処理終わって無いからまってよ!」と言ってるのにsystemdさんは「いや、フォアグランドなんもしてないし、Killします」みたいな感じです。

解決方法

「え、じゃあどうせ停止スクリプトでプロセスは全て終了してくれるし、systemdにわざわざプロセスのKillしてもらう必要ないんじゃないか?」という結論に至りました。

実際に何をするかは簡単です。
.serviceファイルの記述に下記の1行を追加するだけで大丈夫でした。

KillMode=none

systemdにKillさせないためのオプションが用意されておりました。
実際に追加するとこんな感じ↓

[Unit]
Description=Hoge Server
After=network.target local-fs.target

[Service]
Type=forking
User=hoge
WorkingDirectory=/home/hoge/server-dir
ExecStart=/home/hoge/server-dir/start.sh
ExecStop=/home/hoge/server-dir/stop.sh
KillMode=none #追加

[Install]
WantedBy=multi-user.target

あとは設定を反映させて

systemctl daemon-reload
systemctl stop hoge-server

上記コマンドで正常にサーバーデータが保存されているか(残存プロセスがないか)を確認すれば大丈夫です。

あくまでこの解決方法は「停止スクリプトでサービスに関係するプロセスを全て終了できる」という前提なのでお気をつけください。

以上です。
ご覧いただきありがとうございました。