1.はじめに
1.1.はじめに
前回 Lambda コンテナのハンズオンを実施しました。
こちらの、コンテナイメージをベースにどのような変更を加えれば ECSでデプロイ出来るのかと気になり、
本ハンズオンを実施しようと考えました。
Lambda コンテナのコードをコメントアウトしながら比較しますが、基本前回の構築は不要です。
参考として、以下 GitHub に前回構築したリポジトリへのリンクを記載します。
- 前回のリポジトリ GitHub – AWS Lambda コンテナデプロイサンプル
1.2.Amazon ECSとは?
- AWS上でDockerコンテナを簡単にデプロイ・管理・スケーリングできる、フルマネージド型のコンテナオーケストレーションサービス
1.3.AWS Fargateとは?
- Amazon ECSの実行基盤の1つである、ECSの実行基盤としては以下 2種類がある。
項番 | 種類 | メリット | デメリット |
---|---|---|---|
1 | EC2 | EC2インスタンスを自身で管理するため柔軟な設定が可能 | 管理コストがかかる |
2 | Fargate | AWSが完全管理 | 柔軟さが低い |
1.4.本ブログの構築イメージ
- 本ブログの構成を記載する。
2.ハンズオン
2.1.前提
2.1.1.実行環境
- AWS CloudShell 環境
- Docker、AWS CLIはプリインストール済み
- リージョン:バージニア北部
- Amazon ECS/AWS Fargate
2.2.アプリケーションコード作成
2.2.1.LambdaとECSとのアプリケーションコード比較
クリック で コード表示
凡例
■:どちらにも存在しているが書き方が異なる(同じだが追加してる部分)
●:ECS だけにしか存在しない記載
★:同内容の記載
Lambda:
|
ECS:
|
2.2.2.ECSで必要になった定義について
2.2.2.1.FastAPIについて
- Lambdaと異なり、HTTPリクエストの受信・処理・レスポンス返却機能を実装する必要がある
- FastAPIは、HTTPリクエストとPython関数を結びつけるフレームワークとして機能
2.2.2.2.リクエストモデル定義について
- Lambdaでは任意の形式のeventを自由に受け取れたが、ECSではリクエストボディの形式を明示的に定義する必要がある
- 送信可能なデータ構造を明確にするために定義
2.2.2.3.ヘルスチェックエンドポイントについて
- ECSサービスの運用に必須の機能で、コンテナが正常に起動・稼働しているかを継続的に監視
2.2.2.4.HTTPサーバ起動について
- LambdaはAWSがリクエスト時のみ実行する構造だが、ECSは継続的に稼働しているため、リクエストを常時待ち受ける実装が必須
2.2.3.app.py作成
Hello from {message}
を返す ECS
# プロジェクトディレクトリに移動
mkdir my-python-ecs && cd my-python-ecs
# アプリケーション作成
cat > app.py << EOF
import json
from datetime import datetime
import pytz
from fastapi import FastAPI # Webフレームワーク用
from pydantic import BaseModel # リクエスト検証用
# FastAPI初期化
app = FastAPI()
# リクエストモデル定義
class EventModel(BaseModel):
message: str = "Default message from ECS!"
key: str = ""
# ヘルスチェックエンドポイント
@app.get("/health")
async def health_check():
return {"status": "healthy"}
# 関数定義
@app.post("/invoke")
async def invoke(event: EventModel):
# 処理内容
# 東京時間の取得
tokyo_tz = pytz.timezone('Asia/Tokyo')
current_time = datetime.now(tokyo_tz).strftime('%Y-%m-%d %H:%M:%S %Z')
# メッセージ取得
Emessage = event.message # pydanticモデルとして直接アクセス
#レスポンス
return {
'statusCode': 200,
'body': {
'greeting': f'Hello from {event.message}!',
'event': event.dict(),
'requested_at': datetime.now(tokyo_tz).isoformat(),
'display_time': current_time,
'server_type': 'ECS', # ECSであることを明示
}
}
# HTTPサーバ起動
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8080)
EOF
2.2.4.requirements.txt作成
- 新たに追加されたものに関してコメントを記載する
# 必要パッケージを記載
cat > requirements.txt << EOF
pytz # ★同内容タイムゾーン処理に特化したライブラリ
fastapi # ●ECSのみ:HTTPサーバーフレームワーク
uvicorn # ●ECSのみ:ASGIサーバー(FastAPI実行用)
pydantic # ●ECSのみ:データバリデーション用 (FastAPIが依存)
requests # ●ECSのみ:DockerfileのHEALTHCHECKで使用
EOF
2.2.5.LambdaとECSとの Dockerfile比較
クリック で コード表示
凡例
■:どちらにも存在しているが書き方が異なる(同じだが追加してる部分)
●:ECS だけにしか存在しない記載
★:同内容の記載
Lambda:
|
ECS:
|
2.2.6.ECSで必要になった定義について
2.2.6.1.作業ディレクトリ設定について
- Lambdaは専用イメージで暗黙的に設定されているが、ECSは明示的に設定が必要(柔軟性が高い)
2.2.6.2.ECS ヘルスチェックについて
- Dockerコンテナの状態を定期的に確認する機能
- ヘルスチェックエンドポイント(http://localhost:8080/health)にアクセスし、レスポンスを確認する
2.2.7.Dockerfile作成
cat > Dockerfile << EOF
# 汎用的なPythonイメージを使用(Lambda特化じゃない)
FROM public.ecr.aws/docker/library/python:3.11-slim
# 作業ディレクトリを設定
WORKDIR /app
# アプリケーションをコピー
COPY app.py requirements.txt ./
# 依存関係をインストール
RUN pip install --no-cache-dir -r requirements.txt
# コンテナ起動時に実行するコマンド
CMD ["python", "app.py"]
# ECSヘルスチェック用
HEALTHCHECK --interval=30s --timeout=10s \
CMD python -c "import requests; requests.get('http://localhost:8080/health').raise_for_status()"
EOF
2.3.ECR作成
2.3.1.ECRリポジトリ作成
# 変数設定
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGION=us-east-1
ECR_REPO_NAME="lambda-to-ecs"
ECR_IMAGE_URI=$ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$ECR_REPO_NAME
# ECRリポジトリ作成
aws ecr create-repository --repository-name $ECR_REPO_NAME --region $REGION
# ECRにログイン
aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin $ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com
2.4.Dockerイメージ作成
2.4.1.ビルドとプッシュ手順
# Dockerイメージビルド
docker build -t $ECR_IMAGE_URI:latest .
# ECRにプッシュ
docker push $ECR_IMAGE_URI:latest
2.5.Amazon ECSの設定
2.5.1.ECSクラスター作成
- Amazon ECSでタスクやサービスを実行する
コンテナ実行基盤をまとめる論理グループ
の作成
# 変数設定
CLUSTER="lambda-to-ecs-cluster"
# ECSクラスター作成
aws ecs create-cluster --cluster-name $CLUSTER
2.5.2.IAMロール(TaskExecutionRole)作成
2.5.2.1.IAMロールの違いについて
- ECSには以下2種類のIAMロールがあり、今回は
TaskExecutionRole(タスク実行ロール)
のみを作成する。 - 今回のコンテナ(Fargate)は、AWSサービスの権限が必要な挙動はないため
ecsTaskRole(タスクロール)
は作成していない。
項番 | ロールの種類 | アタッチ先 | 概要 |
---|---|---|---|
1 | ecsTaskExecutionRole(タスク実行ロール) | ECS | ECSがコンテナを起動する際に使用 CloudWatch Logsに書き込み等の権限 |
2 | ecsTaskRole(タスクロール) | コンテナ(Fargate, EC2) | コンテナ内のアプリが実行中に使用 AWSサービス(S3やDynamoDBなど)の権限 |
2.5.2.2.IAM((ecsTaskExecutionRole(タスク実行ロール))作成
# 変数設定
ROLE_NAME="ecsTaskExecutionRole"
# IAMロール作成
aws iam create-role --role-name $ROLE_NAME --assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ecs-tasks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}'
# ポリシーをアタッチ
aws iam attach-role-policy --role-name $ROLE_NAME --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
# 実行ロールARNを変数に保存
EXECUTION_ROLE_ARN="arn:aws:iam::${ACCOUNT_ID}:role/${ROLE_NAME}"
2.5.3.タスク定義(Fargate用)作成
2.5.3.1.タスク定義について
- コンテナの設定や起動方法などを記述した
コンテナの設計書
の作成 2.2.アプリケーションコード作成
の Dockerfileは「アプリケーション本体の基盤の設計図」で、本設定は「その基盤を載せるためのサーバの設計図」である- 上記を分けることにより
アプリケーション
とインフラ
の設定を切り分けることが可能となる
2.5.3.2.タスク定義の設定内容
項番 | 項目 | 設定値 | 説明 |
---|---|---|---|
1 | family | lambda-to-ecs-task | タスク定義のグループ名(リビジョン管理用) |
2 | networkMode | awsvpc | Fargate必須。タスクごとに独立したENIを割り当て |
3 | requiresCompatibilities | FARGATE | Fargateで実行することを明示 |
4 | cpu | 256 | CPUユニット(0.25 vCPU) |
5 | memory | 512 | メモリサイズ(MB) |
6 | containerDefinitions.name | lambda-to-ecs-container | コンテナ名 |
7 | containerDefinitions.image | ${ECR_IMAGE_URI} | 使用するDockerイメージ |
8 | containerDefinitions.essential | true | このコンテナがタスクの必須コンポーネント |
9 | portMappings.containerPort | 8080 | コンテナが公開するポート番号 |
10 | portMappings.protocol | tcp | プロトコル種類 |
11 | healthCheck.command | ["CMD-SHELL", "python -c \"import requests; requests.get(‘http://localhost:8080/health’).raise_for_status()\""] | requestsライブラリを使用し、HTTPリクエストを送信 コンテナのヘルス状態を監視 |
12 | healthCheck.interval | 30 | ヘルスチェック間隔(秒) |
13 | healthCheck.timeout | 5 | ヘルスチェックタイムアウト(秒) |
14 | healthCheck.retries | 3 | 失敗時のリトライ回数 |
2.5.3.3.タスク定義作成
# タスク定義JSONを作成
cat > lambda-to-ecs-task-definition.json << EOF
{
"family": "lambda-to-ecs-task",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"executionRoleArn": "${EXECUTION_ROLE_ARN}",
"containerDefinitions": [
{
"name": "lambda-to-ecs-container",
"image": "${ECR_IMAGE_URI}:latest",
"essential": true,
"portMappings": [
{
"containerPort": 8080,
"protocol": "tcp"
}
],
"healthCheck": {
"command": ["CMD-SHELL", "python -c \"import requests; requests.get('http://localhost:8080/health').raise_for_status()\""],
"interval": 30,
"timeout": 5,
"retries": 3
}
}
]
}
EOF
2.5.4.コンテナ(Fargate)基盤のタスク定義 登録
# グループとタスク定義ファイルの紐づけ
aws ecs register-task-definition --cli-input-json file://lambda-to-ecs-task-definition.json
2.5.5.SG(コンテナ(タスク)毎の)作成
- タスクごとに独立したENI(ネットワークインターフェース)に付与される
- 今回 PoCのため
0.0.0.0/0(全開放)
からport 8080
へのアクセスを許可している(※ 本番環境では特定のセキュリティグループのみを許可するような設計ください)
# 変数設定
SG_NAME="ecs-sg"
# セキュリティグループ作成
SG_ID=$(aws ec2 create-security-group --group-name $SG_NAME --description "ECS Security Group" --query 'GroupId' --output text)
# 8080ポート開放
aws ec2 authorize-security-group-ingress --group-id $SG_ID --protocol tcp --port 8080 --cidr 0.0.0.0/0
2.5.6.ECS サービス作成
# サブネットID取得(ECSサービスの実行サブネット)
SUBNET_IDS=$(aws ec2 describe-subnets --query 'Subnets[*].SubnetId' --output text | tr '\t' ',')
# ECS サービス作成
aws ecs create-service \
--cluster $CLUSTER \
--service-name lambda-to-ecs-service \
--task-definition lambda-to-ecs-task \
--desired-count 1 \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[$SUBNET_IDS],securityGroups=[$SG_ID],assignPublicIp=ENABLED}"
- 2回目以降(タスク定義を更新した場合など)は、以下コマンドで実施
# (2回目以降)コマンド
aws ecs update-service \
--cluster $CLUSTER \
--service lambda-to-ecs-service \
--task-definition lambda-to-ecs-task \
--force-new-deployment
2.6.挙動確認
2.6.1.コマンド実行
# ECSタスクのIPアドレスを取得
TASK_ARN=$(aws ecs list-tasks --cluster lambda-to-ecs-cluster --service-name lambda-to-ecs-service --query 'taskArns[0]' --output text)
ENI_ID=$(aws ecs describe-tasks --cluster lambda-to-ecs-cluster --tasks $TASK_ARN --query 'tasks[0].attachments[0].details[?name==`networkInterfaceId`].value' --output text)
PUBLIC_IP=$(aws ec2 describe-network-interfaces --network-interface-ids $ENI_ID --query 'NetworkInterfaces[0].Association.PublicIp' --output text)
# テストイベントを作成
echo '{"key": "value", "message": "ECS on Fargate"}' > test-event.json
# ECSテスト
curl -X POST http://$PUBLIC_IP:8080/invoke \
-H "Content-Type: application/json" \
-d @test-event.json \
-o response.json
2.6.2.実行結果
2.6.2.1.コンソールの出力
項番 | 表示名 | 詳細内容 |
---|---|---|
1 | % Total | 転送の全体的な進捗パーセンテージ(100%完了) |
2 | % Received | 受信したデータのパーセンテージ(229バイト、100%) |
3 | % Xferd | 送信したデータのパーセンテージ(45バイト、100%) |
4 | Average Speed Dload | ダウンロード平均速度(43199バイト/秒) |
5 | Average Speed Upload | アップロード平均速度(8488バイト/秒) |
6 | Time Total/Spent/Left | 合計時間/経過時間/残り時間(すべて0秒) |
7 | Speed | 現在の速度(54800バイト/秒) |
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 274 100 229 100 45 43199 8488 --:--:-- --:--:-- --:--:-- 54800
2.6.2.2.response.jsonの内容
- 適宜改行して記載
- messageが「ECS on Fargate」となっていることが確認できる
- keyに「value」が設定されてることが確認できる
- server_typeは「ECS」になってることが確認できる
{
"statusCode": 200,
"body": {
"greeting": "Hello from ECS on Fargate!",
"event": {
"message": "ECS on Fargate",
"key": "value"
},
"requested_at": "2025-05-06T21:30:09.060311+09:00",
"display_time": "2025-05-06 21:30:09 JST",
"server_type": "ECS"
}
}
3.クリーンアップ
# ECSリソース削除
aws ecs delete-service --cluster lambda-to-ecs-cluster --service lambda-to-ecs-service --force
aws ecs delete-cluster --cluster lambda-to-ecs-cluster
# ECRイメージ削除
aws ecr delete-repository --repository-name lambda-to-ecs --force
# IAMロール削除
# ポリシーをデタッチ
aws iam detach-role-policy --role-name $ROLE_NAME --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
# ロールを削除
aws iam delete-role --role-name $ROLE_NAME
# セキュリティグループ削除
aws ec2 delete-security-group --group-id $SG_ID
4. 終わりに
4.1. 得られた知見
-
同じコンテナでも実行環境で大きな違いがある
- Lambda は完全なイベントドリブン
- ECS は HTTP サーバーとしての継続動作が必要
-
使い分けの目安
- Lambda を選ぶ場合:
- 短時間・不定期な処理
- 運用コストを最小化したい
- ECS を選ぶ場合:
- 常時稼働が必要なサービス
- より細かいリソース制御が必要
4.2.今後の課題
- CI/CDパイプラインの構築
- Task Role を使用した他のAWSサービスとの連携
4.3.サンプルコード
本ハンズオンで使用したコードは以下のリポジトリで公開しています: