Flask並みに簡単なのにモダンな開発スタイルも可能なフレームワークFastAPIを、本番を意識した構成で動かす方法を調べたので共有したいと思います。
構成に関する要件と討ち手
「本番を意識した構成」と大きく出てみたものの、やりたいことは以下2点です。
- マルチプロセス構成の管理をしたい 👉 gunicornでマルチプロセス管理
- SSLをオフロード&証明書を一元管理したい 👉 Nginxでリバースプロキシ
前者によりパフォーマンスや耐障害性を確保し、後者により運用しやすさやセキュリティを確保するようなイメージです。
Uvicornで起動(基本形)
ASGIサーバーとしてUvicornを使用してFastAPIをシングルプロセスで起動するまでの手順です。はじめにライブラリのインストール。
$ pip install fastapi uvicorn
インストールしたらファイルrun.py
を作成して以下の通りコードを記述してください。
from fastapi import FastAPI
app = FastAPI()
@app.get('/')
async def hello():
return {"text": "hello world!"}
そしたらサーバーを起動してみましょう。
$ uvicorn run:app
INFO: Started server process [13372]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
http://127.0.0.1:8000 でUvicornが起動したと出てきたのでブラウザでアクセスしてみると以下のようなJSONが返されるはずです。
{"text":"hello world!"}
稼働確認はこれで完了です。
マルチプロセスにする
Uvicorn自体でマルチプロセスの仕組みは持っているのですが、プロセスを監視して落ちたら再起動するといったような仕組みが無いみたいです。
Uvicorn provides a lightweight way to run multiple worker processes, for example --workers 4, but does not provide any process monitoring.
そこで、最もシンプルな方法として示されているGunicornをプロセスマネージャーとして利用する方法を採用してみたいと思います。
Gunicornのインストール
以下の通り。簡単。
$ pip install Gunicorn
マルチプロセスで起動
早速Gunicornを使ったマルチプロセス管理のもとで(UvicornをASGIとした)FastAPIアプリを起動します。以下の例ではワーカープロセス4つ、ポート1234番で起動しています。
$ gunicorn -w 4 -k uvicorn.workers.UvicornWorker run:app --bind localhost:1234
上記コマンドを実行すると、以下のようにワーカープロセスがずらずらと起動してきます。
[2020-08-18 19:52:58 +0900] [13404] [INFO] Starting gunicorn 20.0.4
[2020-08-18 19:52:58 +0900] [13404] [INFO] Listening at: http://127.0.0.1:1234 (13404)
[2020-08-18 19:52:58 +0900] [13404] [INFO] Using worker: uvicorn.workers.UvicornWorker
[2020-08-18 19:52:58 +0900] [13407] [INFO] Booting worker with pid: 13407
[2020-08-18 19:52:58 +0900] [13408] [INFO] Booting worker with pid: 13408
[2020-08-18 19:52:58 +0900] [13407] [INFO] Started server process [13407]
[2020-08-18 19:52:58 +0900] [13407] [INFO] Waiting for application startup.
:(略)
[2020-08-18 19:52:58 +0900] [13410] [INFO] Waiting for application startup.
[2020-08-18 19:52:58 +0900] [13410] [INFO] Application startup complete.
ログが出てくるのが止まった後、http://127.0.0.1:1234 にアクセスしてHello worldが表示されたらGunicorn経由でのアクセスは成功です。
プロセス管理機能の確認
今回Gunicornを利用するねらいである、プロセス数監視&復活機能が働いていることを確認してみましょう。例ではワーカーとしてPID13407〜13410が起動したので、別のターミナルを起動してPID13407のプロセスを落としてみます。
$ kill 13407 -9
すると以下のように13407が落ち、ただちに新たなワーカープロセスがPID13456として起動してくる様子が確認できると思います。
[2020-08-18 20:07:36 +0900] [13407] [INFO] Shutting down
[2020-08-18 20:07:36 +0900] [13407] [INFO] Error while closing socket [Errno 9] Bad file descriptor
[2020-08-18 20:07:36 +0900] [13407] [INFO] Waiting for application shutdown.
[2020-08-18 20:07:36 +0900] [13407] [INFO] Application shutdown complete.
[2020-08-18 20:07:36 +0900] [13407] [INFO] Finished server process [13407]
[2020-08-18 20:07:36 +0900] [13407] [INFO] Worker exiting (pid: 13407)
[2020-08-18 20:07:36 +0900] [13456] [INFO] Booting worker with pid: 13456
[2020-08-18 20:07:37 +0900] [13456] [INFO] Started server process [13456]
[2020-08-18 20:07:37 +0900] [13456] [INFO] Waiting for application startup.
[2020-08-18 20:07:37 +0900] [13456] [INFO] Application startup complete.
Nginxのリバースプロキシ配下にする
Nginxをリバースプロキシとする方法について解説していきます。ここではMacを前提としていますので、Linuxなどを利用する場合はパスやコマンドを置き換えてください。
なおこの記事ではリバースプロキシもWebアプリも同じマシンで動かしますが、実際には異なるマシンになると思いますので、適宜アドレスなどを読み替えてください。
Nginxのインストール
以下の通りです🍺
$ brew install nginx
インストールしたら起動してみましょう。
$ sudo nginx
http://127.0.0.1:8080/ にアクセスしてNginxのウェルカムページが表示されたらインストールは成功です。
リバースプロキシの設定
/usr/local/etc/nginx/servers/fastapitest
を作成して以下の通り定義します。ポート2345番に来たアクセスについて、ルート以下すべてをhttp://127.0.0.1:1234 のプロキシとして振る舞うような内容です。
proxy_set_header
はセットしてアプリに渡してあげないと、自身のドメインやプロトコルがわからない故に様々な問題が生じます。詳細はNginxのドキュメントなどを参照してください。なおこれはNginx固有の話ではなくリバースプロキシを利用する際の一般的な問題です。
server {
listen 2345;
server_name fastapitest;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
location / {
# 別マシンの場合は127.0.0.1ではなくそのマシンのアドレスにする
proxy_pass http://127.0.0.1:1234/;
}
}
定義が完了したら、以下のコマンドでNginxの設定をリロードします。
$ sudo nginx -s reload
http://127.0.0.1:2345 にアクセスしてHello worldが表示されたらNginx経由のアクセスは成功です。
SSLの設定
先ほどのリバースプロキシの定義に以下の通り追加します。証明書の取得手順は割愛します。監視するポート番号を443に変更し、server_name
を実際のドメイン名に変更します。
server {
# 2345の監視をやめて443を監視
# listen 2345;
listen 443 ssl;
# ドメインを指定
# server_name fastapitest;
server_name fastapitest.your.domain;
# SSL関連の設定を追加
ssl_certificate_key /path/to/privkey.pem; # Key
ssl_certificate /path/to/fullchain.pem; # Cert
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; # TLS1.1以上
ssl_ciphers HIGH:!aNULL:!MD5; # 暗号化方式。HIGH=128bit長以上とし、MD5は不許可
ssl_prefer_server_ciphers on; # サーバの設定を優先
# SSL関連の設定 ここまで
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP $remote_addr;
location / {
# 別マシンの場合は127.0.0.1ではなくそのマシンのアドレスにする
proxy_pass http://127.0.0.1:1234/;
}
}
なおssl_ciphers
の値はNginxのデフォルト値なので指定しなくても同じですが、設定余地として明示したものです。
定義を修正したら、sudo nginx -s reload
でリロードしてhttps://fastapitest.your.domain/ にアクセスしてHello worldが表示されたらSSLオフロードも成功です。
おまけ:Unixドメインソケットで接続
わざわざリバースプロキシをSSLオフロード用に構えたのに同一マシン内のGunicornに接続することもないと思いますが、いつか役に立つかもしれないのでメモしておきます。
まずはTCP通信ではなくUnixドメインソケットにバインドしてGunicornを起動しなおします。
$ gunicorn -w 4 -k uvicorn.workers.UvicornWorker run:app --bind unix:/tmp/myuvicorn.sock
続いてNginxの設定変更です。WSGIであればlocation
ディレクティブにuwsgi_pass
としてUnixドメインソケットのパスを設定すればOKでしたが、ASGIの場合はソケットを含むupstream
ディレクティブでサーバーグループを定義し、これをproxy_pass
ディレクティブで指定する必要があるようです。
:(略)
location / {
# proxy_pass http://127.0.0.1:1234/;
proxy_pass http://uvicorntest;
}
}
upstream uvicorntest {
server unix:/tmp/myuvicorn.sock;
}
設定リロード後にアクセスしてHello worldが表示されたら成功です。
さいごに
私はFlask派でしたが、FastAPIを触ってみてのファーストインプレッションが良かったので徐々に軸足を移して行こうと思っています。
というわけで私もまだFastAPI素人・Uvicorn素人なので、「ここちがってるよー」みたいなのがあれば是非是非ご指摘いただけるとうれしいです🙏