工作と競馬2

電子工作、プログラミング、木工といった工作の記録記事、競馬に関する考察記事を掲載するブログ

システムシャットダウンの際、Pythonアプリのデーモンを安全に終了する

概要

Pythonアプリのデーモンをシャットダウン時に安全に終了するためのメモ。

背景と目的

Pythonアプリのデーモンを、システムシャットダウン時に安全に終了するための基本的な実装方法を確認する。


詳細

0. 環境


1. 実装

Python標準モジュールのsignalを使うことで、システムシャットダウン時に、デーモンにSIGNALが送られたのを、アプリが受信できる。

以下、具体的な実装をメモ。

1.1 Pythonアプリ

  • SIGTERMを受け取ったら、handlerという関数が呼ばれるようにする。
  • SIGTERM受信したら、フラグを立てる。
  • while ループでフラグを調べて、立っていたら抜ける。→アプリが終了

test_signal.py

# coding: utf-8

import signal
import time

x = {
    "end": False,
    "signum": None,
    "frame": None
}

def handler(signum, frame):
    x["end"] = True # 終了させる
    x["signum"] = signum
    x["frame"] = frame

print("test_signal start!")

signal.signal(signal.SIGTERM, handler)

print("test_signal loop!")

while True:
    time.sleep(1)
    if x["end"]:
        print("test_signal handler! {},{}".format(x["signum"], x["frame"]))
        break

1.2 呼び出し用シェルスクリプト

Pythonアプリを直接デーモンとして実行してもいいが、起動用シェルスクリプトをかませることが多いので、試しにシェルスクリプトから呼ぶ。

test_signal.sh

#!/bin/bash

HERE=$(cd $(dirname $0); pwd)

echo "test_signal sh start"

python3 $HERE/test_signal.py

echo "test_signal sh end"

1.3 サービスファイル

test_signal.shが呼ばれるようにしておく。

test_signal.service

[Unit]
Description=test_signal
After=multi-user.target

[Service]
ExecStart=/bin/bash /path/to/test_signal.sh
Restart=on-failure
Type=simple

[Install]
WantedBy=multi-user.target

配置してデーモン有効化、起動。

cp ./test_signal.service /etc/systemd/system
systemctl daemon-reload
systemctl enable test_signal
systemctl start test_signal


2. 動作確認

2.1 手動で止めてみる

systemctl stop test_signal

ログを見ると、以下の感じでちゃんと止まった。 shから呼ばれたPythonアプリでも、SIGTERMを受信して、Pythonアプリで処理できている。

Mar 10 07:14:27 ****** bash[2048]: start!
Mar 10 07:14:27 ****** bash[2048]: handler! 15,<frame at 0x7690fc30, file '/r
Mar 10 07:14:27 ****** bash[2048]: terminate!
Mar 10 07:14:27 ****** systemd[1]: Stopped test_signal Application.

2.2 システムシャットダウン

シャットダウンしてみた。再起動後、syslogを確認したところ、以下。ちゃんと止まった模様。 ただし、なぜかハンドラ関数が2回呼ばれる(SIGTERMを2回受信?)ようなので、念のため、ハンドラ関数内で2回実行されると困るものはやらないようにするほうがいいかもしれない。今回のように、メインループを抜けるためのフラグを立てるだけみたいな単純なものであれば問題ないだろう。

Mar 10 07:40:07 ********** systemd[1]: Started test_signal Application.
Mar 10 07:40:07 ********** bash[2550]: test_signal sh start
Mar 10 07:40:13 ********** systemd[1]: Stopping test_signal Application...
Mar 10 07:40:14 ********** bash[2550]: test_signal start!
Mar 10 07:40:14 ********** bash[2550]: test_signal loop!
Mar 10 07:40:14 ********** bash[2550]: test_signal handler! 15,<frame at 0x769bbc30, file '/root/test/test_signal.py', line 28, code <module>>
Mar 10 07:40:14 ********** bash[2550]: test_signal end!
Mar 10 07:40:14 ********** systemd[1]: Stopped test_signal Application.


まとめと今後の課題

とりあえず、安全にデーモンを終了させられそう。