ECS FargateでGrafanaをセルフホストしてみた

ECS FargateでGrafanaをセルフホストしてみた

ECS FargateでGrafanaをセルフホストし、設定DBにAurora Serverless v2を使う構成を実装してみました。タスク入れ替え時にどのデータが保持されるのかを実機で確認します。
2026.09.08

こんにちは、ゲームソリューション部のsoraです。
今回は、ECS FargateでGrafanaをセルフホストしてみたことについて書いていきます。

Grafanaの設定DBにはAurora Serverless v2を使い、ログ・トレース・メトリクスはCloudWatch / X-Ray / AMPに入れる構成です。

構成

今回構築したのは以下の構成です。

sr-grafana-ecs-aurora-01

Fargateで動くGrafanaが、CloudWatch・X-Ray・AMPをデータソースとして可視化する構成です。
Grafanaの横にはGoのサンプルアプリ(frontend → backend)を置き、ADOT Collectorをサイドカーとしてトレースとメトリクスを収集します。
観測データの保存先はすべてAWSマネージドサービスに寄せています。

Grafanaの設定DB(ダッシュボードやユーザー、データソース定義)はAurora Serverless v2に外出ししています。
ECSではタスクのストレージがエフェメラルなので、1タスクでも内蔵SQLiteでは成り立ちません。
この理由は、動作確認でAuroraの中身を見ながら説明します。

主なコンポーネント

