想定されるシチュエーション
Node.js
であればPM2などのサービスで簡単にデーモン化できますが、今回は Django
や Flask
などのPython Webフレームワークを使ったアプリ開発で、hypercorn
などのPython Webサーバーをデーモン化するための手順です。
想定しているサービス
- Hypercorn / Uvicorn / Gunicorn
- Tornado
基本的な流れ
- .systemファイル作成
- スタートアップ用のシェルスクリプト作成
- サービスの登録・起動
デーモン化
基本的な流れと、hypercorn
などのサーバーと若干違う部分がある Tornado
の設定に分けて記述します。
.systemファイル
/etc/systemd/system
に <YOUR_SERVICE>.service
というファイルを以下の要領で作成します。要所の説明はコメントとして記述しています。また、設定は必要に応じて変更してください。
[Unit]
# 何のサービスなのか分かるように記述
Description = <ABOUT_YOUR_SERVICE>
[Service]
# サーバーがあるディレクトリへのフルパス
WorkingDirectory=<PATH_TO_WORKING_DIR>
# サーバー起動用シェルへのフルパス
ExecStart="<PATH_TO_SHELL_SCRIPT>"
Restart=always
RestartSec=3s # これを省略するとリトライが早すぎてエラーになる
Type=simple
# サービスを起動するユーザーとグループ名
# root権限のあるグループは避ける
User=<USER_NAME>
Group=<GROUP_NAME>
[Install]
WantedBy=multi-user.target
保存したら忘れずにシステムデーモンをリロードしてください。
$ sudo systemctl daemon-reload
シェルスクリプトの作成
Pythonの場合は仮想環境を構築して開発している場合が多いので、.systemファイルに直接起動コマンドを記述しても、仮想環境が反映されないため、エラーで起動しません。これを回避するため、起動用のシェルスクリプトを作成して対応します。記述方法は簡単で、基本は以下のような記述になります。( app.py
の中の app
で定義したアプリケーションを起動)
#!/usr/bin/bash
python hypercorn app:app
python hypercorn app:app
の部分は --reload
など適宜変更して必要な起動コマンドにしてください。
ただ、このままでは複数のサービスをポートを分けて起動している場合など、色々と使いにくいので、メンテナンスを考えて、例えば以下のようなスクリプトにしていきます。
#!/usr/bin/bash
# アプリのホームをフルパスで指定
# `pwd`でカレントディレクトリ
HOME_DIR="<PATH_TO_HOME_DIR>"
# hypercornなどへのHOME_DIRからのパス
# $HOME_DIRで参照
SERVER="<PATH_TO_SERVER_APP>"
# アプリケーションの指定()
APP="app:app"
# バインド情報
HOST_NAME="localhost"
PORT=8000
# その他変数は適宜設定
WORKERS=2
# 起動コマンド
# この例では hypercorn x 2 で localhost:8000 & リロードオプションで起動
echo "Starting <YOUR_APP>..."
$API_HOME$SERVER -w2 $HOME_DIR$APP -w $WORKERS -b $HOST_NAME:$PORT --reload
この例ではワーカー数を2に設定していますが、一つ落ちた場合でも、デーモンが再起動するまで3秒のスリープを取ってあるので、サービスが停止しないような設定になっています。ワーカーの数が多ければ多いほどサービスは停止しにくくなりますが、起動数に比例してメモリの消費が増えてきます。
システムの起動と登録
シェルスクリプトが完成したら、ユーザーとグループに対して実行の許可を与えておきます。今回は0750にしてありますが、ここは適宜調整してください。
$ chmod 0750 ./server.sh
次に、システムデーモンから起動するかどうかを確かめます。
$ sudo systemctl start <YOUR_SERVICE>
$ systemctl status <YOUR_SERVICE>
これでデーモンの起動や停止は systemctl [start|stop|restart] <YOUR_SERVICE>
で管理することができます。起動が確認できたら、システムを登録して自動的に起動するように設定して完了です!
$ sudo systemctl enable <YOUR_SERVICE>
システムの自動起動を解除したい場合は以下のコマンドで解除できます。
$ sudo systemctl disable <YOUR_SERVICE>
Tornado
Hypercorn/Uvicorn/Gunicorn
に一手間かかるので、Tornado
のリロード設定もついでにここで書いておきます。基本的にはプログラムの中の tornado.web.Application
に autoreload=True
を設定して、--autoreload
オプションをつけてサーバーを起動することになります。 実際には debug=True
も同時に設定することが多いと思いますので、まずは以下の感じで app.py
を記述します。
...中略...
app = tornado.web.Application(
[
(r"/", MainHandler),
...中略...
],
debug=True,
autoreload=True,
)
...中略...
リロードを有効にするためには、以下のようにしてこのアプリを起動する必要があります。
python -m tornado.autoreload app.py
デーモン化では、これに応じたシェルスクリプトを書くことになりますが、ポートの設定などは以下のようにプログラムの中にコーディングしておきます。(hypercorn
などでも同様の設定も可能です)
if __name__ == "__main__":
_port = <PORT_NUMBER>
print("\nStarting up <YOUR_APP>...")
if app.listen(_port):
print(f"Started at PORT {_port}!\n")
tornado.ioloop.IOLoop.instance().start()
else:
print("Failed startup...\n")
この例では、localhost:<PORT_NUMBER>
で起動起動する設定になっています。このプログラムをデーモンとして起動するシェルスクリプトは、例えば以下のように書くことができます。
#!/usr/bin/bash
# Pythonへのフルパス
PYTHON_BIN=<PATH_TO_PYTHON>
# アプリケーションへのパス(シェルスクリプトからの相対パスでOK)
APP_NAME=<PATH_TO_APP>
# 起動コマンド
$PYTHON -m tornado.autoreload $APP_NAME
ファイルが揃ったら、他と同じように実行権限を付与して、システムデーモンで起動と登録を行って完了です!