Python × AWS三銃士(DynamoDB・ECS Fargate・API Gateway)でサーバーレスAPIを0から本番構築する

目次

はじめに

本記事では、Amazon DynamoDB・ECS Fargate・API Gatewayを組み合わせたサーバーレス指向のAPIバックエンドを、ゼロから構築する手順を解説します。

従来のEC2+RDS構成と比較したとき、この組み合わせの最大のメリットはインフラ管理コストの削減にあります。EC2ではOSパッチ・スケーリング設定・RDSではパラメータグループ管理など、アプリケーション本体と無関係な運用タスクが多く発生します。一方、Fargate+DynamoDBであれば、サーバーのプロビジョニングやデータベースサーバーのメンテナンスから解放され、コードとデータ設計に集中できます。

この記事を読み終えると、PythonアプリをコンテナイメージとしてECRにプッシュし、Fargate上で稼働させ、API Gateway経由でHTTPSリクエストを受け付け、DynamoDBにデータを書き込む一連のパイプラインを自分で構築できるようになります。

アーキテクチャ概要

各サービスの役割

サービス役割この構成での位置づけ
ACMSSL/TLS証明書の発行・管理(HTTPS通信を可能にする)API Gateway(または ALB)で HTTPS を有効化するための証明書を提供
→ クライアントと API Gateway 間の通信を暗号化するために必須
API Gateway (HTTP API)HTTPSエンドポイントの提供・ルーティングクライアントからのリクエストを受け付ける入口
ALB (Application Load Balancer)トラフィックをコンテナへ転送API GatewayとFargateタスクの橋渡し(VPC Link経由)
ECS Fargateコンテナの実行基盤PythonアプリをサーバーレスなコンテナとしてホスティNG
ECRコンテナイメージのレジストリビルドしたDockerイメージの保管場所
DynamoDBNoSQLデータストアアプリが書き込むデータの永続化先

リクエストフロー(テキスト図)

クライアント (curl等)
    │  HTTPS POST /items
    ▼
API Gateway (HTTP API)
    │  VPC Link 経由
    ▼
ALB (Application Load Balancer)
    │  HTTP ポート80 転送
    ▼
ECS Fargate タスク (Pythonアプリ)
    │  DynamoDB SDK 書き込み
    ▼
DynamoDB (SampleTable)

これは最小構成です。本番運用では、WAFをAPI Gatewayの前段に置くこと、Fargateのオートスケーリング設定、DynamoDBのオンデマンドキャパシティの監視設定などが追加で必要になります。

前提条件

ローカル環境

本手順では、以下を実行できる環境が必要で、本記事ではAlmaLinux9で実施した手順を載せています。

  • AWS CLI:v2.x(aws --version で確認)
  • Podman:1.x以上(本記事ではDockerの代わりにPodmanを使用)
  • Python:3.9以上(アプリコード確認用)
  • テキストエディタ:vi / vim / VSCode等

使用リージョン

本記事では ap-northeast-1(東京リージョン)を使用します。コマンド内の --region ap-northeast-1 はすべてこれに統一してください。

構築手順

Step 1:DynamoDBテーブルの作成

アプリが書き込むデータの永続化先となるテーブルを作成します。PAY_PER_REQUEST(オンデマンド)にすることでキャパシティ管理が不要になります。

aws dynamodb create-table \
  --table-name SampleTable \
  --attribute-definitions AttributeName=id,AttributeType=S \
  --key-schema AttributeName=id,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST \
  --region ap-northeast-1

Step 2:ECSタスク実行ロールの作成

Fargateタスクがリソースにアクセスするために必要なIAMロールを作成します。信頼ポリシーで ecs-tasks.amazonaws.com のみを許可することで最小権限を実現します。

信頼ポリシーファイルを作成します。

cat << 'EOF' > trust.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "Service": "ecs-tasks.amazonaws.com" },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

ロールを作成します。

aws iam create-role \
  --role-name ecsTaskRoleSample \
  --assume-role-policy-document file://trust.json

タスクがDynamoDBに書き込めるよう、マネージドポリシーをアタッチします。

aws iam attach-role-policy \
  --role-name ecsTaskRoleSample \
  --policy-arn arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess

⚠️ 注意:上記は手順を簡略化するためにFullAccessを使用していますが、本番環境では dynamodb:PutItem など必要なアクションのみを許可するカスタムポリシーを作成してください。

