らくがきちょう

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

Python + responder + gunicorn + systemd + Nginx で Web アプリを起動する

以前に Python + falcon + gunicorn + systemd でシンプルな Web アプリを起動する というメモを書きました。 最近は responder が流行りらしいので、Python + responder + gunicorn + systemd + nginx 構成で「responder アプリケーションをデーモン化する」手順をメモしておきます。

今回の環境

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

  • CentOS8
  • pyenv をシステム全体にインストール済み
  • Python 3.8.1

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

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

pip install gunicorn responder

アプリケーションの配置

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

mkdir /opt/hello
cd /opt/hello

アプリケーションとして /opt/hello/app.py を以下の内容で新規作成しました。

cat << EOF > /opt/hello/app.py
import responder

api = responder.API()

@api.route("/")
async def hello(req, resp):
    resp.text = "hello, world!"

if __name__ == '__main__':
    api.run()
EOF

アプリケーション実行用ユーザの作成

セキュリティを確保する為、このサービスは root ユーザでは無く、hello という専用ユーザで動作させることにします。 hello というシステムユーザを作成します。

groupadd -r hello
useradd -g hello -s /sbin/nologin -r hello

今回の環境では UID / GID は以下になりました。

# id hello
uid=994(hello) gid=991(hello) groups=991(hello)

gunicorn の設定

今回は下記の内容で gunicorn 用の設定ファイルを用意します。 パラメータは環境に合わせて調整します。 responder は ASGI なのでワーカクラスには uvicorn.workers.UvicornWorker を指定します。

cat << EOF > /opt/hello/settings.py
worker_class = 'uvicorn.workers.UvicornWorker'
EOF

gunicorn を systemd 経由で起動する

まず先にこのサービスを識別する PID ファイルを作成しておきます。

mkdir /run/hello/
touch /run/hello/pid
chown -R hello:hello /run/hello/

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

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

[Service]
Type=notify
User=hello
Group=hello
RuntimeDirectory=gunicorn
WorkingDirectory=/opt/hello
ExecStart=/usr/local/pyenv/shims/gunicorn app:api --config settings.py
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true

[Install]
WantedBy=multi-user.target
EOF

Nginx と gunicorn 間は UNIX ドメインソケットで通信させますので、ソケットファイルも用意しておきます。 www-data は Nginx インストール時に作成されるユーザです (この時点ではまだ Nginx をインストールしていない為、システム上に www-data ユーザは存在しません)。

cat << EOF > /etc/systemd/system/hello.socket
[Unit]
Description=hello socket

[Socket]
ListenStream=/run/hello.sock
User=www-data

[Install]
WantedBy=sockets.target
EOF

ソケットファイルを有効化しておきます。

systemctl enable --now hello.socket

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

systemctl daemon-reload

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

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

設定ファイルに間違いが無ければ gunicorn が起動するはずです。

Nginx のインストール

CentOS8 標準リポジトリ上の Nginx はバージョンが古いので、Nginx 公式リポジトリからインストールします。 Nginx リポジトリを追加します。

cat << EOF > /etc/yum.repos.d/nginx.repo
[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/mainline/centos/\$releasever/\$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
EOF

インストールします。 AppStream が優先されないよう、AppStream の無効化を明示的に指定してインストールします。

dnf --disablerepo=AppStream -y install nginx

Nginx と gunicorn 間の通信設定

Nginx と gunicorn 間は UNIX ドメインソケットで通信させます。 今回は以下のように設定しました。

cat << EOF > /etc/nginx/conf.d/default.conf
server {
    listen 80;
    server_name 127.0.0.1;

    access_log /var/log/nginx/access.log main;

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

Nginx の起動

後は Nginx の起動&自動起動をすれば完了です。

systemctl enable nginx
systemctl start nginx

接続テスト

ローカルから接続テストを実行してみます。

# curl http://localhost/
hello, world!

問題なければリモートからブラウザで接続テストしてみます。

参考

/etc/nginx/conf.d/default.conf

server {
    listen       80;
    server_name  localhost;

    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}