役割 コンポーネント
可視化 Grafana OSS(ECS Fargate × 1タスク、grafana/grafana:13.2.1
Grafanaの設定DB Aurora Serverless v2(PostgreSQL 18.4)
シークレット SSM Parameter Store(SecureString)
入口 ALB + ACM + Route 53(ホスト名で振り分け)
サンプルアプリ Go製のfrontend / backend(ECS Fargate)
テレメトリ収集 ADOT Collector(サイドカー)
ログ CloudWatch Logs(awslogsドライバー)
トレース X-Ray
メトリクス AMP
サービスディスカバリ Cloud Map

Grafanaのタスクを入れ替えても観測データはCloudWatch / X-Ray / AMPに残り、ダッシュボードはAuroraに残ります。
SQLiteにすると消えるのは後者だけ、というのを実機で確認します。

構築

今回はTerraformで構築しました。
基本的なリソースが多いのでコードは割愛しますが、Grafana周りのポイントだけ解説します。

Grafanaの設定

GrafanaのDB接続は、タスク定義の環境変数GF_DATABASE_*でAuroraを指定しています。

GF_DATABASE_TYPE     = postgres
GF_DATABASE_HOST     = <Auroraのライターエンドポイント>:5432
GF_DATABASE_NAME     = grafana
GF_DATABASE_USER     = grafana
GF_DATABASE_SSL_MODE = require

GF_DATABASE_PASSWORDだけは環境変数ではなく、SSM Parameter Store(SecureString)からタスク定義のsecretsで注入しています。
Grafanaは起動時に環境変数を読むだけでパスワードのローテーションに追従できず、IAM DB認証にも対応していないため、ローテーションはしていません。

X-RayとAMPのデータソースは組み込みではなくプラグインなので、GF_INSTALL_PLUGINSで起動時に入れています。

GF_INSTALL_PLUGINS = grafana-x-ray-datasource,grafana-amazonprometheus-datasource

ADOT Collectorの設定

サンプルアプリのタスクにはADOT Collectorをサイドカーで置き、アプリからのトレースをX-Rayへ、タスクのCPU / メモリをAMPへ送っています。
アプリのログはawslogsドライバーでCloudWatch Logsへ出しています。

exporters:
  awsxray:
    region: ${region}
  prometheusremotewrite:
    endpoint: ${amp_remote_write_url}
    auth:
      authenticator: sigv4auth

動作確認

サンプルアプリ

※ドメインはマスクしています
/にアクセスするとfrontendがbackendの/dataを呼び、frontend -> backend(200): items=[1:static-item] (no db)と返ります。
/errorにアクセスするとbackendがエラーを返します。
/healthはヘルスチェック用で、トレースの対象外です。

これらに何度かアクセスして、トレースをX-Rayに、ログをCloudWatch Logsに、タスクのメトリクスをAMPに溜めておきます。

Grafana

Grafanaのドメイン名で/api/healthを叩きます。

$ curl -s https://grafana.example.com/api/health
{"database":"ok","version":"13.2.1"}

database: okが返っており、GrafanaがAuroraに繋がっていることが確認できました。

ブラウザでhttps://grafana.example.comにアクセスしてログインします。

sr-grafana-ecs-aurora-02

Grafanaでは、CloudWatch・X-Ray・AMPの3つをデータソースとして設定します。
Grafana 13ではcoreのPrometheusデータソースのSigV4認証が非推奨になっているため、AMPは専用プラグイン(grafana-amazonprometheus-datasource)を使用します。
https://grafana.com/docs/grafana/latest/datasources/prometheus/configure/

実際にcoreのPrometheusデータソースでGF_AUTH_SIGV4_AUTH_ENABLED=trueにしてみましたが、AMPへ署名なしで飛び、Missing Authentication Tokenで403になりました。

左メニューの 接続 → データソース → データソースを追加 から、以下の3つを登録します。
タスクロールに読み取り権限を付けているため、アクセスキーは入れません。

データソース 種類 設定
CloudWatch 組み込み Authentication Provider: AWS SDK Default、Default Region: ap-northeast-1
X-Ray プラグイン(grafana-x-ray-datasource 同上
Amazon Managed Service for Prometheus プラグイン(grafana-amazonprometheus-datasource URL: AMPワークスペースのエンドポイント、Authentication Provider: AWS SDK Default、Default Region: ap-northeast-1

それぞれ 保存してテスト でOKが出ることを確認します。
登録後、Exploreからデータソースを確認できます。

ちなみに、Exploreに出てくる「-- Grafana --」は組み込みのテスト用データソースで、A-seriesはランダム値です。
実データではありません。

sr-grafana-ecs-aurora-03

X-Rayでトレース確認

Exploreでデータソースに「X-Ray」を選択し、Query TypeをTrace Listにしてみると、frontend → backendの2ノードのトレースが確認できました。
Service Mapでも2つのサービスが表示されます。

CloudWatchでログ確認

データソースに「CloudWatch」を選択し、Query ModeをCloudWatch Logsに切り替えます。
Log Groupsに/ecs/grafana-single/appを選び、以下のクエリを実行してみると、frontend / backendのログが確認できました。

fields @timestamp, @message | sort @timestamp desc | limit 50

AMPでメトリクス確認

データソースに「Amazon Managed Service for Prometheus」を選択し、ecs_task_cpu_utilized_Noneでクエリを実行してみると、frontend / backendタスクのCPUが確認できました。
ADOTサイドカーがタスクメタデータから取ってremote writeしている値です。

なお、「ドリルダウン → Metrics」はPrometheus互換のデータソースがあると動くので、AMPを登録すれば使えます。
Logs / TracesのドリルダウンはLoki / Tempo専用なので、今回の構成では空のままです。

sr-grafana-ecs-aurora-04

ダッシュボードの作成

タスク入れ替えの前に、Auroraに残ることを確認するためのダッシュボードを1枚作ります。

左メニューの ダッシュボード → ダッシュボードを作成 からPanelを作成して 可視化を設定 を押し、データソースにCloudWatchを選択します。

sr-grafana-ecs-aurora-05

sr-grafana-ecs-aurora-06

クエリは以下のように設定します。

  • Namespace: AWS/ECS
  • Metric name: CPUUtilization
  • Statistic: Average
  • Dimensions: ClusterName = grafana-single

sr-grafana-ecs-aurora-07

sr-grafana-ecs-aurora-08

自分自身(GrafanaのECSサービス)のCPU使用率がグラフに表示されました。

sr-grafana-ecs-aurora-09

続けて、X-RayのTrace ListのパネルとCloudWatch Logsのパネルを足して保存します。

sr-grafana-ecs-aurora-10

sr-grafana-ecs-aurora-11

これで「観測データはX-Ray / CloudWatch / AMPに、ダッシュボード定義とデータソース設定はAuroraに」という2種類のデータが揃いました。

Grafanaタスクの入れ替え

ECSサービスをforce-new-deploymentで更新して、Grafanaのタスクを新しいものに入れ替えてみて、ダッシュボードなどのデータが保持されているかを見てみます。

タスクIDが変わったことを確認してからブラウザでログインし直すと、ダッシュボードecs-cpu-utilizationはそのまま残っていました。
ダッシュボードを開くと、入れ替え前に流したトレース(X-Ray)・ログ(CloudWatch Logs)・メトリクス(AMP)もそのまま表示されます。

Auroraの中身を覗いてみる

Grafanaの設定DB(ダッシュボードやユーザー、データソース定義)の中身を覗いてみます。
まずはテーブル数です。

SELECT count(*) AS tables FROM information_schema.tables WHERE table_schema = 'public';
tables
------
91

ユーザーとデータソースは"user" / data_sourceテーブルに入っています。

SELECT id, login, email, is_admin, created FROM "user" ORDER BY id;
id | login | email           | is_admin | created
---+-------+-----------------+----------+--------------
1  | admin | admin@localhost | True     | 1788743546000
SELECT id, name, type, url FROM data_source ORDER BY id;
id | name                     | type                                | url
---+--------------------------+-------------------------------------+--------------------------------------------------------------------------
2  | cloudwatch               | cloudwatch                          |
3  | grafana-x-ray-datasource | grafana-x-ray-datasource            |
5  | amp                      | grafana-amazonprometheus-datasource | https://aps-workspaces.ap-northeast-1.amazonaws.com/workspaces/ws-.../
6  | aurora                   | grafana-postgresql-datasource       | grafana-single.cluster-....ap-northeast-1.rds.amazonaws.com:5432

次にダッシュボードです。
従来のdashboardテーブルを見てみると、空でした。

SELECT id, uid, title, version, updated FROM dashboard ORDER BY id;
(0 rows)          ← 従来のテーブルは空

Grafana 13ではダッシュボードがunified storageに保存されるようになっており、実体はresource / resource_historyテーブルにあります。

SELECT "group", resource, namespace, name, resource_version FROM resource ORDER BY 1, 2, 4;
group                 | resource   | namespace | name       | resource_version
----------------------+------------+-----------+------------+-----------------
advisor.grafana.app   | checktypes | default   | config     | 1788743551258730
advisor.grafana.app   | checktypes | default   | datasource | 1788743551197334
advisor.grafana.app   | checktypes | default   | instance   | 1788743551284903
advisor.grafana.app   | checktypes | default   | plugin     | 1788743551209005
advisor.grafana.app   | checktypes | default   | ssosetting | 1788743551236030
dashboard.grafana.app | dashboards | default   | adtmwtx    | 1788757495921632   ← ecs-cpu-utilization

resource.valueにはKind=Dashboard / apiVersion=dashboard.grafana.app/v2のJSONがそのまま入っています。

SELECT name, length(value) AS bytes, substr(value::text, 1, 120) AS value_head
FROM resource WHERE "group" = 'dashboard.grafana.app';
name    | bytes | value_head
--------+-------+---------------------------------------------------------------------------------------------
adtmwtx | 6495  | {"kind":"Dashboard","apiVersion":"dashboard.grafana.app/v2","metadata":{"name":"adtmwtx","namespace":"default", ...

パネル3枚(CloudWatch / X-Ray / AMP)のダッシュボード1枚で約6.5KBでした。
サイズはパネル数やクエリの作り込みで変わります。

resource_historyには保存のたびのバージョンが積まれています。

SELECT "group", resource, count(*) AS versions FROM resource_history GROUP BY 1, 2 ORDER BY 1, 2;
group                 | resource   | versions
----------------------+------------+---------
advisor.grafana.app   | checktypes | 5
dashboard.grafana.app | dashboards | 6        ← 保存6回分の履歴

アラートルールは作っていないので0件です。

SELECT count(*) AS alert_rules FROM alert_rule;
alert_rules
-----------
0

Grafanaの設定DBは観測データの保管先ではなく、Grafanaというアプリケーション自身の設定・状態の置き場所です。
主に以下が入ります。

  • ダッシュボードのJSON定義
  • ユーザー、Team、Organization、権限設定
  • データソースの接続定義
  • アラートルール、コンタクトポイント、通知ポリシー
  • アノテーション、スター、プレイリスト

メトリクスやログの実体はCloudWatch / AMP / X-Ray側にあり、Grafanaは画面を開くたびにデータソースへクエリを投げて取ってくるだけです。

ここで、設定DBをAuroraに外出ししている理由に戻ります。
EC2の1台構成なら、この設定DBは内蔵SQLite(grafana.db)でも実現可能です。

補足: Grafana OSSのDockerイメージはgrafana/grafanaを使う

最初はgrafana/grafana-oss:13.2.1を指定していましたが、CannotPullContainerErrorでタスクが起動しませんでした。
grafana-ossリポジトリは13.0.2で更新が止まっていて、13.2.1のタグが存在しないためです。

OSS版の公式イメージはgrafana/grafanaです。
grafana-ossは同じ中身の別名で、Grafana Labsのフォーラムでも以下のように回答されています。

I would strongly encourage you to use grafana/grafana and not grafana/grafana-oss, as it will be deprecated soon. It's pretty much the exact same as grafana/grafana.

https://community.grafana.com/t/docker-tags-for-grafana-grafana-oss/156379/2

最後に

今回は、ECS FargateでGrafanaをセルフホストし、設定DBをAurora Serverless v2に外出しした構成でタスクを入れ替えても何が残るのかを確認してみました。
この記事がどなたかの参考になれば幸いです。

この記事をシェアする

AWSのお困り事はクラスメソッドへ

関連記事