メインコンテンツまでスキップ
最新1mo ago

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つのアプローチ:

  1. パスA — Docker Hubからパブリックイメージを直接プル(例:grafana/grafana) — 最もシンプル、ECRは不要

  2. パスB — 独自のイメージをECRにプッシュ(プライベートレジストリ)して、ECRからプル — カスタム/プライベートバックエンド用


🧱 ECSのコアコンセプト

ステップに進む前に、各要素の役割を説明します:

コンポーネント定義類例
ECS Clusterタスクとサービスの論理的なグループコンテナ用の「ワークスペース」
Task Definitionコンテナのブループリント — イメージURI、CPU、メモリ、ポート、環境変数、IAMロールAWSのdocker-compose.yml
Taskタスク定義の実行インスタンス実行中のdocker runコマンド
Service望ましいタスク数を管理、失敗時に再開、ロードバランサーと統合プロセス監視
Fargateサーバーレスコンピュートエンジン — EC2インスタンスの管理は不要AWSがコンテナを実行
Task Execution RoleECS エージェント がイメージをプルしログを書き込むことを許可する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を作成する場合:

  1. VPCコンソールVPCを作成

  2. VPCおよび詳細 を選択(ウィザードが自動的にサブネット、ルートテーブル、IGWを作成)

  3. 以下を設定:

    • 名前: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コンテナに到達できるトラフィックを制御します。

  1. EC2コンソールセキュリティグループセキュリティグループを作成

  2. 以下を設定:

フィールド
名前grafana-sg
説明Grafana UIアクセスを許可
VPCVPCを選択
  1. インバウンドルール:
タイププロトコルポートソース説明
カスタムTCPTCP30000.0.0.0/0Grafana web UI
  1. アウトバウンドルール: デフォルトのまま(すべてのトラフィックをアウトバウンド許可 — イメージプルとインターネットアクセスに必要)

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クラスターを作成

  1. ECSコンソールクラスタークラスターを作成

  2. 以下を設定:

    • クラスター名:grafana-cluster
    • インフラストラクチャ:AWS Fargate を選択(デフォルト)
  3. 作成 をクリック それで終了です。クラスターは論理的なコンテナに過ぎず、サーバーはプロビジョニングされません。

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

存在しない場合は作成:

  1. IAMコンソールロールロールを作成

  2. 信頼されたエンティティ:AWSサービスElastic Container Service → ユースケース:Elastic Container Service Task

  3. マネージドポリシーをアタッチ:AmazonECSTaskExecutionRolePolicy

  4. ロール名:ecsTaskExecutionRole このポリシーは以下を許可します:

  • ecr:GetAuthorizationToken — ECRに認証

  • ecr:BatchGetImageecr:GetDownloadUrlForLayer — ECRからイメージをプル

  • logs:CreateLogStreamlogs: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/メモリをいくら割り当てるか、どのポートを公開するか、どのロールを使用するかを指定するブループリントです。

  1. ECSコンソールタスク定義新しいタスク定義を作成

  2. 以下を設定:

設定
タスク定義ファミリーgrafana-task
起動タイプAWS Fargate
OS/アーキテクチャLinux/X86_64
タスクサイズ — CPU0.5 vCPU (512)
タスクサイズ — メモリ1 GB (1024)
タスク実行ロールecsTaskExecutionRole
タスクロールなし(GrafanaはAWS APIを呼び出す必要なし)
  1. コンテナ定義:
設定
コンテナ名grafana
イメージURIgrafana/grafana:latest
必須はい
ポートマッピングコンテナポート:3000、プロトコル:TCP
  1. (オプション) 環境変数:
キー目的
GF_SECURITY_ADMIN_USERadminデフォルト管理者ユーザー名
GF_SECURITY_ADMIN_PASSWORDYourStrongPassword123!デフォルトパスワードをオーバーライド
  1. (オプション) ロギング — CloudWatch:
設定
ログドライバーawslogs
ログループ/ecs/grafana
リージョンお使いのリージョン(例:ap-northeast-1
ストリームプリフィックスgrafana
  1. 作成 をクリック

同等の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は自動的に新しいタスクを開始します。

  1. ECSコンソールクラスターgrafana-clusterサービス作成

  2. 以下を設定:

設定
起動タイプFargate
タスク定義grafana-task(最新リビジョン)
サービス名grafana-service
望ましいタスク数1
  1. ネットワーク:
設定
VPCVPCを選択
サブネットパブリック サブネットを選択
セキュリティグループgrafana-sg(ポート3000を許可するもの)
パブリックIP有効 (これは重要です!)
  1. 作成 をクリック

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にアクセス

  1. ECSコンソールクラスターgrafana-clusterタスク タブ

  2. 実行中のタスクIDをクリック

  3. 設定 の下にある パブリックIP を探す(例:3.112.45.67

  4. ブラウザを開いて以下にアクセス:http://3.112.45.67:3000

  5. 以下でログイン:

    • ユーザー名:admin
    • パスワード:admin(またはGF_SECURITY_ADMIN_PASSWORDで設定したもの)
  6. Grafanaは初回ログイン時にパスワード変更を促します これでGrafanaはECS Fargateで実行され、Docker Hubから直接プルされています。


パスB — 独自のイメージをECRにプッシュしてECSにデプロイ

カスタムバックエンド(独自のDockerfile)がある場合またはプライベートレジストリ を使用したい場合に、このパスを使用します。ワークフローは:ローカルでビルド → ECRにプッシュ → ECSはECRからプル。


ステップB1 — ECRリポジトリを作成

  1. ECRコンソールリポジトリリポジトリを作成

  2. 以下を設定:

    • 可視性:プライベート
    • リポジトリ名: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権限がないAmazonECSTaskExecutionRolePolicyecsTaskExecutionRole にアタッチ
タスクが開始した直後に停止コンテナがクラッシュ/ecs/grafana のCloudWatchログを確認
ブラウザでGrafanaにアクセスできないセキュリティグループがポート3000を許可していないポート3000に対するインバウンドTCPルールを追加
ResourceInitializationError実行ロールがないまたは権限がないタスク定義でexecutionRoleArn が設定されていることを確認
Docker HubからtoomanyrequestsDocker 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月

Related Articles