gunicornをsystemdで動かした

gunicorn(19.9)をsystemdで動かした場合に色々と嵌ったのでメモ。

docs.gunicorn.org

https://github.com/benoitc/gunicorn/blob/master/docs/source/deploy.rst#systemd

最終的に動いた設定

/etc/systemd/system/app.socket:

[Unit]
Description=gunicorn app socket

[Socket]
ListenStream=/run/app.sock
User={{ app_user }}
Group={{ app_group }}

[Install]
WantedBy=sockets.target

/etc/systemd/system/app.service:

[Unit]
Description=gunicorn app service
Requires=app.socket
After=network.target

[Service]
PIDFile=/run/gunicorn/app.pid
RuntimeDirectory=gunicorn
User={{ app_user }}
Group={{ app_group }}
ExecStart=/path/to/venv/bin/gunicorn \
        --workers 2 \
        --pid /run/gunicorn/app.pid \
        --access-logfile /var/log/app/access.log \
        --error-logfile /var/log/app/error.log \
        --capture-output \
        --log-level info \
        app.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target

どこにはまったかのか

まず公式のドキュメント通りに動かなかった。なんかソケットが消えてしまう。

https://github.com/benoitc/gunicorn/issues/1524

を見る感じドキュメントを更新したとのことでmasterのドキュメントを参考にしてみた。

これが良くなかったんだけども。

こんな感じにpidの指定はいらないのか−と思ってたりType=notifyってなに?🤔と思いつつ設定してみた。

[Unit]
Description=gunicorn app service
Requires=app.socket
After=network.target

[Service]
Type=notify
User={{ app_user }}
Group={{ app_group }}
ExecStart=/path/to/venv/bin/gunicorn \
        --workers 2 \
        --access-logfile /var/log/app/access.log \
        --error-logfile /var/log/app/error.log \
        --capture-output \
        --log-level info \
        app.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStartSec=5
PrivateTmp=true

[Install]
WantedBy=multi-user.target

アプリは立ち上がるんだけどもapp.serviceのステータスがloadedのままで90秒で強制的にプロセスが終了。

https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/7/html/system_administrators_guide/sect-Managing_Services_with_systemd-Unit_Files#Overriding_Unit_Mod-Change-timout-limit

なんで終了されるのかは、Type=notifyの場合はコード側で起動した事を知らせる必要があるみたいなんだけど、そもそも19.9とmasterではコードが異なってたので19.9ではその処理が入って無かった。

https://github.com/benoitc/gunicorn/blob/19.9.0/gunicorn/systemd.py

https://github.com/benoitc/gunicorn/blob/master/gunicorn/systemd.py

90秒で強制的に終了するというのは

1.リクエストが来る=>2.起動してなければ起動する=>1から90秒立つ=>落ちる=>1に戻る

なのでリクエストが来るたびに起動はするので動いてるようには見えるんだけど、処理のかかるリクエストだと途中で処理が終わって404が返ってきた。

初期データのcsvインポートが大量にあったので気づいたんだけども。

最終的にはapp.serviceをstopしてもリクエストを投げたタイミングで起動してactiveになって強制終了されることはなくなった。

結果として

  • 19.9ではpidの指定が必要 gunicornにbindの指定は不要
  • masterブランチではpidの指定は不要 gunicornにbindの指定は不要(多分)

に至ったんだけどもgunicorn次はいつリリースされるんだろう...

nginx

    location / {
        proxy_pass http://unix:/run/app.sock;
    }

systemdの知識が少し深まったけどsocketの知識も深めないと...