はじめに
本記事では、Amazon DynamoDB・ECS Fargate・API Gatewayを組み合わせたサーバーレス指向のAPIバックエンドを、ゼロから構築する手順を解説します。
従来のEC2+RDS構成と比較したとき、この組み合わせの最大のメリットはインフラ管理コストの削減にあります。EC2ではOSパッチ・スケーリング設定・RDSではパラメータグループ管理など、アプリケーション本体と無関係な運用タスクが多く発生します。一方、Fargate+DynamoDBであれば、サーバーのプロビジョニングやデータベースサーバーのメンテナンスから解放され、コードとデータ設計に集中できます。
この記事を読み終えると、PythonアプリをコンテナイメージとしてECRにプッシュし、Fargate上で稼働させ、API Gateway経由でHTTPSリクエストを受け付け、DynamoDBにデータを書き込む一連のパイプラインを自分で構築できるようになります。
アーキテクチャ概要
各サービスの役割
| サービス | 役割 | この構成での位置づけ |
|---|---|---|
| ACM | SSL/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イメージの保管場所 |
| DynamoDB | NoSQLデータストア | アプリが書き込むデータの永続化先 |
リクエストフロー(テキスト図)
クライアント (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_1、YOUR_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の
imageURIが誤っている(アカウント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タスクが起動していない
- 対処:以下の順で確認してください
- ECSコンソールでタスクのステータスが
RUNNINGであることを確認 - EC2コンソールでALBのリスナー(ポート80)が sample-tg へ転送する設定になっているか確認
- 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ポリシーを、必要なアクションのみのカスタムポリシーに切り替える


コメント