AWSで サーバーレス API のデプロイってちょっと大変
AWSでサーバーレスなBackend APIを作りたい時、まずAPI Gatewayを選択肢に挙げるBackendエンジニャー😺な方は多いかと思います。
ご存知の通り、API Gatewayは単体ではコードロジックを実行できません。
なので、API Gateway -> LambdaのようにProxyさせるやり方は、ど定番で誰もがやったことあるパターンです。
とはいっても、API Gatewayは独自な仕様が多く、AWSコンソールからいじるのも色々とキツく、かといってterraformで書くのも冗長な感じで、なかなか使いこなせないと思っているAWSユーザも多いかと思います。
(少なくとも私はそうです)
API Gatewayに頼らずもっと簡単にサーバーレスしたい。
そこで、Mangum とLambdaのFunctionURLを使い、Lambda単体でBackend APIをデプロイしてみましょう。
デプロイし、ブラウザでAPIを叩けたらゴールです。
Mangum
Mangum は、AWS Lambda でASGIアプリケーションを実行し、関数 URL、API Gateway、ALB、Lambda@Edge イベントを処理するためのアダプタ
ということです(原文訳)。
今回は関数URLからのイベントを処理するということですね。
構成
- python3
- Lambda + FastAPI + Docker
コード
py
ファイルはこれだけです。
from fastapi import FastAPI
from starlette.middleware.cors import CORSMiddleware
from mangum import Mangum
_app = FastAPI()
@_app.get("/health", include_in_schema=True)
async def health():
print("Called")
return "ok"
app = CORSMiddleware(
app=_app,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# これだけ
handler = Mangum(app, lifespan="off")
Mangum独自の実装は、FastAPIインスタンスをWrapする1行だけ。
超簡単ですね。
※ガバガバなCORS入れてますが、適宜ちゃんと設定しましょう。
fastapi
mangum
starlette
DockerファイルにはエントリポイントとしてMangumインスタンスのmain.handler
を指定します。
FROM public.ecr.aws/lambda/python:3.10
RUN yum -y install git
RUN mkdir -p -m 0600 ~/.ssh && ssh-keyscan github.com >> ~/.ssh/known_hosts
COPY requirements.txt .
RUN --mount=type=ssh pip3 install -r requirements.txt --target "${LAMBDA_TASK_ROOT}"
# Copy function code
COPY . ${LAMBDA_TASK_ROOT}
# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
CMD [ "main.handler" ] # これ
LambdaとFunction URLの作成
terraformはこんな感じです(本筋から外れるresourceは省略。GitHubを参照)。
aws_lambda_function_url
でFunction URLを生成します。
aws_lambda_permission
でリソースベースのポリシーを付与するのがミソです。
resource "aws_lambda_function" "api" {
function_name = "lambda_minimum_serverless_api"
role = aws_iam_role.api.arn
package_type = "Image"
memory_size = "512"
timeout = "60"
image_uri = "${local.aws_account_id}.dkr.ecr.${local.region}.amazonaws.com/${aws_ecr_repository.api.name}:latest"
}
resource "aws_lambda_function_url" "api_url" {
function_name = aws_lambda_function.api.function_name
authorization_type = "NONE"
cors {
allow_credentials = false
allow_headers = [
"*",
]
allow_methods = [
"*",
]
allow_origins = [
"*",
]
expose_headers = [
"*",
]
max_age = 0
}
}
resource "aws_lambda_permission" "api_url" {
statement_id = "FunctionURLAllowPublicAccess"
action = "lambda:InvokeFunctionUrl"
function_name = aws_lambda_function.api.arn
function_url_auth_type = "NONE"
principal = "*"
}
output "api_url" {
value = aws_lambda_function_url.api_url.function_url
}
デプロイ
Dockerイメージを生成し、Lambdaへデプロイしましょう。
AWS ECRにイメージをpushし、そこからデプロイします。
ECRのリポジトリ名はapps/sample/api
とします。
ECRへのpush
イメージをビルドしましょう。
$ tar -cJh . | docker build -t apps/sample/api:latest -
ECR用のtagをつけます。
YOUR_ACCOUNT_IDはご自分のAWSアカウントIDを指定してください。
$ docker tag apps/sample/api ${YOUR_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/apps/sample/api
イメージができたので、ECRにpushします。
$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${YOUR_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com
$ docker push ${YOUR_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/apps/sample/api
Using default tag: latest
The push refers to repository [*************.dkr.ecr.ap-northeast-1.amazonaws.com/apps/sample/api]
3081d709ee8a: Pushed
...
latest: digest: sha256:d12aa06e6966509f877d5da4b61b0d87b68a6fab21409b7492f81714a7fa3cbe size: ****
sha256はLambdaをデプロイする時に使いますので控えておきます。
Lambdaデプロイ
AWS CliからLambdaをupdateします。
--image-uri
の指定で、上記のsha256を指定してください。
$ aws lambda update-function-code --region ap-northeast-1 --function-name lambda_minimum_serverless_api --image-uri ${YOUR_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/apps/sample/api@sha256:d12aa06e6966509f877d5da4b61b0d87b68a6fab21409b7492f81714a7fa3cbe
デプロイ完了です!
動作確認
ブラウザからdocsにアクセスしてみましょう!
https://********************************.lambda-url.ap-northeast-1.on.aws/docs
URLを見て頂くと分かる通り、LambdaのFunction URLにアクセスしています。
Lambda単独で、SwaggerUIからFastAPIで定義した/health
が打てています!
Yeah!!!
まとめ
Mangumを使ってLambda + FastAPI + Dockerで最小限&サーバーレスなAPIをデプロイし、ブラウザからAPIを打てることを確認しました。
API GatewayはCognitoとラクに連携できたり、APIのライフサイクル管理ができたりと多機能ですが、
「シンプルに何かのAPIを試したい」、「PoCやMVP用にAPIを作りたい」のような時は、今回のようなLambda単独の最小限な構成でAPIを作れると、とても便利ですね。
GitHub
この記事のソースコードは全てこちらに置いてます。
terraformのコードもあるのでご参考に。