AI事業本部 協業リテールメディア Div. の青見 (@nersonu) です。恐らく機械学習エンジニアをしています。どのような領域をしていれば、機械学習エンジニアと名乗って良いか難しいですよね。私もその一人です。

前回は LLM に表を読解させるテーマでブログを書いています。こちらも興味がありましたら、ぜひ御覧ください。

さて、そんな機械学習エンジニアですが、多くの機械学習エンジニアの方々は何かしら推論エンドポイントを持つ API を立てることが多いのではないでしょうか。推論を行うモデルの多くは Python で実装されるため、基本的に Web API の構成は Python のフレームワークを選定することが多くなるかと思います。

近年はやはり、 FastAPI の人気が高いように見えます。モダンかつ高速であり、何より Pydantic を用いたI/Oのバリデーションが強力です。今回はそんな FastAPI を用いて実装した API を Google Cloud の Cloud Run にデプロイするシチュエーションで構築してみましょう。

目次

シチュエーション

今回は比較的初期フェーズのプロジェクトを想定して、手でデプロイするシチュエーションを考えてみましょう。このデプロイがうまくいったら、デプロイスクリプトやCI, Terraform 等でのデプロイへの移行を試みていくイメージです。

  • Dockerfile でコンテナイメージを作成・ビルド
  • Artifact Registry へのプッシュ (w/ gcloud CLI)
  • Cloud Run へのデプロイ (w/ gcloud CLI)

 

ファイル構成とアプリケーション

全体のファイル構成は以下のとおりです。パッケージ管理には Poetry を利用しています。

.
├── .env.yaml
├── Dockerfile
├── README.md
├── my_app
│   ├── __init__.py
│   ├── gunicorn.conf.py
│   ├── main.py
│   ├── schemas
│   │   ├── __init__.py
│   │   ...
│   ├── services
│   │   ├── __init__.py
│   │   ├── ml.py 
│   ... ...
├── poetry.lock
├── pyproject.toml
└── tests
    ├── __init__.py
    ...

今回はこのようなシンプルな FastAPI のアプリケーション (main.py) をデプロイすることを考えましょう。推論結果を application/json で返すだけのエンドポイントを置いています。

from fastapi import Depends, FastAPI
from fastapi.responses import JSONResponse

from my_app.schemas import PredictionRequest  # 推論のリクエストスキーマ
from my_app.services.ml import MyModel, get_ml_model  # ML モデルを定義


app = FastAPI()


@app.get("/", response_class=JSONResponse)
def read_root() -> JSONResponse:
    return JSONResponse(
        content={"message": "Hello, world!"},
    )


@app.get("/predict", response_class=JSONResponse)
def predict(
    request: PredictionRequest,
    ml_model: MyModel = Depends(get_ml_model),
) -> JSONResponse:
    """推論エンドポイント"""
    result = ml_model.predict(request.input_items)
    return JSONResponse(
        content={"result": result},
    )

Dockerfile は次の通りです。開発用にマルチステージビルドで development ステージを追加しています。

FROM python:3.11-slim-bullseye as base

RUN apt-get update && \
apt-get install --no-install-recommends -y \
gcc \
curl \
python3-dev \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Env vars
ENV PYTHONIOENCODING=utf-8 \
LANG=C.UTF-8
# Python envs
ENV PYTHONUNBUFFERED=1 \
# Disable writing bytecode (.pyc)
PYTHONDONTWRITEBYTECODE=1 \
# pip
PIP_NO_CACHE_DIR=off \
PIP_DISABLE_PIP_VERSION_CHECK=on \
PIP_ROOT_USER_ACTION=ignore \
# Poetry
POETRY_HOME=/opt/poetry \
POETRY_VIRTUALENVS_CREATE=false \
POETRY_NO_INTERACTION=1
ENV PATH="${POETRY_HOME}/bin:${PATH}"
RUN python -m pip install --upgrade pip setuptools wheel
WORKDIR /app
# Install Poetry
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN curl -sSL https://install.python-poetry.org | python -
COPY pyproject.toml poetry.lock ./
COPY my_app ./my_app
RUN poetry install --only main --no-root
RUN poetry install --only-root

FROM base as development
EXPOSE 8080
CMD ["uvicorn", "my_app.main:app", "--reload", "--host", "0.0.0.0", "--port", "8000"]

FROM base as app
COPY . /app/
EXPOSE 8080
CMD ["gunicorn", "-c", "my_app/gunicorn.conf.py"]

ローカルでは development ステージをビルドし、手元で Uvicorn を起動して検証することを想定しています。例えば、以下のようなコマンドで動かすことができます。

