Prometheus Federation: 複数クラスターのメトリクスをグローバルな一点に集約する

Monitoring tutorial - IT technology blog
Monitoring tutorial - IT technology blog

インフラが想像以上に大きくなったとき

最初はPrometheusを1台だけ運用していた。5〜6台のプロダクションサーバーを監視するには十分で、すべてがスムーズに動いていた。しかし会社がSingaporeにKubernetesクラスターを追加し、さらにTokyoにデータセンターを増設すると、それぞれの拠点に独自のPrometheusを立ち上げることになり、すぐにこんな疑問が浮かんだ:インフラ全体を一つのGrafanaダッシュボードで見るにはどうすればいいのか?

一元的な監視を構築する前は、各サーバーにSSHして状態を確認しなければならなかった。夜中の2時に「Singaporeクラスターで問題が発生」というアラートが来ると、3つのターミナルを同時に開き、3か所に接続して原因を探す羽目になる。誰もが繰り返したくない体験だ。今はダッシュボードを開くだけですべてがわかる——しかしそこに至るには、複数のPrometheusからメトリクスを一点に集約するという課題を解決しなければならなかった。

なぜ直接接続しないのか?

Prometheusはpull-basedモデルで動作する。同一ネットワーク内のターゲットから能動的にメトリクスをscrapeする仕組みだ。つまり、各Prometheusはネットワーク的に近いサーバーしか「見えない」。SingaporeのPrometheusがTokyoクラスターのPodsを直接scrapeできないのは、レイテンシーの問題だけでなく、クラウドリージョン間のVPC分離、ファイアウォール、セキュリティポリシーによる制約があるためだ。

結果として、複数のPrometheusが並行稼働し、それぞれが独立した「孤島」のような状態になる:

  • Singaporeクラスター:Prometheus A — Singaporeのメトリクスのみ把握
  • Tokyoクラスター:Prometheus B — Tokyoのメトリクスのみ把握
  • ハノイデータセンター:Prometheus C — ハノイDCのメトリクスのみ把握

Grafanaは複数のデータソースに接続できるが、インフラ全体のCPU使用率を集計する単一のPromQLクエリを書こうとしても、各Prometheusがお互いの存在を知らない状態では不可能だ。

一般的な解決アプローチ

Grafanaでのマルチデータソース接続

最もシンプルな方法:各PrometheusをGrafanaに個別データソースとして追加し、「Mixed」データソース機能を使って複数のメトリクスを同一パネルに表示する。ただしこのアプローチには明確な限界がある——複数ソースのデータを集計する単一のPromQLクエリは書けず、アラートの設定が分散して管理が難しくなる

集中ストレージへのRemote Write

Prometheusはremote_writeをサポートしており、すべてのメトリクスをThanos、Cortex、VictoriaMetricsなどの集中ストレージに送信できる。大規模プロダクション向けの強力なソリューションだが、オーバーヘッドも相当なもの——複雑なシステムの追加デプロイが必要で、コストと運用負荷が増大する。中規模の課題に「バズーカ」は必ずしも必要ではない。

Prometheus Federation

Prometheusが標準でサポートする「中間的な」ソリューションで、追加インストール不要。Global Prometheus(「parent」)を構築し、ローカルのPrometheus(「child」)の/federateエンドポイントから事前集約済みのメトリクスをscrapeさせる。

Prometheus Federation — 中小規模インフラに最適な選択

Federationが適しているのは、2〜10個のクラスター/リージョンを持ち、ThanosやCortexを使いたくなく、「グローバルな概要」レベルのクエリが必要な場合だ——Global Prometheusから個々のPodの詳細にドリルダウンする必要はない(それはchildで直接行う)。

動作原理

各child Prometheusは/federateエンドポイントを公開する。Global Prometheusはこのエンドポイントをscrapeし、フィルタリング済みのメトリクスセットを取得する。childは詳細なメトリクスをすべて保持し、Globalはグローバルな概要とアラートに必要なものだけを取得する。

[Child Prometheus - Singapore] ──┐
[Child Prometheus - Tokyo]    ──┼──▶ /federate ──▶ [Global Prometheus] ──▶ [Grafana]
[Child Prometheus - Hanoi]    ──┘

childの/federateエンドポイントを確認する

まず、child Prometheusが/federate経由でデータを公開していることを確認する:

# /federateエンドポイントを確認 — text/plainのデータが返ってくるはず
curl 'http://prometheus-singapore:9090/federate?match[]={job="node_exporter"}'

# 現在存在するすべてのメトリクスを確認したい場合
curl 'http://prometheus-singapore:9090/federate?match[]={__name__=~".+"}'

Global Prometheusの設定(prometheus.yml)

ここが核心部分だ。Global Prometheusは各childに対して特別なscrapeジョブを追加する必要がある。honor_labels: trueは必須設定——これがないと、Globalはinstancejobラベルを自分自身の値で上書きしてしまい、メトリクスの発生源情報が失われる。

global:
  scrape_interval: 60s      # Globalのscrapeはlocal(15s)より遅い — 概要把握のために使用
  evaluation_interval: 60s

