らくがきちょう

なんとなく ~所属組織/団体とは無関係であり、個人の見解です~

Python + falcon + gunicorn + systemd でシンプルな Web アプリを起動する

Pythonfalcongunicorn を使い、シンプルな Web アプリケーションを作り、更にそれを systemd に登録するまでの手順をメモしておきます。

今回の環境

今回は以下の環境を用いました。

  • CentOS8
  • pyenv 環境
  • Python 3.8.0

ライブラリのインストール

予め falcon と gunicorn を pip でインストールしておきます。

pip install falcon gunicorn

falcon アプリケーションの配置

アプリケーションを配置するディレクトリを作成します。 今回は /opt/hello としました。

mkdir -p /opt/hello/
cd /opt/hello/

実際のアプリケーションは以下のように、最低限としました。

cat << EOF > /opt/hello/hello.py
import falcon
import json

class Hello:
    def on_get(self, req, resp):
        resp.body = "Hello, World!"

app = falcon.API()
app.add_route('/', Hello())
EOF

gunicorn の設定

今回は下記の内容で gunicorn 用の設定ファイルを用意します。

cat << EOF > /opt/hello/settings.py
import multiprocessing

bind = '0.0.0.0:5000'
worker_class = 'sync'
workers = 3
max_requests = 100
max_requests_jitter = 5
EOF

bind

このアプリケーションが Listen するアドレス / ポート番号を指定します。

worker_class

gunicorn のワーカークラスには以下の 5 種類を指定出来ます。

  1. sync
  2. gevent
  3. eventlet
  4. tornado
  5. gthread

今回はシンプルに sync を指定しました。

workers

ワーカーの同時起動数を指定します。 ワーカークラスが sync の場合、ワーカー数は「CPU 数 x 2 + 1」が推奨だそうです。 今回は 1CPU の貧弱な環境です… その為、ワーカー数は 3 としました。

# grep processor /proc/cpuinfo | wc -l
1

max_requests

ワーカーが処理した回数がこの値に達した場合、該当のワーカーを再起動します。

max_requests_jitter

max_requests しか指定していないと、全てのワーカーが近いタイミングで再起動する可能性が高くなってしまいます。 この値を指定しておくことで「ゼロ ~ 指定値」の値を max_requests に加算したタイミングでワーカーが再起動され、結果的にワーカーごとの再起動タイミングがずれやすくなります。

テスト起動

作成した falcon アプリケーションを gunicorn 経由で起動するには以下のように実行します。 今回は「hello.py」ファイルの「app」がエントリーポイントになる為、gunicorn には hello:app と指定します。 今回は settings.py というファイルに設定項目を用意してありますが、gunicorn の引数で起動パラメータを渡すことも可能です。

gunicorn hello:app --config settings.py

実際に起動してみると以下のようになります。 設定ファイルで指定している通り、ワーカーが 3 つ起動していることが分かります。

# gunicorn hello:app --config settings.py
[2019-12-17 21:49:37 +0900] [3788] [INFO] Starting gunicorn 20.0.4
[2019-12-17 21:49:37 +0900] [3788] [INFO] Listening at: http://0.0.0.0:5000 (3788)
[2019-12-17 21:49:37 +0900] [3788] [INFO] Using worker: sync
[2019-12-17 21:49:37 +0900] [3834] [INFO] Booting worker with pid: 3834
[2019-12-17 21:49:37 +0900] [3835] [INFO] Booting worker with pid: 3835
[2019-12-17 21:49:37 +0900] [3836] [INFO] Booting worker with pid: 3836

同一サーバからアクセスしてみると期待した応答があるはずです。

# curl 127.0.0.1:5000
Hello, World!

systemd 経由で起動する

systemd 経由で起動出来るよう、設定ファイルを用意します。

cat << EOF > /etc/systemd/system/hello.service
[Unit]
Description = Hello World!
After = network.target

[Service]
PermissionsStartOnly = true
PIDFile = /run/hello/pid
User = root
Group = root
WorkingDirectory = /opt/hello
ExecStartPre = /bin/mkdir /run/hello
ExecStartPre = /bin/chown -R root:root /run/hello
ExecStart = /root/.pyenv/shims/gunicorn hello:app --pid /run/hello/pid --config settings.py
ExecReload = /bin/kill -s HUP $MAINPID
ExecStop = /bin/kill -s TERM $MAINPID
ExecStopPost = /bin/rm -rf /run/hello
PrivateTmp = true
Restart=always

[Install]
WantedBy = multi-user.target
EOF

systemd の設定を追加したので一度、設定を読み込み直します。

systemctl daemon-reload

後は状態を確認しながらサービスを起動します。

systemctl status hello.service
systemctl start hello.service
systemctl status hello.service

設定ファイルが間違いが無ければ以下のように systemd 経由で gunicorn が起動するはずです。

# systemctl status hello.service
● hello.service - Hello World!
   Loaded: loaded (/etc/systemd/system/hello.service; disabled; vendor preset: disabled)
   Active: inactive (dead)

# systemctl start hello.service

# systemctl status hello.service
● hello.service - Hello World!
   Loaded: loaded (/etc/systemd/system/hello.service; disabled; vendor preset: disabled)
   Active: active (running) since Tue 2019-12-17 22:05:34 JST; 5s ago
  Process: 6952 ExecStartPre=/bin/chown -R root:root /run/hello (code=exited, status=0/SUCCESS)
  Process: 6951 ExecStartPre=/bin/mkdir /run/hello (code=exited, status=0/SUCCESS)
 Main PID: 6954 (gunicorn)
    Tasks: 4 (limit: 12167)
   Memory: 37.7M
   CGroup: /system.slice/hello.service
           ├─6954 /root/.pyenv/versions/3.8.0/bin/python3.8 /root/.pyenv/versions/3.8.0/bin/gunicorn hello:app --pid /run/hello/pid --config >           ├─7041 /root/.pyenv/versions/3.8.0/bin/python3.8 /root/.pyenv/versions/3.8.0/bin/gunicorn hello:app --pid /run/hello/pid --config >           ├─7042 /root/.pyenv/versions/3.8.0/bin/python3.8 /root/.pyenv/versions/3.8.0/bin/gunicorn hello:app --pid /run/hello/pid --config >           └─7043 /root/.pyenv/versions/3.8.0/bin/python3.8 /root/.pyenv/versions/3.8.0/bin/gunicorn hello:app --pid /run/hello/pid --config >
Dec 17 22:05:34 172-020-000-153 systemd[1]: Starting Hello World!...
Dec 17 22:05:34 172-020-000-153 systemd[1]: Started Hello World!.
Dec 17 22:05:34 172-020-000-153 gunicorn[6954]: [2019-12-17 22:05:34 +0900] [6954] [INFO] Starting gunicorn 20.0.4
Dec 17 22:05:34 172-020-000-153 gunicorn[6954]: [2019-12-17 22:05:34 +0900] [6954] [INFO] Listening at: http://0.0.0.0:5000 (6954)
Dec 17 22:05:34 172-020-000-153 gunicorn[6954]: [2019-12-17 22:05:34 +0900] [6954] [INFO] Using worker: sync
Dec 17 22:05:34 172-020-000-153 gunicorn[6954]: [2019-12-17 22:05:34 +0900] [7041] [INFO] Booting worker with pid: 7041
Dec 17 22:05:34 172-020-000-153 gunicorn[6954]: [2019-12-17 22:05:34 +0900] [7042] [INFO] Booting worker with pid: 7042
Dec 17 22:05:34 172-020-000-153 gunicorn[6954]: [2019-12-17 22:05:34 +0900] [7043] [INFO] Booting worker with pid: 7043