docker build --target development -t my-app:dev .
docker run -it -p 8080:8080 -v $(pwd)/:/app/ my-app:dev

本番はパフォーマンスの調整の容易さを考慮し、ワーカー自体は Uvicorn を使いつつも、 Gunicorn を使うことにしてみます。Gunicorn の設定 (gunicorn.conf.py) はこのようにしました。

# gunicorn config's docs: https://docs.gunicorn.org/en/stable/settings.html

from multiprocessing import cpu_count


wsgi_app = "my_app.main:app"
worker_class = "uvicorn.workers.UvicornWorker"
bind = "0.0.0.0:8080"
workers = cpu_count() * 2 + 1

accesslog = "-"
errorlog = "-"
loglevel = "info"

# ref: https://github.com/benoitc/gunicorn/pull/862#issuecomment-53175919
max_requests = 500
max_requests_jitter = 200

 

デプロイ

コンテナビルドと Artifact Registry へのプッシュ

ローカルでのコンテナビルドを Apple シリコンの PC で行う場合、マルチプラットフォームビルドで linux/amd64 を指定してコンテナをビルドしておきます。これは、Cloud Run において独自のコンテナイメージを利用する場合は必須となります。

docker buildx でビルドを行う場合のコマンドの例は以下の通りです (マルチプラットフォームビルドをすることもあり、念には念を入れてdocker buildx でビルドしています)。

docker buildx build --target app --platform linux/amd64 -t my-app:latest .

gcloud auth login によって認証済みの状態で、 Artifact Registry へのプッシュを行います。今回は、 Artifact Registry の Docker リポジトリを事前に準備していることを前提とし、以下のようにタグ付け後、プッシュします。

docker tag my-app:latest asia-docker.pkg.dev/<your-project-name>/containers/my-app:latest
docker push asia-docker.pkg.dev/<your-project-name>/containers/my-app:latest

上記では asia-region であることに留意してください。また、初回は push 前に Docker リポジトリの認証に関する設定を行う必要があります (https://cloud.google.com/artifact-registry/docs/docker/store-docker-container-images?hl=ja#auth)。

Cloud Run へのデプロイ

Artifact Registry へプッシュした Docker イメージの場所を参照してデプロイを行います。例えば、以下のようなコマンドを実行することで、デプロイを行うことが出来ます。

gcloud run deploy my-app \\
    --image=asia-docker.pkg.dev/<your-project-name>/containers/my-app:latest \\
    --env-vars-file=.env.yaml \\
    --memory=2Gi \\
    --cpu=2 \\
    --service-account=my-service-account@<your-project-name>.iam.gserviceaccount.com \\
    --region=asia-northeast1

.env.yaml に API で利用する環境変数を設定したり、リソースの設定、特定のサービスアカウントの指定等を行うことが出来ます。

デプロイが完了すると、以下のような出力とともにデプロイ先の Service URL が表示されます。

✓ Deploying... Done.                                                                                                                                                                                                                                                    
  ✓ Creating Revision...                                                                                                                                                                                                                                                
  ✓ Routing traffic...                                                                                                                                                                                                                                                  
Done. 
Service [my-app] revision [my-app-000xx-xxx] has been deployed and is serving 100 percent of traffic.
Service URL: https://my-app-xxxxxxxxxxxxx.run.app

コンソール上からでも API がデプロイ出来たか確認することができます。

Cloud Run 上の API の動作確認方法

デフォルトでは認証が必要なため、 Authorization ヘッダーに認証トークンを渡す操作が必要です。例えば、 curl でリクエストを送る場合は、以下のように認証トークンを取得しつつ実行します。

curl -H \\
    "Authorization: Bearer $(gcloud auth print-identity-token)" \\
    https://my-app-xxxxxxxxxxxxx.run.app  # your service URL

FastAPI で自動生成される OpenAPI の docs にアクセスしたい場合は、ModHeader などの Chrome 拡張機能を使ってヘッダーの設定をすると良いでしょう。必要な認証トークンは gcloud auth print-identity-token で取得することができます。

おわりに

今回は FastAPI で実装した API をお手軽に Cloud Run に gcloud CLI を用いてデプロイする方法を紹介しました。最初の一歩は簡単に始められますので、ぜひ試してみてはいかがでしょうか。

機械学習エンジニアとしてのアイデンティティに悩む方はぜひお話しましょう。以下のカジュアル面談のページや、X (@nersonu) の DM 等でお気軽にお声掛けくださいませ。

■ チームメンバーの他のおすすめのブログ記事はコチラ