AWS LambdaとCloud FunctionsにFastAPIをデプロイする方法

今回は、AWS LambdaとCloud FunctionsにFastAPIをシームレスにデプロイする方法を紹介します。
OSSの開発などでどちらでも動作する機能を同一コードで管理する時などに便利です。

作成する機能

SentryというApplication Monitoring PlatformのアラートをAWS Lambda, Cloud Functions経由でSlackに通知できる機能を作成します。
Integration機能を使用してSlackに通知する事ができますが、Free planでは直接通知する事ができないため、Webhookを使用して通知するものです。
OSSはこちらで公開しています。

fealone/sentry-alert-to-slack-with-functions
Contribute to fealone/sentry-alert-to-slack-with-functions development by creating an account on GitHub.

使用するライブラリ

今回使用するライブラリはAgraffeというものです。
AWS LambdaやCloud Functionsで使用できるEntrypointをFastAPIのアプリケーションを使用して簡単に作成する事ができます。

開発

パッケージのインストール

poetryを使用してパッケージ管理を行います。
poetryをインストールし、初期化を行います。

pip install poetry
poetry init

初期化が完了したら、今回使用するパッケージをインストールします。

poetry add fastapi
poetry add agraffe
poetry add requests

requirements.txtの作成

パッケージのインストールが完了したら、requirements.txtを作成します。
デプロイする際に必要になるため、パッケージを追加した際はこの作業を必ず実施する必要が有ります。

poetry export -f requirements.txt > requirements.txt

コーディング

実際の処理を書いていきます。
AWS LambdaとCloud Functionsの切り替えを環境変数で行うことで、コードを変えずに処理を変更しています。

import os
from typing import Dict

from agraffe import Agraffe, Service

from fastapi import FastAPI, Request

app = FastAPI()

@app.post("/printf")
async def printf(request: Request) -> Dict[str, str]:
    body = await request.json()
    print(body)
    return {}

platform = os.environ.get("PLATFORM", "GCP")

if platform == "GCP":
    entry_point = Agraffe.entry_point(app, Service.google_cloud_functions)
elif platform == "AWS":
    entry_point = Agraffe.entry_point(app, Service.aws_lambda)
else:
    Exception(f"Unsupported platform of {platform}")

FastAPIのインスタンスをAgraffeに渡す事で柔軟にコード切り替えを行っています。
環境変数はデプロイ時に設定し、その値を使用して切り替えを行っています。

  • クラウド環境の切り替えコード抜粋
platform = os.environ.get("PLATFORM", "GCP")

if platform == "GCP":
    entry_point = Agraffe.entry_point(app, Service.google_cloud_functions)
elif platform == "AWS":
    entry_point = Agraffe.entry_point(app, Service.aws_lambda)
else:
    Exception(f"Unsupported platform of {platform}")

デプロイ

Cloud Functionsへのデプロイ

gcloudコマンドを使用してデプロイします。
gcloudコマンドのインストール方法はこちらを参照してください。

gcloud functions deploy sentry-alert-to-slack-with-functions \
    --runtime python37 \
    --trigger-http \
    --entry-point entry_point \
    --region {region} \
    --set-env-vars SLACK_ENDPOINT={SLACK_WEBHOOK_ENDPOINT} \
    --set-env-vars PLATFORM=GCP

AWS Lambdaへのデプロイ

zipにパッケージ化した後、awsコマンドを使用してデプロイします。
awsコマンドのインストール方法はこちらを参照してください。

  • パッケージ化
mkdir dist \
    && cp main.py dist/ \
    && pip install -r requirements.txt -t dist/ \
    && cd dist \
    && zip -r ../dist.zip * \
    && cd ../ \
    && rm -rf dist
  • デプロイ
aws lambda create-function \
    --function-name sentry-alert-to-slack-with-functions \
    --runtime python3.7 \
    --role {LambdaExecuteRole} \
    --handler entry_point \
    --zip-file fileb://dist.zip \
    --environment "Variables={SLACK_ENDPOINT={SLACK_WEBHOOK_ENDPOINT},PLATFORM=AWS}"

デプロイ後、Lambdaを開きAPI Gatewayの設定を行います。
AgraffeはREST APIのみ対応しているので、REST APIを選択します。

おわりに

以上でAWS LambdaとCloud Functionsを同一コードでシームレスにデプロイする事ができます。
今回は公式のデプロイツールを使用していますが、より便利なツールを使用すると保守/運用性が上がると思います。