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

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

Clock Icon2025.05.20

はじめに

データアナリティクス事業本部のkobayashiです。

以前「Hypercorn+FastAPIのコンテナイメージを作成してみる」という内容でブログを書きましたが、内容が古くなってきたのとASGIとしてHypercornよりもGranianの開発が活発で評判も良いのでこちらに置き換えてみたのでまとめます。

https://dev.classmethod.jp/articles/hypercorn_fastapi/

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
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
granian

pandas

requirements.txtにはpythonライブラリは今回目的のGranianとFastAPI及びレスポンスで使用しているpandasを記載してあります。

Dockerfile
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を使った設定と同じ挙動になるようなオプションを付けています。
他にも使用できるオプションは沢山あるので公式ドキュメントをご確認ください。

https://github.com/emmett-framework/granian?tab=readme-ov-file#options

ではイメージを作成して実行してみます。

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
granian

Hypercorn
hypercorn

比較するとGranianのほうがHypercornに比べてCPU仕様が3%ほど、Memoryが5%ほど使用量が少なくなっています。それほど負荷をかけた処理ではないので明確な差は出ませんでしたが体感としてレスポンスもGranianの方が速かったです。

まとめ

Hypercornを使ってFastAPIを実行するコンテナイメージをGranianで置き換えてみました。ENTRYPOINTの書き換えだけで簡単に移行ができ、パフォーマンスも良さそうで開発も活発なので今後はGranian+FastAPIをメインに使っていくべきかと思います。

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

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.