Step 3:アプリケーションコードの作成

DynamoDBへ書き込みを行うPythonアプリとそのコンテナ設定を用意します。

mkdir app && cd app

app.py(Flaskを使ったシンプルなAPIサーバー)

cat << 'EOF' > app.py
import json
import uuid
import boto3
from flask import Flask, request, jsonify

app = Flask(__name__)
dynamodb = boto3.resource('dynamodb', region_name='ap-northeast-1')
table = dynamodb.Table('SampleTable')

@app.route('/items', methods=['POST'])
def create_item():
    body = request.get_json()
    item_id = str(uuid.uuid4())
    table.put_item(Item={'id': item_id, 'data': body.get('data', '')})
    return jsonify({'id': item_id, 'message': 'created'}), 201

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=80)
EOF

requirements.txt

cat << 'EOF' > requirements.txt
flask
boto3
EOF

Dockerfile

cat << 'EOF' > Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 80
CMD ["python", "app.py"]
EOF

Step 4:Podmanのインストール

コンテナイメージのビルド・プッシュに使用するPodmanをインストールします。
(DockerデーモンなしでコンテナビルドできるのがPodmanの特徴です。)

sudo dnf install -y podman

⚠️ 注意:AlmaLinux9を想定しています。Ubuntu等を使用している場合は apt-get install podman に読み替えてください。

Step 5:ECRリポジトリの作成とイメージのプッシュ

ビルドしたコンテナイメージをECS Fargateが取得できるよう、ECRに保管します。

ECRリポジトリを作成します。

aws ecr create-repository --repository-name sample-app --region ap-northeast-1

ECRへログインします。

# YOUR_ACCOUNT_ID を自分のAWSアカウントIDに置き換えてください
aws ecr get-login-password --region ap-northeast-1 | \
  podman login --username AWS --password-stdin \
  YOUR_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com

イメージをビルドし、タグ付けしてプッシュします。

# appディレクトリにいることを確認してから実行
podman build -t sample-app .

