AWS上でECSを使用してGrafanaをデプロイする
実践例: Docker HubからGrafanaの公式イメージをプルして、ECS Fargateで実行し、ブラウザからアクセス可能な完全に動作するモニタリングダッシュボードを取得します。ネットワーク、IAM、サービスセットアップのすべてが最初から説明されています。
📌 何を構築するか?
┌─────────────────────────────────────────────────┐
│ AWS Cloud (your account) │
│ │
You (browser) │ ┌──────────────┐ │
http://\\<IP>:3000 ──┼───►│ Security │ │
│ │ Group :3000 │ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────▼───────────────────────────────┐ │
│ │ ECS Cluster (Fargate) │ │
│ │ │ │
│ │ ┌─────────────────────────────────┐ │ │
│ │ │ ECS Task (awsvpc mode) │ │ │
│ │ │ │ │ │
│ │ │ ┌───────────────────────────┐ │ │ │
│ │ │ │ grafana/grafana:latest │ │ │ │
│ │ │ │ (from Docker Hub) │ │ │ │
│ │ │ │ Port 3000 │ │ │ │
│ │ │ └───────────────────────────┘ │ │ │
│ │ │ │ │ │
│ │ │ Task Execution Role │ │ │
│ │ │ → pulls image │ │ │
│ │ │ → writes CloudWatch logs │ │ │
│ │ └─────────────────────────────────┘ │ │
│ └──────────────────────────────────────┘ │
│ │
│ VPC → Public Subnet → Internet Gateway │
└─────────────────────────────────────────────────┘
このガイドで説明する2つのアプローチ:
-
パスA — Docker Hubからパブリックイメージを直接プル(例:
grafana/grafana) — 最もシンプル、ECRは不要 -
パスB — 独自のイメージをECRにプッシュ(プライベートレジストリ)して、ECRからプル — カスタム/プライベートバックエンド用
🧱 ECSのコアコンセプト
ステップに進む前に、各要素の役割を説明します:
| コンポーネント | 定義 | 類例 |
| ECS Cluster | タスクとサービスの論理的なグループ | コンテナ用の「ワークスペース」 |
| Task Definition | コンテナのブループリント — イメージURI、CPU、メモリ、ポート、環境変数、IAMロール | AWSのdocker-compose.yml |
| Task | タスク定義の実行インスタンス | 実行中のdocker runコマンド |
| Service | 望ましいタスク数を管理、失敗時に再開、ロードバランサーと統合 | プロセス監視 |
| Fargate | サーバーレスコンピュートエンジン — EC2インスタンスの管理は不要 | AWSがコンテナを実行 |
| Task Execution Role | ECS エージェント がイメージをプルしログを書き込むことを許可するIAMロール | インフラストラクチャの権限 |
| Task Role | アプリケーションコード がAWS APIを呼び出すために使用するIAMロール | アプリの権限 |
📋 前提条件
-
管理者権限またはIAM権限を持つAWSアカウント
-
AWS CLIがインストール・設定されている(
aws configure) -
Dockerがローカルにインストールされている(パスBで必要)
-
パブリックサブネットとインターネットゲートウェイを持つVPC(デフォルトVPCで動作)
パスA — Docker Hubからパブリックイメージをプル(Grafana)
最もシンプルなアプローチです。ECS Fargateはリモートからパブリックイメージを直接プルできます — タスク定義でイメージ名を指定するだけです。ECRリポジトリは不要です。
ステップ1 — VPCを作成する(またはデフォルトVPCを使用)
すべてのAWSアカウントには、各リージョンにデフォルトVPCが付属しています。簡単なセットアップの場合、デフォルトVPCで十分です。 カスタムVPCを作成する場合:
-
VPCコンソール → VPCを作成
-
VPCおよび詳細 を選択(ウィザードが自動的にサブネット、ルートテーブル、IGWを作成)
-
以下を設定:
- 名前:
grafana-vpc - IPv4 CIDR:
10.0.0.0/16 - AZ数:
2 - パブリックサブネット:
2 - プライベートサブネット:
0(このシンプルなセットアップでは) - NAT Gateway:なし
- VPC Endpoints:なし
- 名前:
Fargateタスクがインターネットからイメージをプルするために必要な条件:
-
タスクが パブリックサブネット で実行される
-
サブネットのルートテーブルに
0.0.0.0/0 → Internet Gatewayのルートがある -
タスク起動時に パブリックIPの自動割り当て が
有効に設定されている -
または、タスクが プライベートサブネット で実行され、NAT Gateway へのルートがある
ステップ2 — セキュリティグループを作成
セキュリティグループは、Grafanaコンテナに到達できるトラフィックを制御します。
-
EC2コンソール → セキュリティグループ → セキュリティグループを作成
-
以下を設定:
| フィールド | 値 |
| 名前 | grafana-sg |
| 説明 | Grafana UIアクセスを許可 |
| VPC | VPCを選択 |
- インバウンドルール:
| タイプ | プロトコル | ポート | ソース | 説明 |
| カスタムTCP | TCP | 3000 | 0.0.0.0/0 | Grafana web UI |
- アウトバウンドルール: デフォルトのまま(すべてのトラフィックをアウトバウンド許可 — イメージプルとインターネットアクセスに必要)
AWS CLI:
# セキュリティグループを作成
SG_ID=$(aws ec2 create-security-group \
--group-name grafana-sg \
--description "Allow Grafana UI on port 3000" \
--vpc-id vpc-0abc123 \
--query 'GroupId' --output text)
# ポート3000のインバウンドルールを追加
aws ec2 authorize-security-group-ingress \
--group-id $SG_ID \
--protocol tcp \
--port 3000 \
--cidr 0.0.0.0/0
ステップ3 — ECSクラスターを作成
-
ECSコンソール → クラスター → クラスターを作成
-
以下を設定:
- クラスター名:
grafana-cluster - インフラストラクチャ:AWS Fargate を選択(デフォルト)
- クラスター名:
-
作成 をクリック それで終了です。クラスターは論理的なコンテナに過ぎず、サーバーはプロビジョニングされません。
AWS CLI:
aws ecs create-cluster --cluster-name grafana-cluster
ステップ4 — Task Execution IAM ロールを作成
Task Execution Role はECSエージェント(アプリケーションではなく)がイメージをプルしログを書き込むことを許可します。ECSを使用したことがあれば、おそらくecsTaskExecutionRoleがすでに存在します。
存在確認:
aws iam get-role --role-name ecsTaskExecutionRole
存在しない場合は作成:
-
IAMコンソール → ロール → ロールを作成
-
信頼されたエンティティ:AWSサービス → Elastic Container Service → ユースケース:Elastic Container Service Task
-
マネージドポリシーをアタッチ:
AmazonECSTaskExecutionRolePolicy -
ロール名:
ecsTaskExecutionRoleこのポリシーは以下を許可します:
-
ecr:GetAuthorizationToken— ECRに認証 -
ecr:BatchGetImage、ecr:GetDownloadUrlForLayer— ECRからイメージをプル -
logs:CreateLogStream、logs:PutLogEvents— CloudWatchにコンテナログを書き込み
AWS CLI:
# トラストポリシードキュメントを作成
cat > trust-policy.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "ecs-tasks.amazonaws.com" },
"Action": "sts:AssumeRole"
}]
}
EOF
# ロールを作成
aws iam create-role \
--role-name ecsTaskExecutionRole \
--assume-role-policy-document file://trust-policy.json
# マネージドポリシーをアタッチ
aws iam attach-role-policy \
--role-name ecsTaskExecutionRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
ステップ5 — タスク定義を作成
これはセットアップの中核です — ECSにどのイメージを実行するか、CPU/メモリをいくら割り当てるか、どのポートを公開するか、どのロールを使用するかを指定するブループリントです。
-
ECSコンソール → タスク定義 → 新しいタスク定義を作成
-
以下を設定:
| 設定 | 値 |
| タスク定義ファミリー | grafana-task |
| 起動タイプ | AWS Fargate |
| OS/アーキテクチャ | Linux/X86_64 |
| タスクサイズ — CPU | 0.5 vCPU (512) |
| タスクサイズ — メモリ | 1 GB (1024) |
| タスク実行ロール | ecsTaskExecutionRole |
| タスクロール | なし(GrafanaはAWS APIを呼び出す必要なし) |
- コンテナ定義:
| 設定 | 値 |
| コンテナ名 | grafana |
| イメージURI | grafana/grafana:latest |
| 必須 | はい |
| ポートマッピング | コンテナポート:3000、プロトコル:TCP |
- (オプション) 環境変数:
| キー | 値 | 目的 |
GF_SECURITY_ADMIN_USER | admin | デフォルト管理者ユーザー名 |
GF_SECURITY_ADMIN_PASSWORD | YourStrongPassword123! | デフォルトパスワードをオーバーライド |
- (オプション) ロギング — CloudWatch:
| 設定 | 値 |
| ログドライバー | awslogs |
| ログループ | /ecs/grafana |
| リージョン | お使いのリージョン(例:ap-northeast-1) |
| ストリームプリフィックス | grafana |
- 作成 をクリック
同等のJSONタスク定義:
{
"family": "grafana-task",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "512",
"memory": "1024",
"executionRoleArn": "arn:aws:iam::YOUR_ACCOUNT_ID:role/ecsTaskExecutionRole",
"containerDefinitions": [
{
"name": "grafana",
"image": "grafana/grafana:latest",
"essential": true,
"portMappings": [
{
"containerPort": 3000,
"protocol": "tcp"
}
],
"environment": [
{ "name": "GF_SECURITY_ADMIN_USER", "value": "admin" },
{ "name": "GF_SECURITY_ADMIN_PASSWORD", "value": "YourStrongPassword123!" }
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/grafana",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "grafana",
"awslogs-create-group": "true"
}
}
}
]
}
CLIから登録:
aws ecs register-task-definition --cli-input-json file://grafana-task-def.json
重要なポイント:
imageフィールドは単にgrafana/grafana:latestです — Docker Hubのパブリックイメージです。ECSはDocker Hubから直接プルします。パブリックイメージにはECRリポジトリは不要です。
ステップ6 — ECSサービスを作成(またはスタンドアロンタスクを実行)
オプションA — サービスとして実行(推奨)
サービス はGrafanaの実行を継続します。タスクがクラッシュした場合、ECSは自動的に新しいタスクを開始します。
-
ECSコンソール → クラスター →
grafana-cluster→ サービス → 作成 -
以下を設定:
| 設定 | 値 |
| 起動タイプ | Fargate |
| タスク定義 | grafana-task(最新リビジョン) |
| サービス名 | grafana-service |
| 望ましいタスク数 | 1 |
- ネットワーク:
| 設定 | 値 |
| VPC | VPCを選択 |
| サブネット | パブリック サブネットを選択 |
| セキュリティグループ | grafana-sg(ポート3000を許可するもの) |
| パブリックIP | 有効 (これは重要です!) |
- 作成 をクリック
AWS CLI:
aws ecs create-service \
--cluster grafana-cluster \
--service-name grafana-service \
--task-definition grafana-task \
--desired-count 1 \
--launch-type FARGATE \
--network-configuration '{
"awsvpcConfiguration": {
"subnets": ["subnet-0abc123"],
"securityGroups": ["sg-0def456"],
"assignPublicIp": "ENABLED"
}
}'
オプションB — スタンドアロンタスクを実行(クイックテスト)
aws ecs run-task \
--cluster grafana-cluster \
--task-definition grafana-task \
--launch-type FARGATE \
--network-configuration '{
"awsvpcConfiguration": {
"subnets": ["subnet-0abc123"],
"securityGroups": ["sg-0def456"],
"assignPublicIp": "ENABLED"
}
}'
ステップ7 — Grafanaにアクセス
-
ECSコンソール → クラスター →
grafana-cluster→ タスク タブ -
実行中のタスクIDをクリック
-
設定 の下にある パブリックIP を探す(例:
3.112.45.67) -
ブラウザを開いて以下にアクセス:
http://3.112.45.67:3000 -
以下でログイン:
- ユーザー名:
admin - パスワード:
admin(またはGF_SECURITY_ADMIN_PASSWORDで設定したもの)
- ユーザー名:
-
Grafanaは初回ログイン時にパスワード変更を促します これでGrafanaはECS Fargateで実行され、Docker Hubから直接プルされています。
パスB — 独自のイメージをECRにプッシュしてECSにデプロイ
カスタムバックエンド(独自のDockerfile)がある場合またはプライベートレジストリ を使用したい場合に、このパスを使用します。ワークフローは:ローカルでビルド → ECRにプッシュ → ECSはECRからプル。
ステップB1 — ECRリポジトリを作成
-
ECRコンソール → リポジトリ → リポジトリを作成
-
以下を設定:
- 可視性:プライベート
- リポジトリ名:
my-backend
AWS CLI:
aws ecr create-repository \
--repository-name my-backend \
--region ap-northeast-1
以下のようなリポジトリURIが得られます:123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my-backend
ステップB2 — Dockerイメージをビルド、タグ付け、プッシュ
# 1. DockerをECRに認証
aws ecr get-login-password --region ap-northeast-1 | \
docker login --username AWS --password-stdin \
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com
# 2. Dockerイメージをビルド
docker build -t my-backend .
# 3. ECR用のイメージにタグ付け
docker tag my-backend:latest \
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my-backend:latest
# 4. ECRにプッシュ
docker push \
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my-backend:latest
ECRコンソールでリポジトリにイメージが表示されることを確認してください。
ステップB3 — タスク定義を作成(ECRイメージを使用)
パスAとの唯一の違いは、image フィールドがDocker Hubの代わりにECR URIを指すことです:
{
"family": "my-backend-task",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
"containerDefinitions": [
{
"name": "my-backend",
"image": "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/my-backend:latest",
"essential": true,
"portMappings": [
{
"containerPort": 8080,
"protocol": "tcp"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/my-backend",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "backend",
"awslogs-create-group": "true"
}
}
}
]
}
ecsTaskExecutionRole はすでに(AmazonECSTaskExecutionRolePolicyを介して)ECRからプルする権限を持っています。追加の設定は不要です。
その後、クラスター、サービス、セキュリティグループを作成 — パスAのステップ1~7と同じです。
🔄 イメージの更新(CI/CDサイクル)
ECRに新しいイメージをプッシュするか、Docker Hubが更新されたとき、ECSは既に実行中のタスク用に自動的にそれをピックアップしません。新しいイメージをデプロイするには:
# 新しいデプロイを強制 — ECSは最新イメージをプルします
aws ecs update-service \
--cluster grafana-cluster \
--service grafana-service \
--force-new-deployment
これにより、ローリングアップデートが開始されます:ECSは最新イメージを持つ新しいタスクを開始してから、古いタスクをドレインします。
🔒 本番環境の堅牢化
上記のセットアップは、学習とテスト向けです。本番環境の場合、以下の改善を加えてください:
1. タスクをプライベートサブネットに移動 + ALBを追加
タスクのパブリックIPを直接公開する代わりに:
Internet → ALB (public subnet) → ECS Task (private subnet) → NAT GW (outbound only)
これでGrafanaはロードバランサーを通じてのみ到達可能で、コンテナ自体はパブリックIPを持っていません。
2. Application Load Balancer(ALB)を使用
-
パブリックサブネットでALBを作成
-
ターゲットグループを作成(タイプ:
ip、ポート3000、ヘルスチェックパス/api/health) -
ターゲットグループをECSサービスにアタッチ
-
ACM証明書でHTTPSリスナー(443)を設定
-
HTTP(80)→ HTTPS(443)にリダイレクト これで
https://grafana.yourdomain.comを通じてGrafanaにアクセスできます。
3. パスワードにSecrets Managerを使用
GF_SECURITY_ADMIN_PASSWORD をタスク定義にハードコードしないでください。代わりに:
# シークレットを保存
aws secretsmanager create-secret \
--name grafana/admin-password \
--secret-string "YourStrongPassword123!"
タスク定義で参照:
"secrets": [
{
"name": "GF_SECURITY_ADMIN_PASSWORD",
"valueFrom": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:grafana/admin-password-AbCdEf"
}
]
タスク実行ロールには追加の権限が必要です:
{
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue"],
"Resource": "arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:grafana/*"
}
4. EFSを使用した永続的なストレージ
デフォルトでは、Grafanaデータ(ダッシュボード、データソース)はタスク再開時に失われます。EFSボリュームをマウント:
"volumes": [{
"name": "grafana-data",
"efsVolumeConfiguration": {
"fileSystemId": "fs-0abc123",
"rootDirectory": "/grafana"
}
}]
コンテナ定義で:
"mountPoints": [{
"sourceVolume": "grafana-data",
"containerPath": "/var/lib/grafana"
}]
5. 自動スケーリング
# スケーラブルターゲットを登録
aws application-autoscaling register-scalable-target \
--service-namespace ecs \
--resource-id service/grafana-cluster/grafana-service \
--scalable-dimension ecs:service:DesiredCount \
--min-capacity 1 \
--max-capacity 3
# CPUに基づいてスケール
aws application-autoscaling put-scaling-policy \
--service-namespace ecs \
--resource-id service/grafana-cluster/grafana-service \
--scalable-dimension ecs:service:DesiredCount \
--policy-name cpu-scaling \
--policy-type TargetTrackingScaling \
--target-tracking-scaling-policy-configuration '{
"TargetValue": 70.0,
"PredefinedMetricSpecification": {
"PredefinedMetricType": "ECSServiceAverageCPUUtilization"
}
}'
🔍 トラブルシューティング
| 問題 | 考えられる原因 | 修正 |
タスクがPROVISIONINGに留まる | サブネットがインターネットに到達できない | パブリックサブネットがIGWルートを持つことと、パブリックIPが有効なことを確認 |
CannotPullContainerError | インターネットアクセスがない OR イメージ名が間違い | サブネットルートテーブルを確認、イメージ名が完全に正しいことを確認(大文字小文字は区別) |
ECRイメージのCannotPullContainerError | タスク実行ロールにECR権限がない | AmazonECSTaskExecutionRolePolicy を ecsTaskExecutionRole にアタッチ |
| タスクが開始した直後に停止 | コンテナがクラッシュ | /ecs/grafana のCloudWatchログを確認 |
| ブラウザでGrafanaにアクセスできない | セキュリティグループがポート3000を許可していない | ポート3000に対するインバウンドTCPルールを追加 |
ResourceInitializationError | 実行ロールがないまたは権限がない | タスク定義でexecutionRoleArn が設定されていることを確認 |
Docker Hubからtoomanyrequests | Docker Hubレート制限に達した | 代わりにECR Publicイメージを使用:public.ecr.aws/grafana/grafana:latest |
| タスク実行中だがパブリックIPなし | パブリックIPが割り当てられていない | ネットワーク設定でassignPublicIp: ENABLED を設定 |
| ECSサービスがタスクを繰り返し再開 | ヘルスチェックが失敗している | コンテナの健康性を確認、アプリがヘルスチェック猶予期間内に開始することを確認 |
コンテナログを確認
# タスクIDを探す
aws ecs list-tasks --cluster grafana-cluster --service-name grafana-service
# タスク詳細を取得(パブリックIPを含む)
aws ecs describe-tasks --cluster grafana-cluster --tasks <TASK_ID>
# CloudWatchのログを表示
aws logs tail /ecs/grafana --follow
📊 Docker Hub vs ECR — どちらを使用するか
| シナリオ | Docker Hubを直接使用 | ECRを使用(プライベート) |
| パブリック/公式イメージ(Grafana、Nginx、Redis) | ✅ 最もシンプル — イメージ名を使用するだけ | ❌ 不要なオーバーヘッド |
| カスタムアプリケーションイメージ | ❌ Docker Hubアカウント + プッシュが必要 | ✅ AWSシステムと統合、レート制限なし |
| 本番ワークロード | ⚠️ Docker Hubレート制限の対象 | ✅ プル制限なし、AWS内でのプルが高速 |
| エアギャップ/プライベート環境 | ❌ インターネットアクセスが必要 | ✅ VPCエンドポイント機能(インターネット不要) |
| CI/CDパイプライン | ⚠️ Docker Hub認証情報が必要 | ✅ aws ecr get-login-password がネイティブで動作 |
プロチップ: 本番環境のパブリックイメージでは、Docker Hubの代わりに ECR Public を使用してレート制限を回避:public.ecr.aws/grafana/grafana:latest
🏗️ Terraformの例(完全)
# ─── Cluster ───
resource "aws_ecs_cluster" "grafana" {
name = "grafana-cluster"
}
# ─── CloudWatch Log Group ───
resource "aws_cloudwatch_log_group" "grafana" {
name = "/ecs/grafana"
retention_in_days = 7
}
# ─── Task Definition ───
resource "aws_ecs_task_definition" "grafana" {
family = "grafana-task"
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
cpu = "512"
memory = "1024"
execution_role_arn = aws_iam_role.ecs_task_execution.arn
container_definitions = jsonencode([{
name = "grafana"
image = "grafana/grafana:latest"
essential = true
portMappings = [{
containerPort = 3000
protocol = "tcp"
}]
environment = [
{ name = "GF_SECURITY_ADMIN_USER", value = "admin" }
]
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" = aws_cloudwatch_log_group.grafana.name
"awslogs-region" = var.region
"awslogs-stream-prefix" = "grafana"
}
}
}])
}
# ─── Security Group ───
resource "aws_security_group" "grafana" {
name = "grafana-sg"
description = "Allow Grafana UI access"
vpc_id = var.vpc_id
ingress {
from_port = 3000
to_port = 3000
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
# ─── ECS Service ───
resource "aws_ecs_service" "grafana" {
name = "grafana-service"
cluster = aws_ecs_cluster.grafana.id
task_definition = aws_ecs_task_definition.grafana.arn
desired_count = 1
launch_type = "FARGATE"
network_configuration {
subnets = var.public_subnet_ids
security_groups = [aws_security_group.grafana.id]
assign_public_ip = true
}
}
# ─── IAM: Task Execution Role ───
resource "aws_iam_role" "ecs_task_execution" {
name = "ecsTaskExecutionRole"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "ecs-tasks.amazonaws.com" }
}]
})
}
resource "aws_iam_role_policy_attachment" "ecs_task_execution" {
role = aws_iam_role.ecs_task_execution.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
📚 参考
最終更新:2026年3月