経緯
- 1つのAWSインスタンスで、複数のgunicornプロセスを起動することを検討している
- 間違えて、他のプロセスを停止しないように、よい方法を模索している
環境
- AWS
- Ubuntu 20.04.2 LTS
- Python 3.8.10 (venvで環境構築)
uvicorn==0.14.0
gunicorn==20.1.0
setproctitle==1.2.2
fastapi==0.65.2
用語の整理
Uvicornとは
Uvicorn is a lightning-fast ASGI server implementation, using uvloop and httptools.
*2より抜粋
- ASGI server の実装
ASGI とは
ASGI (Asynchronous Server Gateway Interface) is a spiritual successor to WSGI,
*5より抜粋
- WSGI の 精神的続編 (*6 より)
WSGI とは
The Web Server Gateway Interface (WSGI, pronounced whiskey[1][2] or WIZ-ghee[3]) is a simple calling convention for web servers
to forward requests to web applications or frameworks written in the Python programming language.
*4より抜粋
- Pythonで書かれたWebアプリケーションに、フォワードするWebサーバーのインターフェース定義
- ウィスキーと発音することがあるみたいだ (英語の解説動画を見てるとウィスキーと聞こえる)
Uvicornとは (2回目)
- Pythonで書かれたWebアプリケーションに、フォワードするWebサーバーのためのインターフェース定義の
- 精神的続編である ASGIを実装したサーバー
- 正確な表現ではないが、PythonでWebアプリを作って、Webサーバー化してくれる、と考えればよいだろうか
gunicorn とは
Gunicorn 'Green Unicorn' is a Python WSGI HTTP Server for UNIX.
*7 より抜粋
Gunicorn is a mature, fully featured server and process manager.
Uvicorn includes a Gunicorn worker class allowing you to run ASGI applications,
with all of Uvicorn's performance benefits, while also giving you
unicorn's fully-featured process management.
*2 より抜粋
- gunicornは、 WSGIのHTTPサーバー
- バージョン番号からも伺えるが、Gunicornは成熟している
- UvicornWorkerをgunicornで使えば、Uvicornのパフォーマンスの恩恵を受けつつ、gunicornのプロセス管理などの機能が使える、といったところだろうか
Uvicorn のインストールと Hello, world
Uvicorn のインストール
pip install uvicorn
Uvicorn の Hello, world
- uvicornを使ってWebサーバーを起動して、
- HTTPリクエストに対して、
- Hello, world と 環境変数 を返却する簡単な例
import uvicorn
import os
async def app(scope, receive, send):
assert scope['type'] == 'http'
mode = 'DEBUG'
if ('MODE' in os.environ.keys()):
mode = os.environ['MODE']
body = (f'Hello, world (mode:{mode})\n').encode()
await send({
'type': 'http.response.start',
'status': 200,
'headers': [
[b'content-type', b'text/plain'],
],
})
await send({
'type': 'http.response.body',
'body': body,
})
if __name__ == "__main__":
uvicorn.run("main:app", host="127.0.0.1", port=8080, log_level="info")
unicornで、Webサーバーを起動
- 以下のようにすると、フォアグランドで実行される
- (nohup などを使って、バックグランド実行にすることもできる)
python main.py
INFO: Started server process [49698]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8080 (Press CTRL+C to quit)
INFO: 127.0.0.1:45258 - "GET / HTTP/1.1" 200 OK
curlで動作確認
- 環境変数が設定されていないので、モードがDEBUGと表示されることを確認
curl http://127.0.0.1:8080/
Hello, world (mode:DEBUG)
gunicornの活用
gunicorn のインストール
pip install gunicorn
gunicorn の起動
- 先ほど作った Pythonのファイル名 main と 関数名appをgunicornのコマンドの引数に指定する
- gunicornのコマンドオプションで各種設定が行える
- ここでは、-c コマンドオプションのみを指定
- -c を使うと、設定を外部ファイル(gunicorn_config.pyなど)に記述できる
# mainはpythonのファイル名、appは関数名
gunicorn -c gunicorn_config_prod.py main:app
gunicornの設定
- ワーカー指定、デーモン化、プロセスIDをファイルに保存、プロセス名の指定ができる
- (このあたりが、gunicornの特徴と考えてよいだろうか)
- 設定の詳細は、*3が参考になる
# 実行するPythonがあるパス
pythonpath = './'
# ワーカー数
workers = 2
# ワーカーのクラス、*2 にあるようにUvicornWorkerを指定 (Uvicornがインストールされている必要がある)
worker_class = 'uvicorn.workers.UvicornWorker'
# IPアドレスとポート
bind = '127.0.0.1:8080'
# プロセスIDを保存するファイル名
pidfile = 'prod.pid'
# Pythonアプリに渡す環境変数
raw_env = ['MODE=PROD']
# デーモン化する場合はTrue
daemon = True
# エラーログ
errorlog = './logs/error_log.txt'
# プロセスの名前
proc_name = 'my_python_app'
# アクセスログ
accesslog = './logs/access_log.txt'
動作確認
- 設定した環境変数 PROD が表示されることを確認
curl http://127.0.0.1:8080/
Hello, world (mode:PROD)
gunicornのプロセスの確認
プロセス名を変更するためのパッケージをインストールする
pip install setproctitle
psコマンドでプロセスを確認
- proc_name を設定しておくと、grepしやすい、みやすい
- プロセス名を変更していない場合は、「gunicornという文字列」と「指定したport番号」などでgrepすればよさそう
- (コマンドオプションでport番号を指定しておく必要があるが)
- 以下のpsコマンドの実行で、masterプロセスが1つ、workerプロセスが2つあることが確認できる
- gunicornの設定ファイルで指定したmy_python_appという名前が表示されることを確認
ps aux | grep my_python_app | grep -v grep
ubuntu 46752 0.1 2.2 33040 22608 ? S 17:41 0:00 gunicorn: master [my_python_app]
ubuntu 46754 0.2 2.2 33512 22804 ? S 17:41 0:00 gunicorn: worker [my_python_app]
ubuntu 46755 0.1 2.2 33512 22808 ? S 17:41 0:00 gunicorn: worker [my_python_app]
gunicorn でプロセスIDをファイルに保存
- 「pidfile」項目でファイル名のファイルにPIDが出力される
- masterプロセスのPIDが保存されるようだ
cat prod.pid
46752
- errorlog のINFOレベルのログでもプロセスIDを確認できそうだ
[2021-07-01 17:41:23 +0900] [46752] [INFO] Listening at: http://127.0.0.1:8080 (46752)
[2021-07-01 17:41:23 +0900] [46752] [INFO] Using worker: uvicorn.workers.UvicornWorker
[2021-07-01 17:41:23 +0900] [46754] [INFO] Booting worker with pid: 46754
[2021-07-01 17:41:23 +0900] [46754] [INFO] Started server process [46754]
[2021-07-01 17:41:23 +0900] [46754] [INFO] Waiting for application startup.
[2021-07-01 17:41:23 +0900] [46754] [INFO] Application startup complete.
[2021-07-01 17:41:23 +0900] [46755] [INFO] Booting worker with pid: 46755
[2021-07-01 17:41:23 +0900] [46755] [INFO] Started server process [46755]
[2021-07-01 17:41:23 +0900] [46755] [INFO] Waiting for application startup.
[2021-07-01 17:41:23 +0900] [46755] [INFO] Application startup complete.
gunicorn のプロセスの停止
- master プロセスを停止すれば、workerプロセスも停止するようだ
デーモン化したプロセスを停止するコマンド例
kill `cat prod.pid`
FastAPI
- FastAPIを使った場合も同様に実行できた
FastAPIのインストール
pip install FastAPI
import uvicorn
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
if __name__ == "__main__":
uvicorn.run("main2:app", host="127.0.0.1", port=8080, log_level="info")
gunicorn -c gunicorn_config_prod.py main2:app
curl http://127.0.0.1:8080/
{"Hello":"World"}
参考資料
- *1 https://stackoverflow.com/questions/14604653/how-to-stop-gunicorn-properly
- *2 https://www.uvicorn.org/
- *3 https://docs.gunicorn.org/en/stable/settings.html
- *4 https://en.wikipedia.org/wiki/Web_Server_Gateway_Interface
- *5 https://asgi.readthedocs.io/en/latest/
- *6 https://ja.wikipedia.org/wiki/%E7%B2%BE%E7%A5%9E%E7%9A%84%E7%B6%9A%E7%B7%A8
- *7 https://gunicorn.org/
- *8 https://fastapi.tiangolo.com/