0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【簡単3ステップ】Terraform で AWS Lambda + API Gateway + Docker (FastAPI) を自動デプロイ

Posted at

はじめに

この記事では、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 アプリケーションを自動デプロイする方法を紹介しました:

  1. Docker で API のイメージをビルド
  2. Terraform でラムダ+API Gateway+イメージのプッシュを自動で実行
  3. 環境変数に API URL と API KEY の設定

この方法を活用することで、インフラストラクチャのプロビジョニングを自動化し、API キーで保護されたサーバーレス API を効率的にデプロイすることができます。また、クライアントスクリプトを使用することで、デプロイした API の動作を簡単に確認することができます。

最後に、本番環境での利用時にはさらなるセキュリティ設定やエラーハンドリングなど、追加の考慮が必要な点にご注意ください。

リポジトリ

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?