はじめに
データアナリティクス事業本部のkobayashiです。
コンテナ環境でFastAPIを使ったプロダクト開発を行っているのですがイメージを作成する際にこれまではGunicornによる公式Dockerイメージを使っていましたが、Hypercornを使ったイメージをゼロから作成してみたのでその内容をまとめます。
Hypercornを使ったイメージを作成した経緯
コンテナ環境でFastAPIを動かすためにFastAPIの公式ページ(コンテナ内のFastAPI - Docker - FastAPI )で紹介されているGunicornによる公式DockerイメージGunicornによる公式Dockerイメージ を使っていたところBackgroundTasksがで想定外の動きをしました。それで原因を解消すべく調査を詳しくすることも考えたのですが、以前より気になっていたHypercornを使ってみたいという動機もあったのでHypercornを使ってゼロからFastAPIのイメージを作成することにしました。
Hypercornとは
PythonでWebアプリケーションを管理するインターフェースであるASGIのサーバーの一つです。HypercornはGunicornに影響を受け、ASGIが提供する非同期性と非同期Pythonフレームワークの利点を活用することを目的としてかいはつされました。主な特徴としては以下のようなものがあります。
- HTTP/1, HTTP/2, WebSocket,および HTTP/3(またはQUIC)のプロトコルをサポート。
- ASGI、および WSGI 仕様をサポート。
- 設定の項目が多く使いやすい。
- プロキシプロトコルを含むさまざまなネットワークプロトコルをサポート。
- HTTP/2を自動で有効にする。
Hypercornは非同期フレームワーク、例えばStarletteやQuartなどの実行に使われることが多いです。
- GitHub - pgjones/hypercorn: Hypercorn is an ASGI and WSGI Server based on Hyper libraries and inspired by Gunicorn.
- Hypercorn documentation — Hypercorn 0.14.4+dev documentation
FastAPI+Hypercornのイメージを作成してみる
では実際にFastAPI+Hypercornのイメージを作成してみます。ベースイメージはイメージ軽量化のため必要最低限のものがインストールされているPythonイメージのpython:3.9-slim
を使います。
ディレクトリ構成は以下になります。
├── Dockerfile
├── app
│ └── main.py
└── requirements.txt
main.py
from typing import Optional
import pandas as pd
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/item/")
def read_items():
df = pd.DataFrame(
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]],
index=["a", "b", "c", "d"],
columns=["c1", "c2", "c3"],
)
return df.to_dict()
@app.get("/item/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
return {"item_id": item_id, "q": q}
main.pyで作成するAPIはテスト用にシンプルなレスポンスを返すエンドポイントを3つ用意しました。
requirements.txt
fastapi
hypercorn
pandas
requirements.txtにはpythonライブラリは今回目的のHypercornとFastAPI及びレスポンスで使用しているpandasを記載してあります。
Dockerfile
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt /requirements.txt
RUN pip install --no-cache-dir --upgrade -r /requirements.txt
RUN rm -rf /root/.cache/pip
COPY ./app /app
ENTRYPOINT ["hypercorn", "main:app", "--bind", "0.0.0.0:80", "--access-logfile", "-", "--error-logfile","-","--workers","4"]
肝心のイメージを作成するDockerfileですが以下の様に特に凝った記述ではなく非常にシンプルなDockerfileで実行できます。
唯一最後のENTRYPOINTの記述でhypoercornのオプションを指定している箇所があります。オプションの内容は--bind
でバインド先のTCPホスト/アドレスとして0.0.0.0:80
を指定、--access-logfile
,-error-logfile
でstout,stderrにログを出力し、--workers
で生成するワーカー数を4に指定しています。Hypercornで指定できるオプションは他にもあり主に使うオプションとしては以下があるかと思います。
- backlog
- 待機させる接続数
- access_log_format
- アクセスログのフォーマット
- worker_class
- 使用するワーカーのタイプ。Hypercorn は、asyncio、uvloop、trio ワーカークラスをサポートしています。
また今回は実行コマンドでオプションを指定していましたが、Hypercornでは他にも.py
や.toml
ファイルにオプションを記述して読み込んだり、PythonモジュールまたはPythonモジュール内のインスタンスを使用してオプションを指定することも可能です。
詳しくは以下のHypercornの公式ドキュメンををご確認ください。
Configuring — Hypercorn 0.14.4+dev documentation
ではイメージを作成して実行してみます。
$ docker build -t hypercorn-test .
[+] Building 0.9s (11/11) FINISHED docker:desktop-linux
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 385B 0.0s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [internal] load metadata for docker.io/library/python:3.9-slim 0.9s
=> [1/6] FROM docker.io/library/python:3.9-slim@sha256:5479e53617ff968c45561001cd33e0fa60a7daea4810c21c9ea20a7cf6397e4e 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 202B 0.0s
=> CACHED [2/6] WORKDIR /app 0.0s
=> CACHED [3/6] COPY requirements.txt /requirements.txt 0.0s
=> CACHED [4/6] RUN pip install --no-cache-dir --upgrade -r /requirements.txt 0.0s
=> CACHED [5/6] RUN rm -rf /root/.cache/pip 0.0s
=> CACHED [6/6] COPY ./app /app 0.0s
=> exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:fdc30432c3745dcee7be969b9c503262cffb73ef9333e5b4b6a02e9fb5b679b9 0.0s
=> => naming to docker.io/library/hypercorn-test 0.0s
What's Next?
View a summary of image vulnerabilities and recommendations → docker scout quickview
FastAPI+Hypercornのコンテナを起動をしてみる
イメージ作成できたのでコンテナを起動してリクエストを送ってみます。
$ docker run -p 80:80 hypercorn-test
[2023-10-20 00:38:34 +0000] [11] [INFO] Running on http://0.0.0.0:80 (CTRL + C to quit)
[2023-10-20 00:38:34 +0000] [12] [INFO] Running on http://0.0.0.0:80 (CTRL + C to quit)
[2023-10-20 00:38:34 +0000] [9] [INFO] Running on http://0.0.0.0:80 (CTRL + C to quit)
[2023-10-20 00:38:34 +0000] [10] [INFO] Running on http://0.0.0.0:80 (CTRL + C to quit)
--worker
を4に指定してあるので4つのプロセスが起動しています。
curlにてエンドポイントにリクエストを送ってみます。
$ curl http://127.0.0.1/
{"Hello":"World"}
$ curl http://127.0.0.1/item/
{"c1":{"a":0,"b":3,"c":6,"d":9},"c2":{"a":1,"b":4,"c":7,"d":10},"c3":{"a":2,"b":5,"c":8,"d":11}}
$ curl http://127.0.0.1/item/999
{"item_id":999,"q":null}
問題なくレスポンスが返ってきました。
まとめ
Hypercornを使ってFastAPIを実行するコンテナイメージをゼロから作成してみました。HypercornですとUvicornではまだ対応していないHTTP/2, HTTP/3にも対応しているのでHTTP/2, HTTP/3を使う際には Hypercornは選択肢として有効だと思います。
最後まで読んで頂いてありがとうございました。