podman tag sample-app:latest \
  {YOUR_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/sample-app:latest

podman push \
  {YOUR_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/sample-app:latest
  • 変更が必要な箇所YOUR_ACCOUNT_ID(12桁のAWSアカウントID)

⚠️ 注意(最多ハマりポイント):ECRへのログインを忘れたままプッシュしようとすると unauthorized: authentication required エラーになります。ビルド後、プッシュ前に必ずログイン済みであることを確認してください。

Step 6:ECSクラスターとタスク定義の作成

Fargateタスクを実行するクラスターを作成し、コンテナの起動設定(タスク定義)を登録します。

ECSクラスターを作成します。

aws ecs create-cluster --cluster-name sample-cluster --region ap-northeast-1

タスク定義ファイルを作成します。

cat << 'EOF' > task.json
{
  "family": "sample-task",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512",
  "executionRoleArn": "arn:aws:iam::{YOUR_ACCOUNT_ID}:role/ecsTaskRoleSample",
  "taskRoleArn": "arn:aws:iam::{YOUR_ACCOUNT_ID}:role/ecsTaskRoleSample",
  "containerDefinitions": [
    {
      "name": "app",
      "image": "{YOUR_ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/sample-app:latest",
      "portMappings": [
        { "containerPort": 80, "protocol": "tcp" }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/sample-task",
          "awslogs-region": "ap-northeast-1",
          "awslogs-stream-prefix": "ecs"
        }
      }
    }
  ]
}
EOF
  • 変更が必要な箇所YOUR_ACCOUNT_ID(2箇所)
  • 固定でよい箇所cpu: 256 / memory: 512(開発用最小構成。本番では要見直し)

⚠️ 注意executionRoleArn(ECRからイメージを取得する権限)と taskRoleArn(アプリがDynamoDBにアクセスする権限)は別物です。今回は同じロールを使用していますが、本番では分離することを推奨します。

CloudWatch Logsのロググループを事前に作成しておきます。

aws logs create-log-group --log-group-name /ecs/sample-task --region ap-northeast-1

タスク定義を登録します。

aws ecs register-task-definition --cli-input-json file://task.json --region ap-northeast-1

Step 7:VPCとALBの準備

FargateタスクをプライベートネットワークALBで受け取る構成を作ります。API GatewayのVPC Linkを使ってALBと接続します。

⚠️ 注意:この手順はAWSコンソールとCLIが混在します。CLIで取得したリソースIDをコンソールで使う場面があります。IDは都度メモしてください。

デフォルトVPCが存在しない場合は作成します(すでにある場合はスキップ)。

aws ec2 create-default-vpc --region ap-northeast-1

サブネットIDを確認します(後続のコマンドで使用)。

aws ec2 describe-subnets \
  --filters "Name=defaultForAz,Values=true" \
  --query "Subnets[*].[SubnetId,AvailabilityZone]" \
  --output table \
  --region ap-northeast-1

内部向けALBを作成します(SUBNET_ID_1SUBNET_ID_2は上記で確認したIDで置き換え)。

aws elbv2 create-load-balancer \
  --name sample-alb \
  --subnets {SUBNET_ID_1} {SUBNET_ID_2} \
  --scheme internal \
  --type application \
  --region ap-northeast-1

VPC IDを確認します。

aws ec2 describe-vpcs \
  --filters "Name=isDefault,Values=true" \
  --query "Vpcs[0].VpcId" \
  --output text \
  --region ap-northeast-1

ターゲットグループを作成します(YOUR_VPC_IDは上記で確認したIDで置き換え)。

aws elbv2 create-target-group \
  --name sample-tg \
  --protocol HTTP \
  --port 80 \
  --target-type ip \
  --vpc-id {YOUR_VPC_ID} \
  --region ap-northeast-1

Step 8:ECSサービスの作成

定義したタスクを指定した数だけ常時稼働させるECSサービスを作成します。サービスはタスクの起動・ヘルスチェック・再起動を自動管理します。

ECSサービスが使用するセキュリティグループのIDを確認します。

aws ec2 describe-security-groups \
  --filters "Name=group-name,Values=default" "Name=vpc-id,Values={YOUR_VPC_ID}" \
  --query "SecurityGroups[0].GroupId" \
  --output text \
  --region ap-northeast-1

ECSサービスを作成します。

aws ecs create-service \
  --cluster sample-cluster \
  --service-name sample-service \
  --task-definition sample-task \
  --desired-count 1 \
  --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={
    subnets=[SUBNET_ID_1],
    securityGroups=[YOUR_SG_ID],
    assignPublicIp=DISABLED
  }" \
  --region ap-northeast-1
  • 変更が必要な箇所SUBNET_ID_1YOUR_SG_ID
  • 固定でよい箇所assignPublicIp=DISABLED(ALB経由のアクセスのみ許可するためプライベートに設定)

⚠️ 注意:セキュリティグループはALBからポート80のインバウンドを許可する設定が必要です。デフォルトSGのルールを確認してください。

Step 9:API GatewayとVPC Linkの設定(コンソール操作中心)

外部からHTTPS接続できるエンドポイントを作成し、VPC内部のALBとAPI Gatewayを接続します。

HTTP APIを作成します。

aws apigatewayv2 create-api --name sample-api --protocol-type HTTP --region ap-northeast-1

VPC Linkを作成します(サブネットとセキュリティグループを指定)。

aws apigatewayv2 create-vpc-link \
  --name sample-vpc-link \
  --subnet-ids SUBNET_ID_1 SUBNET_ID_2 \
  --security-group-ids YOUR_SG_ID \
  --region ap-northeast-1

以降のルート・リスナー・統合・ACM証明書の設定はAWSコンソールから行います。

コンソール操作の手順(API Gateway → sample-api):

  • ルートの作成:「ルート」メニューで「ルートを作成」→ メソッド: POST / パス: /items
  • 統合の作成:作成したルートを選択 → 「統合を作成してアタッチ」→ 統合タイプ: ALB/NLB → Step 8で作成したVPC LinkとALBのリスナーARNを選択
  • ACM証明書:ACMコンソールでドメインの証明書をリクエスト(DNS検証推奨)
  • ALBリスナー作成:EC2コンソール → ロードバランサー → sample-alb → リスナーを追加 → HTTP:80、転送先: sample-tg

⚠️ 注意:VPC Linkのステータスが AVAILABLE になるまで数分かかります。PENDING のままで統合を設定しても接続できません。

動作確認

1. curlによるAPIテスト

API Gatewayのエンドポイントにリクエストを送り、正常に201レスポンスが返るか確認します。

# YOUR_API_ID を実際のAPI GatewayのIDに置き換えてください
curl -s https://YOUR_API_ID.execute-api.ap-northeast-1.amazonaws.com/items \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"data":"test"}' | jq .

期待するレスポンス:

{
  "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
  "message": "created"
}

2. DynamoDBへのデータ反映確認

APIが書き込んだデータが実際にDynamoDBに存在することを確認します。

aws dynamodb scan \
  --table-name SampleTable \
  --region ap-northeast-1

期待するレスポンス(一部省略):

{
  "Items": [
    {
      "id": { "S": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" },
      "data": { "S": "test" }
    }
  ],
  "Count": 1
}

3. CloudWatch Logsでのログ確認

Fargateタスクのアプリログを確認してエラーがないことを検証します。

aws logs tail /ecs/sample-task \
  --follow \
  --region ap-northeast-1

正常時は以下のようなFlaskのアクセスログが流れます。

172.31.x.x - - [01/May/2026 12:00:00] "POST /items HTTP/1.1" 201 -

以上で手順は終了です。うまくいかなかった場合は後続のトラブルシューティングをご確認ください。

トラブルシューティング

エラー 1:unauthorized: authentication required

  • 原因:ECRへのログインが完了していない、またはセッションが期限切れになっている
  • 対処aws ecr get-login-password コマンドを再実行してPodmanでログインしてからプッシュしてください

エラー 2:タスクが STOPPED になり起動しない

  • 原因:task.jsonの image URIが誤っている(アカウントIDの書き間違い、タグ指定ミス)
  • 対処aws ecs describe-tasks でstoppedReasonを確認。ECRのURIをコンソールからコピーして正確に貼り付けてください
aws ecs describe-tasks \
  --cluster sample-cluster \
  --tasks $(aws ecs list-tasks --cluster sample-cluster --query "taskArns[0]" --output text) \
  --region ap-northeast-1 \
  --query "tasks[0].stoppedReason"

エラー 3:ResourceNotFoundException: Requested resource not found(DynamoDB)

  • 原因:app.py内のテーブル名がStep 1で作成したテーブル名と不一致、またはリージョンが異なる
  • 対処:app.py内の Table('SampleTable')region_name がDynamoDB作成時の値と一致しているか確認してください

エラー 4:502 Bad Gateway(API Gatewayから)

  • 原因:ALBのリスナーが未設定、VPC LinkのステータスがPENDING、またはFargateタスクが起動していない
  • 対処:以下の順で確認してください
  1. ECSコンソールでタスクのステータスが RUNNING であることを確認
  2. EC2コンソールでALBのリスナー(ポート80)が sample-tg へ転送する設定になっているか確認
  3. API GatewayコンソールでVPC LinkのステータスがAVAILABLEになっているか確認

エラー 5:AccessDeniedException(CloudWatchへのログ書き込み失敗)

  • 原因:タスク実行ロール(executionRoleArn)にCloudWatch Logsへの書き込み権限がない
  • 対処ecsTaskRoleSampleロールに AmazonECSTaskExecutionRolePolicy マネージドポリシーをアタッチしてください
aws iam attach-role-policy \
  --role-name ecsTaskRoleSample \
  --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

まとめと次のステップ

今回の構築を通じて、「コンテナはFargateで実行し、DBアクセスはIAMロールで認証し、外部公開はAPI Gatewayに任せる」というAWSらしい責務分離の設計パターンを体験できました。各サービスが独立しているため、Fargateのタスク数を増やすだけでスケールアウトできる構成になっています。

本番運用に向けた次の改善点として、以下を検討してください。

  • セキュリティ強化:API GatewayにAWS WAFを追加してSQLインジェクション・レートリミットを設定する
  • オートスケーリング:ECS Service Auto Scalingを設定してCPU/メモリ使用率に応じてタスク数を自動調整する
  • CI/CD:CodePipelineとCodeBuildでイメージビルドからデプロイまでを自動化する
  • 監視:CloudWatch AlarmでALBの5xxエラー率・DynamoDBのThrottledRequestsをSNS通知に連携する
  • IAM最小権限化:今回のFullAccessポリシーを、必要なアクションのみのカスタムポリシーに切り替える
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次