AWS を使用した 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 でのみ必要 — ECR へのプッシュ)
-
インターネットゲートウェイを備えた 1 つ以上のパブリックサブネットを持つ VPC(デフォルト VPC で動作)
パス A — Docker Hub から公開イメージをプル(Grafana)
最もシンプルなアプローチです。ECS Fargate は Docker Hub から公開イメージを直接プルできます — タスク定義でイメージ名を指定するだけです。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 ゲートウェイ:なし
- VPC エンドポイント:なし
- 名前:
Fargate タスクがインターネットからイメージをプルするために満たす必要があること:
-
タスクがパブリックサブネットで実行される
-
サブネットのルートテーブルに
0.0.0.0/0 → Internet Gatewayのルートがある -
タスク起動時にパブリック IP の自動割り当てが
ENABLEDに設定されている -
または、タスクがプライベートサブネットで実行され、NAT ゲートウェイへのルートがある
ステップ 2 — セキュリティグループを作成
セキュリティグループは、Grafana コンテナに到達できるトラフィックを制御します。
-
EC2 コンソール → セキュリティグループ → セキュリティグループを作成
-
次のように設定します:
| フィールド | 値 |
| 名前 | grafana-sg |
| 説明 | Grafana UI アクセスを許可 |
| VPC | VPC を選択 |
- インバウンドルール:
| タイプ | プロトコル | ポート | ソース | 説明 |
| カスタム TCP | TCP | 3000 | 0.0.0.0/0 | Grafana ウェブ 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 ロールにより、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 は初回ログイン時にパスワード変更を求めます これで、Docker Hub から直接プルされた ECS Fargate 上で Grafana が実行されています。
パス 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 には既に ECR からプルする権限があります(AmazonECSTaskExecutionRolePolicy を通じて)。追加の設定は不要です。
その後、クラスター、サービス、セキュリティグループを作成 — パス 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 サービスにアタッチ
-
HTTPS リスナー(443)を ACM 証明書で設定
-
HTTP(80)→ HTTPS(443)にリダイレクト これで、raw IP の代わりに
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 | インターネットアクセスがないか、イメージ名が間違っています | サブネットルートテーブルを確認し、イメージ名が完全に正しいこと(大文字と小文字を区別)を確認 |
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 と ECR — どちらを使用するか
| シナリオ | Docker Hub を直接使用 | ECR(プライベート)を使用 |
| パブリック/公式イメージ(Grafana、Nginx、Redis) | ✅ 最もシンプル — イメージ名を使用するだけ | ❌ 不要なオーバーヘッド |
| カスタムアプリケーションイメージ | ❌ Docker Hub アカウント + プッシュが必要 | ✅ AWS IAM と統合、レート制限なし |
| 本番環境ワークロード | ⚠️ 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 月