Prometheusのデータ消失を防ぐ:ThanosサイドカーによるHA構成とS3長期保存の実装ログ

深夜2時にPagerDutyが鳴り響く原因のトップ3に、「監視サーバー自体のディスク枯渇」が入っている現場は少なくありません。私が担当していたEKS上の大規模なマイクロサービス環境でも、Pod数が数千規模にスケールするにつれ、PrometheusのPVC(EBS)が圧迫され、Retention(保持期間)を15日まで短縮せざるを得ない状況に追い込まれました。さらに最悪なことに、Availability Zoneの障害で単一構成のPrometheusがダウンし、その間のメトリクスが完全に欠落するという「監視システム」としての信頼性を揺るがす事態も発生しました。本稿では、こうしたSPOF(単一障害点)とストレージ問題を解決するために導入した、Thanosサイドカーパターンによるアーキテクチャ刷新の全容を共有します。

Prometheus HAとスケーラビリティの壁

当時の環境はAWS EKS上で動作しており、Prometheus Operatorを使用していました。秒間のIngestionは約15万サンプル。単純なリソース追加では対応しきれない以下の課題に直面していました。

直面したエラー:
level=error ts=2024-03-10T14:00:00.000Z caller=main.go:800 err="opening storage failed: lock DB directory: resource temporarily unavailable"
これは再起動時にWAL(Write-Ahead Log)の破損やロック競合が発生し、復旧に数時間を要した際のログの一部です。

本来であれば、Prometheus公式ドキュメントにあるように、シンプルさを保つことが推奨されます。しかし、エンタープライズレベルの「オブザーバビリティ」を確保するには、過去1年分のデータ参照や、99.9%の可用性が求められます。ローカルディスクに依存するPrometheus単体では、これらをコスト効率よく実現するのは物理的に不可能でした。

失敗談:単純なFederationとEBS拡張の限界

Thanosを導入する前、私たちは「Federation(階層化)」による解決を試みました。各クラスタのPrometheusから、中央集権的なGlobal PrometheusがデータをPullする構成です。しかし、これはすぐに破綻しました。Global Prometheusが全メトリクスをPullする際に巨大なネットワーク帯域を消費し、さらにスクレイピングのタイムアウトが多発したのです。また、単にEBSボリュームを2TBに拡張する案も検討しましたが、Prometheus再起動時のWALリプレイ(メモリへのデータ読み込み)に数十分かかり、その間のダウンタイムが許容できませんでした。「ローカルストレージに依存しない」かつ「Prometheus自体をステートレスに近づける」アプローチが必要だったのです。

解決策:Thanos Sidecarパターンの実装

最終的に採用したのは、Prometheus Pod内にThanos Sidecarコンテナを同居させ、2時間ごとに生成されるTSDBブロックを即座にS3へアップロードする「Thanosアーキテクチャ」です。これにより、Prometheus本体のRetentionを「6時間」程度まで短く設定しても、長期データはS3に永続化されるため問題なくなります。

以下は、kube-prometheus-stack (Helm) を使用した場合の、SidecarとObject Storage設定の重要な抜粋です。

// objstore.yaml (Kubernetes Secretとして登録)
// バケット名は環境に合わせて変更してください
type: S3
config:
  bucket: "my-company-metrics-longterm"
  endpoint: "s3.ap-northeast-1.amazonaws.com"
  region: "ap-northeast-1"
  encrypt_sse: true

---
// values.yaml (Prometheus Operator設定)
prometheus:
  prometheusSpec:
    # Prometheusのローカル保持期間を短縮(ストレージ節約)
    retention: 6h
    
    # Sidecarの有効化
    thanos:
      version: "v0.32.5"  # バージョン固定を推奨
      objectStorageConfig:
        key: objstore.yaml
        name: thanos-objstore-secret
        
    # HA構成のためにレプリカを2に設定
    replicas: 2
    
    # 異なるレプリカのデータを重複排除するために必要
    externalLabels:
      cluster: "production-eks-01"
      __replica__: "$(POD_NAME)" 

ここで重要なのは externalLabels の設定です。Thanos Queryは、同じ cluster ラベルを持ち、異なる __replica__ ラベルを持つデータセットを「同じデータの複製」とみなし、クエリ時に自動的に重複排除(Deduplication)を行います。これにより、Prometheusのレプリカを2台立てて片方が落ちても、欠損のないグラフを描画できる「Prometheus HA」が成立します。

長期ストレージ参照のためのStore Gateway

S3にアップロードされたデータを参照するには、Sidecarだけでは不十分です。Thanos Store Gateway をデプロイし、Querierがここを経由してS3のデータを検索できるようにする必要があります。

// Store Gatewayのデプロイメント設定例
args:
  - store
  - --data-dir=/var/thanos/store
  - --objstore.config-file=/etc/thanos/objstore.yaml
  # インデックスキャッシュを適切に設定しないとOOMKillされる可能性がある
  - --index-cache-size=500MB
  - --chunk-pool-size=500MB

導入効果とベンチマーク

Thanos導入前後での、ストレージコストとクエリパフォーマンスの比較結果です。特に「長期ストレージ」としてのコストパフォーマンスが劇的に改善しました。

指標導入前 (Prometheusのみ)導入後 (Thanos Sidecar)
データ保持期間15日 (EBS容量限界)無制限 (S3)
月額ストレージコスト$400 (GP3 4TB)$80 (S3 Standard + IA)
可用性単一障害点あり (SPOF)冗長構成 (HA)
過去データクエリ速度遅い (ローカルIO負荷高)高速 (Store Gateway並列処理)

コストが約1/5に圧縮されただけでなく、EBSのボリューム拡張作業という「Toil(苦役)」から解放されたことが運用チームにとって最大のメリットでした。また、S3のライフサイクルポリシーを活用し、1年経過したデータをGlacierに移行することで、さらにコストを下げることが可能です。

Thanos Sidecar公式ドキュメントを確認する

注意点とエッジケース:Compactorのメモリ管理

この構成は強力ですが、運用上の落とし穴があります。特に注意すべきは Thanos Compactor です。これはS3上のブロックを整理し、ダウンサンプリング(解像度を落として容量を削減)を行うコンポーネントですが、非常に多くのメモリを消費します。

パフォーマンス警告:
大規模なデータセットに対してCompactorを実行する場合、一時的に数十GBのメモリを消費することがあります。KubernetesのLimit設定を厳しくしすぎるとOOMKillが頻発し、ブロックのコンパクション(結合)が失敗し続けることで、クエリ速度が低下します。Compactorは専用のノードプールで実行するか、十分なリソースを割り当てることを推奨します。

また、ネットワーク転送コスト(NAT Gatewayの処理費用など)も見落としがちです。VPCエンドポイントを活用してS3へのトラフィックを内部ネットワークに閉じ込めることで、AWSの転送コストを抑える工夫が必要です。

結論

PrometheusのHA化と長期ストレージ対応は、システムが成長するにつれて避けては通れない課題です。Thanos Sidecarパターンは、既存のPrometheus構成への影響を最小限に抑えつつ、S3という安価で堅牢なバックエンドを活用できる点で、現時点での最適解と言えます。まだローカルディスクの残容量に怯えているのであれば、まずはSidecarの導入から始めてみてください。オブザーバビリティの質が劇的に向上するはずです。

Post a Comment