Hypercorn+FastAPIのコンテナイメージを作成してみる

2023.10.20

はじめに

データアナリティクス事業本部の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などの実行に使われることが多いです。

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は選択肢として有効だと思います。

最後まで読んで頂いてありがとうございました。