scrape_configs:
  # SingaporeクラスターからのFederation
  - job_name: 'federate-singapore'
    scrape_interval: 60s
    honor_labels: true        # childのラベルをそのまま保持 — 必須
    metrics_path: '/federate'
    params:
      match[]:
        - '{job="node_exporter"}'            # ノードのCPU/RAM/Disk
        - '{job="kube-state-metrics"}'       # Kubernetes cluster state
        - 'up'                               # 全ターゲットのヘルスチェック
        - '{__name__=~"instance:.*"}'        # 事前集約済みのRecording rules
    static_configs:
      - targets:
          - 'prometheus-singapore.internal:9090'
        labels:
          cluster: 'singapore'
          region: 'ap-southeast-1'

  # TokyoクラスターからのFederation
  - job_name: 'federate-tokyo'
    scrape_interval: 60s
    honor_labels: true
    metrics_path: '/federate'
    params:
      match[]:
        - '{job="node_exporter"}'
        - '{job="kube-state-metrics"}'
        - 'up'
        - '{__name__=~"instance:.*"}'
    static_configs:
      - targets:
          - 'prometheus-tokyo.internal:9090'
        labels:
          cluster: 'tokyo'
          region: 'ap-northeast-1'

  # ハノイDCからのFederation(bare-metal、Kubernetesなし)
  - job_name: 'federate-hanoi'
    scrape_interval: 60s
    honor_labels: true
    metrics_path: '/federate'
    params:
      match[]:
        - '{job="node_exporter"}'
        - 'up'
    static_configs:
      - targets:
          - '10.0.1.50:9090'
        labels:
          cluster: 'hanoi'
          region: 'ap-southeast-7'

Child PrometheusにRecording Rulesを設定して最適化する

raw metricsをfederate(帯域幅を消費しcardinalityが増加)する代わりに、各childにrecording rulesを作成して事前集約する。Global Prometheusは計算済みの結果だけを取得すればよい——よりシンプルで効率的だ。

# Child Prometheus上で設定: rules/federation-aggregates.yml
groups:
  - name: federation_aggregates
    interval: 30s
    rules:
      # 各ノードの平均CPU使用率
      - record: instance:node_cpu_usage:rate5m
        expr: |
          1 - avg by (instance) (
            rate(node_cpu_seconds_total{mode="idle"}[5m])
          )

      # Memory usage percentage
      - record: instance:node_memory_usage:ratio
        expr: |
          1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)

      # Disk usage percentage (mount point /)
      - record: instance:node_disk_usage:ratio
        expr: |
          1 - (
            node_filesystem_avail_bytes{mountpoint="/"}
            / node_filesystem_size_bytes{mountpoint="/"}
          )

recording rulesを設定すれば、Global Prometheusは生のメトリクス全体ではなく、{__name__=~"instance:.*"}だけをfederateすれば済む。

セットアップ後のGlobal PrometheusでのQuery

GrafanaをGlobal Prometheusに接続すれば、インフラ全体の概要を確認するPromQLを書けるようになる:

# クラスター別の平均CPU使用率
avg by (cluster) (instance:node_cpu_usage:rate5m)

# リージョン別の「up」状態のノード数
count by (region) (up{job="node_exporter"} == 1)

# インフラ全体でメモリ使用率が最も高いTop 5ノード
topk(5, instance:node_memory_usage:ratio)

# アラート: disk使用率が85%を超えているノード
instance:node_disk_usage:ratio > 0.85

デプロイ時の注意事項

  • Globalのscrape intervalは60sで十分:Federationはリアルタイムの詳細監視のためではない——それはchildに任せる。Globalはトレンドと概要把握のためのものであり、60sは妥当だ。
  • Cardinalityを管理する:match[]={__name__=~".+"}ですべてのメトリクスをfederateしないこと。Global Prometheusがlocalと同じくらい重くなる。グローバルレベルで本当に必要なものだけを取得する。
  • クラスター間ネットワーク:GlobalはHTTP(S)経由でchildに到達できる必要がある。マルチリージョンのクラウド環境では、VPNまたはプライベートピアリングの使用を検討すること——認証なしにPrometheusをパブリックインターネットに公開しないこと。
  • Globalの保持期間は短く:Global Prometheusは概要把握のためなので、通常7〜15日間のデータを保持する。各childは詳細データをより長期間(30〜90日間)保持する。
  • セキュリティが必要な場合はBasic auth:child Prometheusが認証付きリバースプロキシの背後にある場合は、Globalのscrape configにbasic_authを追加する。

FederationかThanos——どちらに切り替えるべきか?

Federationは10クラスター未満で、長期ストレージ(過去データへのクエリ)が不要な場合に十分だ。インフラが大きくなり——数十のクラスター、数か月のretention、HA時のdeduplicationが必要になったとき——ThanosやVictoriaMetricsの検討が適切になる。しかし、Federationで3クラスターが問題なく管理できているのに、急いでThanosに移行する必要はない:複雑さが課題に見合っていない。

現在の私のセットアップ——3リージョン、Federationが安定稼働中——では、Grafanaダッシュボードがインシデント発生から2分以内にインフラ全体の状態を表示する。各サーバーにSSHして確認していた頃と比べると、チーム全体の作業品質が大幅に向上した。

Share: