本番環境における手動モニタリングの混乱
6ヶ月前、私たちのチームは大きな壁に突き当たりました。3つのKubernetesクラスターにまたがる約40のマイクロサービスを管理していましたが、開発者が新しいサービスを立ち上げるたびに、私は1,200行にも及ぶprometheus.ymlのConfigMapを手動で更新しなければなりませんでした。PrometheusのPodをリロードする際、たった一つのタブやスペースのミスでパイプライン全体が壊れてしまわないよう、祈るような気持ちで作業していました。それは後手に回る消耗の激しいプロセスであり、DevOpsチームは常に火消し作業に追われていました。
限界が訪れたのは、ある金曜午後のデプロイ作業中でした。サービス名の些細な変更が原因で、Prometheusがスクレイプ対象を見失ってしまったのです。モニタリングシステムが変更に自動追従できなかったため、私たちは4時間もの間、状況を把握できない「目隠し状態」で過ごすことになりました。その瞬間、動的な環境においてPrometheusを静的な存在として扱うことは、災厄を招くレシピであると痛感したのです。
なぜ従来のPrometheusはKubernetesで限界を迎えるのか
苦労の根本的な原因はPrometheus自体ではなく、設定のオーバーヘッドにありました。標準的なセットアップでは、Prometheusは一つの巨大な設定ファイルに依存します。これは、Podがエフェメラル(一時的)で、サービスが数秒でスケールするKubernetesの世界では大きな負債となります。
アノテーション(prometheus.io/scrape: "true")による標準的なディスカバリは、小規模な環境では機能しますが、制御性に欠けます。特定のサービスに対して異なるスクレイプ間隔を簡単に定義することはできません。また、複数のチームで共有される150以上のアラートルールを管理することも、ロジスティクス上の悪夢となります。私たちは、モニタリングをデプロイ対象のアプリケーションと同じように宣言的なものにする必要がありました。
ソリューションの比較
移行前に、主に3つの道を検討しました:
- 手動のHelmチャート: コミュニティのPrometheusチャートを試しました。生のYAMLよりはマシでしたが、新しいアラートを追加するたびに巨大なvaluesファイルを管理する必要がある点は変わりませんでした。
- SaaSソリューション: Datadogのようなプラットフォームは非常に優れていますが、私たちの規模で見積もると月額2,500ドルを超えてしまいました。また、機密性の高いメトリクスデータは自社のVPC内に保持する必要もありました。
- Prometheus Operator: カスタムリソース定義(CRD)を使用してコンポーネントを管理します。モニタリングをKubernetes APIの「第一級市民」として扱います。
Operatorパターンを採用した決め手は、設定の権限をサービスを所有する各チームに委譲できる点でした。開発者が新しいアプリをモニタリングしたい場合、自身のHelmチャートにServiceMonitorオブジェクトを含めるだけで済みます。プラットフォームチームにチケットを送る必要はもうありません。
Prometheus Operatorの実装
1ヶ月のテストを経て、私たちはkube-prometheus-stackを採用しました。これはOperator、Prometheus、Grafana、Alertmanagerを一つのデプロイメントにまとめたものです。このスタックを使いこなすことは、ジュニア管理者からシニアプラットフォームエンジニアへとステップアップするための近道と言えます。
1. Helmによるデプロイ
セットアップはHelmリポジトリの追加から始まります。クラスターを整理し、厳格なリソースクォータを適用するために、常に専用のmonitoringネームスペースを使用するようにしています。
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install monitoring prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace
2. ServiceMonitorによる自動検出
本当の魔法はServiceMonitorで起こります。グローバル設定を編集する代わりに、小さなYAMLファイルを作成します。これにより、ラベルに基づいてどのサービスをスクレイプすべきかをPrometheusに伝えます。以下は、内部APIサービスに使用しているテンプレートです:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: backend-api-monitor
namespace: monitoring
labels:
release: monitoring # Prometheusインスタンスが期待するラベルと一致させる必要があります
spec:
selector:
matchLabels:
app: backend-api
namespaceSelector:
matchNames:
- production
endpoints:
- port: http-metrics
interval: 15s
path: /metrics
これを適用すると、Operatorは即座にPrometheusの設定を更新します。再起動も手動の介入も不要です。ただ、動くのです。
3. PrometheusRuleによる宣言的なアラート管理
かつてのアラート管理は、ネストされたConfigMapが入り乱れる混乱状態でした。PrometheusRuleを使えば、ソースコードのすぐそばでアラートを定義できます。サービスに高レイテンシが発生した場合のアラート定義は、そのサービスのデプロイメントと同じGitリポジトリに配置されます。
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: api-latency-alerts
labels:
release: monitoring
spec:
groups:
- name: backend.rules
rules:
- alert: HighLatency
expr: job:request_latency_seconds:mean5m{job="backend-api"} > 0.5
for: 10m
labels:
severity: critical
annotations:
summary: "{{ $labels.instance }} で高レイテンシが発生"
description: "10分以上にわたりレイテンシが0.5秒を超えています。"
4. Alertmanagerによるルーティング
OperatorはAlertmanagerも管理します。私たちは、クリティカルなアラートはPagerDutyへ、それ以外はSlackへルーティングしています。設定はKubernetesのSecret経由で管理されます。OperatorはこのSecretを監視しており、通知を落とすことなくルーティングの変更を適用してくれます。
現場から学んだ教訓
Operatorへの移行は、単なる技術的なアップグレードではありませんでした。それは「Monitoring as Code(コードとしてのモニタリング)」への文化的なシフトでした。本番環境での6ヶ月間で得られた教訓を紹介します:
- ラベルの不一致が失敗の第1原因:
ServiceMonitorのトラブルのほとんどは、releaseラベルの欠落に起因します。Prometheus CRDが期待するラベルと一致しない場合、メトリクスは表示されません。 - メモリ消費に注意: Prometheusはメモリを大量に消費します。運用開始から90日後、私たちのインスタンスは8GBのRAMでOOM(メモリ不足)によるクラッシュを起こしました。制限を12GBに引き上げ、保存ポリシーを15日間に調整する必要がありました。
- メトリクスの標準化: 開発者が自らモニターを作成することを推奨しましょう。ただし、Node Exporterやクラスターのヘルスチェックなどのグローバルなルールについては、アラートの重複を防ぐために中央リポジトリで管理するのが賢明です。
この移行により, チームは毎月約10時間の手動作業を削減できました。何より重要なのは、モニタリングがインフラと同じように動的であると信頼できるようになったことです。もし、今でも手動でPrometheusの設定を編集しているのなら、自動化をOperatorに任せるべき時が来ています。

