
Granian+FastAPIのコンテナイメージを作成してみる
はじめに
データアナリティクス事業本部のkobayashiです。
以前「Hypercorn+FastAPIのコンテナイメージを作成してみる」という内容でブログを書きましたが、内容が古くなってきたのとASGIとしてHypercornよりもGranianの開発が活発で評判も良いのでこちらに置き換えてみたのでまとめます。
- granian: A Rust HTTP server for Python applications
- hypercorn: Hypercorn is an ASGI and WSGI Server based on Hyper libraries and inspired by Gunicorn.
Granianとは
Granianは、Hyper crate上に構築されたPythonアプリケーション用のRust製HTTPサーバーで以下の特徴があります。
- HTTP/1、HTTP/2をサポート
- ASGI/3、RSGI、WSGIインターフェースに対応
- HTTPS、mTLS、Websocketsをサポート
- 静的ファイルの直接配信が可能
- 高い並行処理能力とパフォーマンス
Granianの公式では他モジュールとの比較も出ていますが、Hypercornよりもとても高いパフォーマンスとなっていて、Uvicornをはじめとする高速と言われているサーバーと比較しても高いパフォーマンスとなっています。
granian/benchmarks/vs.md at master · emmett-framework/granian
FastAPI+Granianのイメージを作成してみる
では実際にFastAPI+Granianのイメージを作成してみます。
作成するイメージは以前作成したものと同じものを作成してリソース使用量の比較をしてみたいと思います。
ベースイメージはイメージ軽量化のため必要最低限のものがインストールされているPythonイメージのpython:3.13-slim
を使います。
ディレクトリ構成は以下になります。
├── Dockerfile
├── app
│ └── main.py
└── requirements.txt
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つ用意しました。
fastapi
granian
pandas
requirements.txtにはpythonライブラリは今回目的のGranianとFastAPI及びレスポンスで使用しているpandasを記載してあります。
FROM python:3.13-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"]
ENTRYPOINT ["granian", "--interface", "asgi", "--host", "0.0.0.0", "--port", "80", "--workers", "4","--access-log", "main:app"]
ENTRYPOINT
は前回のHypercornを使った設定と同じ挙動になるようなオプションを付けています。
他にも使用できるオプションは沢山あるので公式ドキュメントをご確認ください。
ではイメージを作成して実行してみます。
FastAPI+Granianのコンテナを起動をしてみる
イメージ作成できたのでコンテナを起動してリクエストを送ってみます。
[INFO] Starting granian (main PID: 1)
[INFO] Listening at: http://0.0.0.0:80
[INFO] Spawning worker-1 with PID: 7
[INFO] Spawning worker-2 with PID: 9
[INFO] Spawning worker-3 with PID: 11
[INFO] Spawning worker-4 with PID: 13
[INFO] Started worker-2
[INFO] Started worker-3
[INFO] Started worker-4
[INFO] Started worker-3 runtime-1
[INFO] Started worker-4 runtime-1
[INFO] Started worker-2 runtime-1
[INFO] Started worker-1
[INFO] Started worker-1 runtime-1
[2025-05-19 08:17:32 +0000] 172.20.0.83 - "GET /static/docs HTTP/1.1" 200 8.193
[2025-05-19 08:17:42 +0000] 172.20.0.83 - "GET /item HTTP/1.1" 200 109.221
今回も--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}
問題なくレスポンスが返ってきました。
GranianとHypercornのリソース消費量の比較
最後にFargateにデプロイしたコンテナのリソース使用量を比較してみます。
fastapiのコードは同一でバックエンドの環境(vCPU: 1024, Mem: 2048)は全く同一にしてあります。また送信したリクエストも全く同じ内容です。
Granian
Hypercorn
比較するとGranianのほうがHypercornに比べてCPU仕様が3%ほど、Memoryが5%ほど使用量が少なくなっています。それほど負荷をかけた処理ではないので明確な差は出ませんでしたが体感としてレスポンスもGranianの方が速かったです。
まとめ
Hypercornを使ってFastAPIを実行するコンテナイメージをGranianで置き換えてみました。ENTRYPOINTの書き換えだけで簡単に移行ができ、パフォーマンスも良さそうで開発も活発なので今後はGranian+FastAPIをメインに使っていくべきかと思います。
最後まで読んで頂いてありがとうございました。