Grafana の Git Sync でダッシュボードを GitOps 管理する
Table of Contents
1. はじめに
みなさん、Grafana のダッシュボード管理どうしてますか?
私は自宅の Kubernetes クラスターで Grafana を動かしており、ダッシュボードを Web UI でポチポチ作ったり、mcp-grafana を使って Claude Code や kagent から作ったりしていました。
しかし、それではダッシュボードのデータが Grafana 内部のデータベースにしか残らず、PVC を使っていたとしても Kubernetes クラスター自体を作り直した際などには消えてしまいますし、バージョン管理もできないため履歴を追うことも難しいです。 実際に今まで何回も Kubernetes クラスター自体が壊れて作り直しており、その度に同じようなダッシュボードを都度都度作成していました、、、
そんな中、Grafana v12.4 から Git Sync という機能が使えるようになっていることを知りました。 ダッシュボード設定の JSON を GitHub リポジトリと双方向に同期してくれる機能で、まさに求めていたものです。
今回は、自宅サーバーの Grafana に Git Sync を導入して、ダッシュボードを GitOps で管理できるようにするまでの手順を紹介します。
2. Git Sync とは
Git Sync は、Grafana インスタンスと Git リポジトリの間でダッシュボードを双方向に同期する機能です。 Grafana Cloud だけでなく、OSS 版や Enterprise 版でも利用できます。
主な特徴はこんな感じです。
- 双方向同期: Web UI での変更は Git にコミットされ、Git への変更は Grafana に反映される
- PR ワークフロー: Web UI での変更をブランチにコミットして PR を作成できる(
branchワークフロー) - 直接コミット: Web UI での変更を直接メインブランチにコミットもできる(
writeワークフロー)
- PR ワークフロー: Web UI での変更をブランチにコミットして PR を作成できる(
- 定期同期: 設定した間隔(例: 60秒)で自動的に同期される
従来のプロビジョニング(provisioning/dashboards/ にファイルを配置する方法)との大きな違いは、Git → Grafana の一方向ではなく、Grafana → Git の方向も同期される点です。
Web UI で気軽にダッシュボードを編集しつつ、その変更が自動的に Git に記録されるのはとても便利です。
2.1 アーキテクチャ
Git Sync は以下の要素で構成されています。
- Connection: GitHub App 等の認証情報を管理するリソース
- Repository: 同期先の Git リポジトリ・ブランチ・パスや同期間隔などを定義するリソース
Grafana は Repository リソースの設定に基づいてリポジトリをポーリングし、指定パス配下のダッシュボード JSON を同期します。 同期されたダッシュボードは Grafana のダッシュボード一覧にフォルダとして表示されます。
3. 前提条件
今回の環境はこんな感じです。
- Kubernetes クラスター: 自宅サーバー上の kubeadm + OVN-Kubernetes 環境
- Grafana:
grafana/grafana:latest(v13 以降であればprovisioningfeature toggle がデフォルト有効) - Argo CD: マニフェストの GitOps デプロイに利用
Git Sync を使うには、以下が必要です。
- Grafana v13 以降(
provisioningfeature toggle がデフォルト有効。v12.4 でも利用可能だが feature toggle の手動有効化が必要) - GitHub App の作成とインストール
- grafana.ini での
repository_typesの設定
4. 構築手順
4.1 GitHub App の作成
まず、Grafana がリポジトリにアクセスするための GitHub App を作成します。 以下の権限を付与してください。
| Permission | Access |
|---|---|
| Contents | Read and write |
| Pull requests | Read and write |
| Webhooks | Read and write |
| Administration | Read-only |
| Metadata | Read-only |
作成したら、対象のリポジトリにインストールして、App ID、Installation ID、Private Key(.pem ファイル)を控えておきます。
4.2 Kubernetes Secret の作成
GitHub App の認証情報と Grafana の管理者パスワードを Secret として登録します。
kubectl -n grafana create secret generic grafana-git-sync-secret \
--from-literal=admin-password=<GRAFANA_ADMIN_PASSWORD> \
--from-literal=github-app-id=<APP_ID> \
--from-literal=github-installation-id=<INSTALLATION_ID> \
--from-file=github-private-key=<PEM_FILE_PATH>
4.3 grafana.ini の設定
Git Sync を有効にするには、grafana.ini に以下を追加します。
[provisioning]
repository_types = github
この設定は 環境変数でオーバーライド することもできます。
Grafana の環境変数は GF_<SECTION>_<KEY> の命名規則で、この場合は GF_PROVISIONING_REPOSITORY_TYPES=github になります。
今回は ConfigMap で grafana.ini を管理しているので、ファイルに直接記述しています。
# kustomization.yaml
configMapGenerator:
- name: grafana-ini
files:
- grafana.ini
options:
disableNameSuffixHash: true
4.4 Deployment の設定
Grafana の Deployment では、Secret から管理者パスワードを取得し、grafana.ini を ConfigMap からマウントしています。
apiVersion: apps/v1
kind: Deployment
metadata:
name: grafana
spec:
strategy:
type: Recreate
template:
spec:
securityContext:
fsGroup: 472
supplementalGroups:
- 0
containers:
- name: grafana
image: grafana/grafana:latest
env:
- name: GF_SECURITY_ADMIN_PASSWORD
valueFrom:
secretKeyRef:
name: grafana-git-sync-secret
key: admin-password
ports:
- containerPort: 3000
name: http-grafana
volumeMounts:
- mountPath: /var/lib/grafana
name: grafana-pv
- mountPath: /etc/grafana/grafana.ini
name: grafana-ini
subPath: grafana.ini
readOnly: true
volumes:
- name: grafana-pv
persistentVolumeClaim:
claimName: grafana-pvc
- name: grafana-ini
configMap:
name: grafana-ini
特に特別なことはしていなくて、通常の Grafana デプロイに grafana.ini の ConfigMap マウントを追加しただけです。 Git Sync は Grafana 自体の機能なので、サイドカーコンテナなどは不要です。
4.5 Git Sync の設定リソース
Git Sync の設定は、Grafana の Provisioning API で Connection と Repository のリソースを登録して行います。 これらの設定は YAML ファイルとして定義し、grafanactl という CLI ツールで Grafana に投入します。
まず、GitHub App の接続情報を定義する Connection リソースです。
apiVersion: provisioning.grafana.app/v0alpha1
kind: Connection
metadata:
name: github
namespace: default
spec:
title: GitHub
type: github
url: https://github.com
github:
appID: ""
installationID: ""
secure:
privateKey:
create: ""
appID、installationID、privateKey はこの時点では空にしておき、後述の Job で Secret から注入します。
次に、同期先のリポジトリを定義する Repository リソースです。
apiVersion: provisioning.grafana.app/v0alpha1
kind: Repository
metadata:
name: kubernetes-bootstrap
spec:
title: kubernetes-bootstrap
type: github
github:
url: https://github.com/TOMOFUMI-KONDO/kubernetes-bootstrap.git
branch: main
path: grafana/dashboards/
sync:
enabled: true
intervalSeconds: 60
target: folder
workflows:
- write
- branch
connection:
name: github
ポイントをいくつか説明します。
path: grafana/dashboards/: リポジトリ内のこのディレクトリ配下にあるダッシュボード JSON のみが同期対象になりますintervalSeconds: 60: 60秒間隔でポーリングして同期しますworkflows: [write, branch]: Web UI からの変更を直接コミット(write)と PR 作成(branch)の両方で行えますconnection.name: github: 先ほど定義した Connection リソースを参照します
これらの YAML は ConfigMap にまとめて管理しています。
apiVersion: v1
kind: ConfigMap
metadata:
name: grafana-git-sync
data:
connection.yaml: |-
# (上記の Connection YAML)
repository.yaml: |-
# (上記の Repository YAML)
4.6 セットアップ Job
Git Sync の設定を Grafana に投入するために、Argo CD の PostSync Hook として Job を作成しました。 この Job は Grafana がデプロイされた後に実行され、grafanactl を使って Connection と Repository リソースを登録します。
apiVersion: batch/v1
kind: Job
metadata:
name: grafana-git-sync-setup
annotations:
argocd.argoproj.io/hook: PostSync
spec:
backoffLimit: 5
ttlSecondsAfterFinished: 300
template:
spec:
restartPolicy: OnFailure
containers:
- name: setup
image: alpine:3.21
env:
- name: GRAFANA_SERVER
value: http://grafana.grafana.svc:3000
- name: GRAFANA_ORG_ID
value: "1"
- name: GRAFANA_USER
value: admin
- name: GRAFANA_PASSWORD
valueFrom:
secretKeyRef:
name: grafana-git-sync-secret
key: admin-password
- name: GITHUB_APP_ID
valueFrom:
secretKeyRef:
name: grafana-git-sync-secret
key: github-app-id
- name: GITHUB_INSTALLATION_ID
valueFrom:
secretKeyRef:
name: grafana-git-sync-secret
key: github-installation-id
- name: GITHUB_PRIVATE_KEY
valueFrom:
secretKeyRef:
name: grafana-git-sync-secret
key: github-private-key
command:
- sh
- -euc
- |
echo "Installing dependencies..."
apk add --no-cache yq
ARCH=$(uname -m | sed 's/aarch64/arm64/')
echo "Downloading grafanactl v0.1.9 (${ARCH})..."
wget -qO- "https://github.com/grafana/grafanactl/releases/download/v0.1.9/grafanactl_Linux_${ARCH}.tar.gz" | tar xz -C /usr/local/bin
echo "Waiting for Grafana to be ready..."
until wget -qO /dev/null "${GRAFANA_SERVER}/api/health"; do sleep 5; done
echo "Grafana is ready."
echo "Preparing config files..."
mkdir -p /tmp/config
cp /config/repository.yaml /tmp/config/
GITHUB_PRIVATE_KEY_B64=$(echo -n "${GITHUB_PRIVATE_KEY}" | base64 | tr -d '\n')
export GITHUB_PRIVATE_KEY_B64
yq ".spec.github.appID = \"${GITHUB_APP_ID}\"
| .spec.github.installationID = \"${GITHUB_INSTALLATION_ID}\"
| .secure.privateKey.create = strenv(GITHUB_PRIVATE_KEY_B64)" \
/config/connection.yaml > /tmp/config/connection.yaml
echo "Pushing resources to Grafana..."
grafanactl resources push --stop-on-error -v --path /tmp/config
echo "Done."
volumeMounts:
- name: config
mountPath: /config
readOnly: true
volumes:
- name: config
configMap:
name: grafana-git-sync
この Job がやっていることを整理すると、以下の流れです。
- 依存ツールのインストール: yq(YAML 処理)と grafanactl(Grafana CLI)をダウンロード
- Grafana の起動待ち:
/api/healthエンドポイントをポーリングして Grafana が Ready になるまで待機 - 設定ファイルの準備: ConfigMap からマウントした connection.yaml に Secret の値を yq で注入
- リソースの登録:
grafanactl resources pushで Connection と Repository を Grafana に投入
ここで一つ工夫したのが、Connection の認証情報の扱いです。
ConfigMap にはテンプレートとして appID や privateKey を空にした状態で配置し、Job の中で Secret から値を取得して yq で埋め込んでいます。
Private Key は Base64 エンコードして secure.privateKey.create に設定する必要がある点に注意です。
4.7 ダッシュボードの配置
ダッシュボードの JSON ファイルは、Repository リソースの path で指定した grafana/dashboards/ ディレクトリに配置します。
grafana/
└── dashboards/
├── argocd.json
├── cert-manager.json
├── coredns.json
├── grafana-mcp.json
├── k8s.json
├── kagent.json
├── kube-state-metrics.json
└── node-exporter-full.json
既存のダッシュボードがある場合は、Grafana の Web UI からダッシュボードの JSON をエクスポートして、このディレクトリに配置してコミットすれば OK です。
5. ディレクトリ構成
最終的な Kustomize のディレクトリ構成はこのようになりました。
kustomize/grafana/
├── kustomization.yaml
├── namespace.yaml
├── deployment.yaml
├── service.yaml
├── pvc.yaml
├── http-route.yaml
├── grafana.ini
├── git-sync-configmap.yaml # Connection + Repository の YAML テンプレート
└── git-sync-setup-job.yaml # PostSync Hook で grafanactl を実行する Job
リポジトリのルートにはダッシュボード JSON を配置する grafana/dashboards/ ディレクトリがあり、Git Sync はここを同期対象として監視します。
6. 動作確認
6.1 Argo CD での同期
Argo CD で Grafana の Application を Sync すると、以下の順序で処理が進みます。
- Grafana の Deployment、Service、ConfigMap などがデプロイされる
- Grafana Pod が起動し、Ready になる
- PostSync Hook の Job が起動する
- Job が grafanactl で Connection と Repository を登録する
- Grafana が GitHub リポジトリのポーリングを開始する
grafana/dashboards/配下のダッシュボード JSON が同期される
6.2 ダッシュボードの確認
同期が成功すると、Grafana のダッシュボード一覧に kubernetes-bootstrap というフォルダが作成され、その中にダッシュボードが表示されます。
Administration → Provisioning の画面からも、リポジトリの接続状態と最終同期時刻を確認できます。
リポジトリ名をクリックすると、Health や Pull status の詳細も確認できます。
6.3 ダッシュボードの追加
新しいダッシュボードを追加する場合の流れです。
grafana/dashboards/にダッシュボードの JSON ファイルを配置- Git にコミットしてプッシュ
- 最大 60秒後に Grafana に自動反映
逆に、Web UI でダッシュボードを編集した場合は、write ワークフローなら GitHub に直接コミットされ、branch ワークフローなら PR が作成されます。
7. つまずいたポイント
7.1 GitHub App 認証情報の扱い
grafanactl に渡す Connection リソースには GitHub App の appID、installationID、privateKey を含める必要がありますが、ConfigMap に記載して git 管理下におくようなことはセキュリティリスクを考えると避けたいです。
そこで、先ほど説明した通り ConfigMap にはテンプレートとして値を空にした Connection YAML を配置しておき、Job の中で Kubernetes Secret から取得した値を yq で注入するようにしました。
yq ".spec.github.appID = \"${GITHUB_APP_ID}\"
| .spec.github.installationID = \"${GITHUB_INSTALLATION_ID}\"
| .secure.privateKey.create = strenv(GITHUB_PRIVATE_KEY_B64)" \
/config/connection.yaml > /tmp/config/connection.yaml
これにより、Git リポジトリには機密情報を一切含めずに管理できます。
これも再掲にはなりますが、注意点として secure.privateKey.create には Private Key を Base64 エンコードした文字列を渡す必要があります。
PEM ファイルの中身をそのまま渡すと認証に失敗するので気をつけてください。
8. まとめ
Grafana の Git Sync を導入したことで、以下のメリットが得られました。
- ダッシュボードがコードとして Git 管理される: 変更履歴の追跡、レビュー、ロールバックが可能に
- 双方向同期: Web UI での編集も自動的に Git に反映されるので、運用の手間が増えない
- Argo CD との統合: PostSync Hook で初期設定を自動化できる
個人的には、以前は git-sync サイドカー + provisioning で一方向同期する方法も検討していたのですが、Git Sync の双方向同期と PR ワークフローが使えるのは大きなアドバンテージだと感じました。 特に「ちょっとした修正は Web UI でサクッとやりたいけど、Git にも残したい」というユースケースにぴったりです。
最後まで読んでいただき、ありがとうございました。