【簡単3ステップ】TerraformでAWS Lambda+API Gateway + Docker (FastAPI) を自動デプロイ
はじめに
この記事では、AWS Lambda、API Gateway、そして Docker コンテナ化された FastAPI アプリケーションを Terraform を使用して自動デプロイする方法を紹介します。わずか3ステップで、API キーで保護されたサーバーレス API を構築できる方法をご覧いただけます。
前提条件
- AWS アカウント
- Terraform がインストールされていること
- Docker がインストールされていること
- AWS CLI がインストールされ、設定されていること
- Python 3.9 以上がインストールされていること
ステップ1: Docker で API のイメージをビルド
まず、FastAPI アプリケーションのコードと Dockerfile を作成します。
app.py
:
from fastapi import FastAPI
from mangum import Mangum
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
@app.get("/test")
async def test():
return {"message": "This is a test endpoint"}
# Mangum handler for AWS Lambda
handler = Mangum(app)
Dockerfile
:
FROM public.ecr.aws/lambda/python:3.9
WORKDIR /var/task
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD [ "app.handler" ]
requirements.txt
:
fastapi
mangum
次に、Docker イメージをビルドします:
docker build -t fastapi-lambda .
ステップ2: Terraform でラムダ+API Gateway+イメージのプッシュを自動で実行
Terraform の設定ファイルを作成し、AWS リソースとイメージのプッシュを自動化します。
main.tf
:
# AWSプロバイダーの設定
provider "aws" {
region = var.aws_region
}
# Lambda実行用のIAMロール
resource "aws_iam_role" "lambda_role" {
name = "fastapi_lambda_role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "lambda.amazonaws.com"
}
}
]
})
}
# Lambda実行用のIAMポリシー
resource "aws_iam_role_policy_attachment" "lambda_policy" {
role = aws_iam_role.lambda_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}
# ECRリポジトリ
resource "aws_ecr_repository" "fastapi_lambda_repo" {
name = "fastapi-lambda-repo"
force_delete = true
}
# Dockerイメージのビルドとプッシュを行うnull_resource
resource "null_resource" "docker_push" {
triggers = {
always_run = "${timestamp()}"
}
provisioner "local-exec" {
command = <<EOT
echo Building the Docker image
echo ${var.aws_region}
echo ${aws_ecr_repository.fastapi_lambda_repo.repository_url}
echo Logging in to ECR
aws ecr get-login-password --region ${var.aws_region} | docker login --username AWS --password-stdin ${aws_ecr_repository.fastapi_lambda_repo.repository_url}
echo Tagging the image
docker tag fastapi-lambda:latest ${aws_ecr_repository.fastapi_lambda_repo.repository_url}:latest
echo Pushing the image to ECR
docker push ${aws_ecr_repository.fastapi_lambda_repo.repository_url}:latest
Start-Sleep 10
EOT
interpreter = ["PowerShell", "-Command"]
}
depends_on = [aws_ecr_repository.fastapi_lambda_repo]
}
# Lambda関数
resource "aws_lambda_function" "fastapi_lambda" {
function_name = var.lambda_function_name
role = aws_iam_role.lambda_role.arn
package_type = "Image"
image_uri = "${aws_ecr_repository.fastapi_lambda_repo.repository_url}:latest"
environment {
variables = {
STAGE = var.stage
}
}
depends_on = [null_resource.docker_push]
}
# API Gateway (REST API)
resource "aws_api_gateway_rest_api" "lambda_api" {
name = "fastapi-lambda-api"
description = "FastAPI Lambda API"
}
# API Gatewayリソース
resource "aws_api_gateway_resource" "proxy" {
rest_api_id = aws_api_gateway_rest_api.lambda_api.id
parent_id = aws_api_gateway_rest_api.lambda_api.root_resource_id
path_part = "{proxy+}"
}
# API Gatewayメソッド (ANY)
resource "aws_api_gateway_method" "proxy_method" {
rest_api_id = aws_api_gateway_rest_api.lambda_api.id
resource_id = aws_api_gateway_resource.proxy.id
http_method = "ANY"
authorization = "NONE"
api_key_required = true
}
# Lambda関数のAPI Gateway呼び出し許可
resource "aws_lambda_permission" "api_gw" {
statement_id = "AllowAPIGatewayInvoke"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.fastapi_lambda.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.lambda_api.execution_arn}/*/*"
}
# API Gateway統合
resource "aws_api_gateway_integration" "lambda_integration" {
rest_api_id = aws_api_gateway_rest_api.lambda_api.id
resource_id = aws_api_gateway_resource.proxy.id
http_method = aws_api_gateway_method.proxy_method.http_method
integration_http_method = "POST"
type = "AWS_PROXY"
uri = aws_lambda_function.fastapi_lambda.invoke_arn
}
# API Gatewayデプロイ
resource "aws_api_gateway_deployment" "lambda_deployment" {
depends_on = [
aws_api_gateway_integration.lambda_integration,
]
rest_api_id = aws_api_gateway_rest_api.lambda_api.id
stage_name = var.stage
}
# CloudWatch Logs
resource "aws_cloudwatch_log_group" "api_gw" {
name = "/aws/api_gw/${aws_api_gateway_rest_api.lambda_api.name}"
retention_in_days = 30
}
# APIキーの作成
resource "aws_api_gateway_api_key" "fastapi_lambda_api_key" {
name = "fastapi-lambda-api-key"
}
# 使用量プランの作成
resource "aws_api_gateway_usage_plan" "fastapi_lambda_usage_plan" {
name = "fastapi-lambda-usage-plan"
description = "Usage plan for FastAPI Lambda API"
api_stages {
api_id = aws_api_gateway_rest_api.lambda_api.id
stage = aws_api_gateway_deployment.lambda_deployment.stage_name
}
quota_settings {
limit = 1000
offset = 0
period = "MONTH"
}
throttle_settings {
burst_limit = 5
rate_limit = 10
}
}
# 使用量プランとAPIキーの関連付け
resource "aws_api_gateway_usage_plan_key" "fastapi_lambda_usage_plan_key" {
key_id = aws_api_gateway_api_key.fastapi_lambda_api_key.id
key_type = "API_KEY"
usage_plan_id = aws_api_gateway_usage_plan.fastapi_lambda_usage_plan.id
}
# 出力
output "ecr_repository_url" {
description = "ECR URL"
value = aws_ecr_repository.fastapi_lambda_repo.repository_url
}
output "lambda_function_name" {
description = "Lambda関数名"
value = aws_lambda_function.fastapi_lambda.function_name
}
output "api_gateway_url" {
description = "API Gateway URL"
value = aws_api_gateway_deployment.lambda_deployment.invoke_url
}
output "fastapi_lambda_api_key" {
description = "FastAPI Lambda APIキー"
value = aws_api_gateway_api_key.fastapi_lambda_api_key.value
sensitive = true
}
この設定を適用するには、以下のコマンドを実行します:
terraform init
terraform apply
ステップ3: 環境変数に API URL と API KEY の設定
Terraform の実行が完了したら、生成された API URL と API キーを環境変数に設定します。.env
ファイルを作成し、以下の内容を記述します:
API_URL=https://XXXXX.execute-api.ap-northeast-1.amazonaws.com/dev
API_KEY=YYYYY
ここで、XXXXX
は Terraform の出力から得られる API Gateway の URL、YYYYY
は生成された API キーに置き換えてください。
動作確認
API の動作を確認するために、クライアントスクリプト lambda_tester_api.py
を使用します:
import requests
import os
from dotenv import load_dotenv
# .env ファイルから環境変数を読み込む
load_dotenv()
API_URL = os.getenv('API_URL')
API_KEY = os.getenv('API_KEY')
def invoke_lambda(path, http_method="GET"):
url = f"{API_URL}{path}"
headers = {
'x-api-key': API_KEY
}
try:
if http_method == "GET":
response = requests.get(url, headers=headers)
elif http_method == "POST":
response = requests.post(url, headers=headers)
else:
raise ValueError(f"Unsupported HTTP method: {http_method}")
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Lambda invocation error: {e}")
return None
def main():
paths = ["/", "/items/42", "/test"]
for path in paths:
result = invoke_lambda(path)
print(f"Response from {path}:")
print(result)
print()
if __name__ == "__main__":
main()
このスクリプトを実行して、API の各エンドポイントをテストします:
python lambda_tester_api.py
正常に動作していれば、各エンドポイントからのレスポンスが表示されます。
まとめ
この記事では、以下の3ステップで AWS Lambda、API Gateway、そして Docker コンテナ化された FastAPI アプリケーションを自動デプロイする方法を紹介しました:
- Docker で API のイメージをビルド
- Terraform でラムダ+API Gateway+イメージのプッシュを自動で実行
- 環境変数に API URL と API KEY の設定
この方法を活用することで、インフラストラクチャのプロビジョニングを自動化し、API キーで保護されたサーバーレス API を効率的にデプロイすることができます。また、クライアントスクリプトを使用することで、デプロイした API の動作を簡単に確認することができます。
最後に、本番環境での利用時にはさらなるセキュリティ設定やエラーハンドリングなど、追加の考慮が必要な点にご注意ください。
リポジトリ
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
Discussion