FastAPIアプリをGunicorn + Nginxで公開する
はじめに
概要
PythonのAPIフレームワークであるFastAPIを用い、Ubuntu環境 (VPS) にFastAPI + Gunicorn + Nginxの構成でREST APIを作る際の設定方法です。特にNginxの設定がいつも分からなくてググっているのでメモしておきます。
この記事の対象はNginxを全く触ったことないような方です。私のようなNginxが全く分からないPython使いがとりあえずFastAPIをNginxで公開できる所まで持っていこうという趣旨です。
環境
- Ubuntu 22.04.1 LTS(ConoHa VPS)
- nginx 1.18.0
- certbot 1.21.0
- Python: Miniconda環境
- Miniconda 4.12.0
- Python 3.10.4
- FastAPI 0.79.0
- uvicorn 0.18.2
- gunicorn 20.1.0
FastAPIアプリを公開する(Uvicorn/Gunicorn)
FastAPIでAPIエンドポイントの作成
それでは今回デプロイするFastAPIアプリを作成します。
$ pip install fastapi pydantic uvicorn[standard] gunicorn
以下のコードをVPS上の適当なディレクトリにmain.py
というファイル名で保存します。
from fastapi import FastAPI
app = FastAPI(root_path="/")
@app.get("/")
def say_hello():
return {"message": "Hello!"}
ルートにGETすると{"message": "Hello!"}
というJSONを返すAPIです。
FastAPIアプリの公開(ローカル)
まずはアプリケーションサーバにUvicornを用いて127.0.0.1:8000にこのAPIを立てます。ポート番号は好きな番号で構いませんが、とりあえず8000番ポートに立ててみます。
$ cd [main.pyを保存したディレクトリのパス]
$ python -m uvicorn main:app --host 127.0.0.1 --port 8000
AnacondaやMiniconda環境の場合は、
conda activate [仮想環境名]
してから上を実行するか、上のpython
を、Anaconda/Miniconda環境で使用しているPythonのパスに置き換えます。このパスは、conda activate [仮想環境名]; which python
で知ることができます。
別のターミナルを開き、VPS上で127.0.0.1:8000にcurlでGETして{"message": "Hello!"}
というJSONが返ってくればAPIが立てられています。
$ curl 127.0.0.1:8000
{"message":"Hello!"}
今はアプリケーションサーバにUvicornを用いましたが、Gunicornを用いてGunicornからUvicornを触ることができます。本番環境ではGunicornを用いる方がいいようなので、以下Gunicornを用いて説明します。
Uvicornを直接用いる前述の場合は単一プロセスですが、Gunicornを用いるとUvicornを複数プロセス立ち上げることができ、またそのUvicornプロセスが落ちたとしても再度プロセスを自動で立ち上げてくれます。(詳細は公式ドキュメントを参照: Server Workers - Gunicorn with Uvicorn - FastAPI)
以下のようにすることで、-wの引数で指定したプロセス数だけワーカーを持つようにGunicornが起動します。ワーカー数は適当に2にします。127.0.0.1:8000にcurlでGETすると同様に{"message": "Hello!"}
が返ってきます。
python -m gunicorn main:app --bind 127.0.0.1:8000 -w 2 -k uvicorn.workers.UvicornWorker
FastAPIアプリの公開(グローバル)
これまでは127.0.0.1にAPIを立てていました。以下のように0.0.0.0を指定することで、外部からアクセスできるようになります。
# Uvicornの場合
$ python -m uvicorn main:app --host 0.0.0.0 --port 8000
# Gunicornの場合
$ python -m gunicorn main:app --bind 0.0.0.0:8000 -w 2 -k uvicorn.workers.UvicornWorker
- 指定したポート番号のポート(ここでは8000番)をファイアウォールで開けておいてください。
- 特権ポートと呼ばれる1023番までのポート番号を指定する場合、sudo権限が必要です。先頭にsudoを付けてください。
VPSではなく自分のローカルPCからcurl [VPSのIPアドレス]:8000
を叩いてみて、同様に{"message":"Hello!"}
が返ってくれば成功です。Webブラウザのアドレスバーに[VPSのIPアドレス]:8000
を入力して開いてみても構いません。
Nginxの概略
以上の内容でとりあえずFastAPIアプリを公開することができますが、以下ではAPIにアクセスしてくるユーザとUvicorn/Gunicornの間にWebサーバのNginxを入れようと思います。
Nginxを入れない場合、複数のFastAPIアプリなどを公開しようとすると、アプリごとにポート番号を変える必要があります。
http://x.x.x.x:8000
でアプリ1にアクセスできるhttp://x.x.x.x:8001
でアプリ2にアクセスできる
一方、Nginxを入れてリバースプロキシすると、このようにサブディレクトリへのアクセスを振り分けることができます。
http://127.0.0.1:8000
でアプリ1を立ち上げるhttp://hoge.example.com/app1/
を127.0.0.1:8000
にリバースプロキシしてアプリ1にアクセスできる
http://127.0.0.1:8001
でアプリ2を立ち上げるhttp://hoge.example.com/app2/
を127.0.0.1:8001
にリバースプロキシしてアプリ2にアクセスできる
この次の章では、先程作成したFastAPIアプリを127.0.0.1:8000
で立ち上げておき、http://(VPSのIPアドレス)/app
にGETするとNginxのリバースプロキシで127.0.0.1:8000
に転送され、先程のアプリがレスポンスを返すようにします。この章では、その前にNginxの設定ファイルについて簡単に説明します。
Nginxのインストール
まずNginxをインストールします。
$ sudo apt install nginx
1行目でNginxを起動し、2行目でUbuntuの起動時にNginxが自動で起動するようにします。
$ sudo systemctl start nginx
$ sudo systemctl enable nginx
起動できているか、また自動起動が有効になっているかを確認します。
$ sudo systemctl status nginx
nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2022-12-25 20:43:29 JST; 25s ago
(以下略)
Active: active (running)
とあるのが今起動できていること、/lib/systemd/system/nginx.service; enabled
とあるのが自動起動されていることを示します。
なお、起動しているNginxを停止したい場合はsudo systemctl stop nginx
、自動起動を無効にしたい場合はsudo systemctl disable nginx
です。
Nginxの設定ファイル
バーチャルホストを作るときに使うNginxの設定ファイルの構成は以下のようになっています。
- /etc/nginx/nginx.conf
- /etc/nginx/conf.d/*.conf
- /etc/nginx/sites-enabled/*
バーチャルホストを追加する際は、/etc/nginx/nginx.conf
は編集せず、/etc/nginx/conf.d/*.conf
か/etc/nginx/sites-enabled/*
に追加します。
/etc/nginx/nginx.conf
には以下のように記載されていることから、/etc/nginx/nginx.conf
がロードされるときに、その中で/etc/nginx/conf.d/
直下にある拡張子confのファイルと/etc/nginx/sites-enabled/
直下にあるファイルが読み込まれることが分かります。
http {
(略)
##
# Virtual Host Configs
##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
/etc/nginx/sites-enabled/*
に追加する際は、実際には/etc/nginx/sites-available/
直下に設定を作成し、それを/etc/nginx/sites-enabled/
にシンボリックリンクを張るようにするのが一般的です。シンボリックリンクを外せばNginxの設定から除外されるのがメリットです。
それでは/etc/nginx/conf.d/*.conf
とetc/nginx/sites-available/*
のどちらに設定を記載すればよいかですが、多くのバーチャルホストを使う場合、あるいはバーチャルホストをデプロイしたりしなかったりと切り替えたい場合は後者、そうではない場合は前者、のような考え方が一つの決め方になります。本記事では、/etc/nginx/sites-available/
以下に設定を作ることにします。
FastAPIアプリを公開する(Gunicorn + Nginx)
Nginxを用いたリバースプロキシでのAPIの公開について説明します。
まず、/etc/nginx/sites-available/
直下に設定ファイルを作成します。この記事では/etc/nginx/sites-available/fastapi
というファイルを作ることにします。デフォルトのファイルとして/etc/nginx/sites-available/default
が用意されていますので、これをひな形としてコピーしてから編集することにします。
$ sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/fastapi
$ sudo ln -s /etc/nginx/sites-available/fastapi /etc/nginx/sites-enabled/
2行目のシンボリックリンクを貼る作業は、シンボリックリンクを外さない限りは/etc/nginx/sites-available/
に新しいファイルを作成したら最初の1回だけ行っておけば大丈夫です。
sudo nano /etc/nginx/sites-available/fastapi
でこのファイルを編集します。以下を貼り付けて上書き保存します。
server {
listen 80;
location /app/ {
proxy_pass http://127.0.0.1:8000/;
}
}
VPSのIPアドレスをx.x.x.x
とします。80番ポートをlistenするよ、http://x.x.x.x:80/app/
に来たアクセスは127.0.0.1:8000
に転送するよということですね。
なお、proxy_passのtrailing slash(末尾のスラッシュ)は付けるようにしましょう。付けないと正しくアクセスできません。地味にハマりポイントです。
保存したら、sudo nginx -t
を実行してNginxの設定ファイルに構文エラーがないかどうかを確かめておきます。
エラーが表示されなければ構文に誤りはありませんので、Nginxを再起動することで今作成した設定ファイルを反映させます。設定ファイルを更新したら必ずNginxを再起動してください。再起動するまでは反映されません。
$ sudo systemctl stop nginx
$ sudo systemctl start nginx
次に、先程作成したFastAPIアプリのroot_pathを/app/
に変更します。
from fastapi import FastAPI
app = FastAPI(root_path="/app/")
@app.get("/")
def say_hello():
return {"message": "Hello!"}
このFastAPIのエンドポイントをGunicornで公開します。
$ python -m gunicorn main:app --bind 127.0.0.1:8000 -w 2 -k uvicorn.workers.UvicornWorker
ローカルのPCからhttp://x.x.x.x/app/
をブラウザで開くかcurlを叩いて{"message":"Hello!"}
が返ってくれば成功です。
http://x.x.x.x/
で公開したい場合は、FastAPIアプリのroot_pathを/app/
ではなく/
にし、/etc/nginx/sites-available/fastapi
のlocationも/app/
ではなく/
にします。
Nginxのより進んだ設定
ここまででFastAPIアプリをGunicorn + Nginxで公開することができました。
FastAPIアプリを本番公開する際には、Nginxの設定において追加でいくつか行った方がいいことがあります。以下順に説明していきます。
- SSL化
- Let’s Encript + certbotでSSL証明書の導入と自動更新
- SSL対応 + HTTPに来たアクセスのリダイレクト
- IPアドレス直打ちでのアクセスを拒否
- Nginxのバージョンを非表示
- IPv6対応
- アクセスログにリバースプロキシを考慮したIPアドレスを残す
- アクセスログにPOSTボディを出す
- アクセスログを別ファイル化
1. SSL化
SSL証明書の導入・自動更新の設定
この節は独自ドメインのSSL証明書を取る時の最初の1回だけ行います。
独自ドメインを取得していることを前提にします。以下、hoge.example.com
という独自ドメインを使いたいとします。まず、独自ドメインを取得したドメイン会社のDNSレコード設定ページから、独自ドメインとVPSサーバのIPアドレスを紐づけてください。
sshの証明書はLet’s Encriptで取ることにします。証明書は90日おきに更新する必要がありますが、certbotを入れておくと自動で更新してくれます。
$ sudo apt install -y certbot python3-certbot-nginx
certbotの設定をします。以下を実行すると、恐らくメールアドレスを入力するようにメッセージが出ると思いますので、その通り入力してください。
$ sudo certbot --nginx -d hoge.example.com
次に、証明書の90日おきの自動更新が機能しているかどうか確かめます。以下を実行してエラーが出なければOKです。
$ sudo certbot renew --dry-run
Nginxの設定ファイルの対応
ここまでで独自ドメインのSSL化ができました。次にNginxの設定を行います。
独自ドメインを使いたいアプリの設定が記述されているファイル(/etc/nginx/sites-available/fastapi
など)に以下を貼り付けて上書き保存します。
server {
server_name hoge.example.com;
listen 443 ssl default_server;
ssl_certificate /etc/letsencrypt/live/hoge.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/hoge.example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
(略)
}
server {
if ($host = hoge.example.com) {
return 301 https://$host$request_uri;
}
listen 80;
server_name hoge.example.com;
return 404;
}
- 1個目のserverディレクティブでは、80番ポートだけでなく443番ポートもlistenするように設定します。また、SSLの証明書のパスなどを設定しています。
- 2個目のserverディレクティブでは、80番ポートへのアクセスを443番ポートにリダイレクトしています。
なお、default_serverの挙動ですが、デフォルトでは複数の設定ファイルを読み込んでいるときはファイル名の順番に読み込み、一番最初に読み込まれた設定ファイルに記載されているserver_nameをdefault_serverとします。
上で記載したように同一のserverディレクティブ内にserver_nameを指定してlisten [port] default_server
と記載すると、このserverディレクティブ内のserver_nameをdefault_serverとします。
2. IP直打ち拒否
x.x.x.x
宛のアクセスを拒否します。ユーザがWebサーバにIPアドレス直打ちでアクセスしてくることは普通考えにくいためです。
設定を反映させたい設定ファイルに以下を記述します。性質上、全てのバーチャルホストで同じ設定をしたいケースが多いと思いますので、個別の設定ファイルである/etc/nginx/sites-available/fastapi
ではなく、/etc/nginx/nginx.conf
か/etc/nginx/conf.d/default.conf
に以下の通り記述するのでも構いません。
server {
server_name _;
listen 80 default_server;
listen 443 ssl default_server;
return 444;
}
Nginxは、Hostヘッダがどのサーバ名ともマッチしないとき、あるいはリクエストにHostヘッダが含まれていないときはデフォルトサーバに振り分けます。これによって、他の設定ファイルのserverディレクティブ内に記載されているドメイン以外のアクセスは444エラーを返します。
server_nameのアンダーバーは「全てのサーバ」を示します。他の設定ファイルで定義されているhoge.example.com
などのドメインに該当しなかった全てのアクセスをこのserverディレクティブでキャッチするということですね。
3. Nginxのバージョンを非表示
Nginxはデフォルトでは使っているバージョンを表示します。特定のバージョンに脆弱性があり、自分が使っているバージョンが脆弱性のあるバージョンの場合、侵入者に脆弱性を知らせてしまっていますから、バージョンを非表示にするのが望ましいです。
/etc/nginx/nginx.conf
内に以下の1行を記載すればOKです。
http {
(略)
server_tokens off;
}
あるいは、/etc/nginx/sites-available/fastapi
などの個別の設定ファイル内に以下のように記載しても構いません。
server {
(略)
server_tokens off;
}
4. IPv6対応
/etc/nginx/sites-available/fastapi
に以下を記載します。
server {
(略)
# ここから下2行はIPv4対応
listen 80 default_server;
listen 443 ssl default_server;
# ここから下2行はIPv6対応
listen [::]:80 default_server;
listen [::]:443 ssl default_server;
}
5. ログにリバースプロキシを考慮した接続元のIPアドレスを残す
リバースプロキシしているので、何も設定しないとログファイルに残るIPアドレスなどは自分のIPアドレスになってしまいます。外部からアクセスしてきたIPアドレスなどをそのまま残すには、/etc/nginx/sites-available/fastapi
のlocationディレクティブの中に、proxy_set_header
で始まる5行を書きます。
server {
(略)
location /app/ {
proxy_pass http://127.0.0.1:8000/;
# ここから
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Real-IP $remote_addr;
# ここまで
}
}
6. ログにPOSTボディを出す
Nginxのデフォルトの設定では、ログにPOSTボディの中身は表示されません。以下のようにすると表示できます。
ただし、POSTボディが長い文字列になる場合、ログファイルが圧迫されてしまうことに注意してください。
まず、/etc/nginx/nginx.conf
にコメントを付した3行を記載します。
http {
##
# Logging Settings
##
log_format '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# 以下の3行を記載する
log_format format1 '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" "$request_body"';
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
次に、/etc/nginx/sites-available/fastapi
内に次の1行を追加します。これにより、/etc/nginx/nginx.conf
で定義したformat1の形式でログが記載されます。
server {
location /app/ {
(略)
# 追加する
access_log /var/log/nginx/access.log format1;
}
}
7. ログを別ファイル化
デフォルトでは/etc/nginx/nginx.conf
のaccess_log
に記載の/var/log/nginx/access.log
にログが作られますが、変更することもできます。
/etc/nginx/sites-available/fastapi
に以下を追記します。
server {
location /app/ {
(略)
# 追加する
access_log /var/log/nginx/access_fastapi.log;
}
}
もちろん、上で説明したようにログのフォーマットを変えることもできます。
server {
location /app/ {
(略)
# 追加する
access_log /var/log/nginx/access_fastapi.log format1;
}
}
参考
- 公式ドキュメント
- FastAPI + Uvicorn / Gunicorn
- Nginx
- その他参考にさせていただいた記事(Nginx)
- Nginxを導入したFastAPIの公開方法
- Nginxの設定ファイルについて
- NginxのTips