PrometheusとGrafanaによるetcdクラスター監視ガイド:Disk Fsync Latency、Raft Proposals、Leader ChangesでKubernetes Control Planeを守る

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

あるひどい夜があった。本番Kubernetesクラスターが突然すべてのkubectlコマンドを受け付けなくなったのだ。API serverは動いているし、nodeもすべてgreenなのに、すべてのリクエストがタイムアウトする。犯人はetcdのdisk I/Oボトルネックで、fsync latencyが2秒以上に跳ね上がっていた。そして、そのためのアラートが一つもなかったのだ。

その障害をきっかけに、etcdのmonitoringスタック全体を作り直した。この記事は、6ヶ月間の実運用から学んだことをまとめたものだ。

クイックスタート:5分でダッシュボードを手に入れる

すでにPrometheus + Grafanaが導入済みなら(このブログのPrometheus Operatorの記事を参照)、etcd metricsはすぐに取得できる状態だ。あとはscrapeを正しく設定するだけでいい。

ステップ1:etcdのmetricsポートを開く

etcdはデフォルトで:2381/metricsにmetricsを公開する。有効になっているか確認しよう:

# control plane node上で
curl -s http://localhost:2381/metrics | grep etcd_disk_wal_fsync

出力があればetcdはmetricsを公開している。なければ、etcd configにこのflagを追加しよう(kubeadmの場合は/etc/kubernetes/manifests/etcd.yaml):

spec:
  containers:
  - command:
    - etcd
    - --listen-metrics-urls=http://0.0.0.0:2381
    # ... 他のflagはそのまま

ステップ2:PrometheusにScrape Configを追加する

# prometheus-etcd-scrape.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: etcd
  namespace: monitoring
  labels:
    release: prometheus
spec:
  endpoints:
  - interval: 30s
    port: metrics
    scheme: http
    path: /metrics
  namespaceSelector:
    matchNames:
    - kube-system
  selector:
    matchLabels:
      component: etcd
kubectl apply -f prometheus-etcd-scrape.yaml

ステップ3:GrafanaダッシュボードをImportする

Grafana.comのDashboard ID 3070が最も標準的なetcdダッシュボードだ。すぐにImportできる:

# またはAPIを使う
curl -X POST http://admin:password@localhost:3000/api/dashboards/import \
  -H 'Content-Type: application/json' \
  -d '{"dashboard":{"id":3070},"overwrite":true,"inputs":[{"name":"DS_PROMETHEUS","type":"datasource","pluginId":"prometheus","value":"Prometheus"}]}'

Grafana → Dashboards → Import → ID 3070を入力 → Prometheus datasourceを選択。これで完了だ。

etcdで最も重要な3つのMetrics

etcdは数十種類のmetricsを公開しているが、実運用を通じて、クラスターの死活を左右するのはこの3グループだと分かった:

1. Disk Fsync Latency — etcdダウンの最大の原因

etcdはdurabilityを保証するためにWAL(Write-Ahead Log)を使用している。すべてのwriteはcommit前にdiskへfsyncされなければならない。diskが遅ければ、クラスター全体が遅くなる。

# P99 WAL fsync latency
histogram_quantile(0.99, sum(rate(etcd_disk_wal_fsync_duration_seconds_bucket[5m])) by (le))

# P99 Backend commit latency (bbolt)
histogram_quantile(0.99, sum(rate(etcd_disk_backend_commit_duration_seconds_bucket[5m])) by (le))

実際の閾値:

  • WAL fsync < 10ms:正常、SSDは良好
  • 10ms – 100ms:警告 — diskに問題が発生している
  • > 100ms:クリティカル — etcdがタイムアウト寸前、Kubernetes APIが不安定になる

私が経験した障害では、IOPS burst limitの枯渇によりEBS volumeがthrottleされ、WAL fsyncが2.3秒まで達した。教訓:etcdには専用SSDが必要で、他のworkloadと共有してはいけない。

2. Raft Proposals — etcdクラスターの鼓動

Raftはetcdのconsensusアルゴリズムだ。Proposalsとは、Raftを通じて提案およびcommitされるトランザクションのことだ。

# Proposal rate(トランザクション/秒)
rate(etcd_server_proposals_committed_total[5m])

# Proposal failures — これは危険なmetric
rate(etcd_server_proposals_failed_total[5m])

# 待機中のProposals(キュー)
etcd_server_proposals_pending

最も注目するのはproposals_pendingだ。この数値が下がることなく増え続けているなら、etcdが処理に追いつけていないということだ。たいていはdiskかmember間のnetworkがボトルネックになっている。

# アラート:5分間継続してproposal failuresが発生
sum(rate(etcd_server_proposals_failed_total[5m])) > 0

3. Leader Changes — クラスター不安定のサイン

3ノードのetcdクラスターには常に1つのleaderがある。Leader changeは、旧leaderがタイムアウトまたはクラッシュしたときに発生する。1日に数回のleader changeは正常だ(rolling restart時など)。しかし、頻繁なleader changeは非常に悪いサインだ。

# leader changeの回数
changes(etcd_server_leader_changes_seen_total[1h])

# 現在のnodeがleaderかどうか(1 = leader、0 = follower)
etcd_server_is_leader

以前、1つのnodeが残り2つのnodeとの間でnetwork packet loss >5%になったために、クラスターが1時間に50回以上leader changeした事例を見たことがある。Kubernetes APIはダウンしなかったが、latencyが急上昇し、kubectlが断続的にエラーを返した。

実際のアラート設定

ここが最も時間をかけた部分だ。alert fatigueはmonitoring初期設定時の問題だった。最初は閾値を低く設定しすぎてAlertmanagerが頻繁に発火し、徐々に誰もがアラートを無視するようになってしまった。適切な閾値に行き着くまで何度も調整が必要だった。

# etcd-alerts.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: etcd-alerts
  namespace: monitoring
spec:
  groups:
  - name: etcd.rules
    rules:
    # WAL fsync latencyが高い
    - alert: EtcdHighFsyncLatency
      expr: |
        histogram_quantile(0.99,
          sum(rate(etcd_disk_wal_fsync_duration_seconds_bucket[5m])) by (le, instance)
        ) > 0.1
      for: 10m
      labels:
        severity: warning
      annotations:
        summary: "{{ $labels.instance }}でetcd WAL fsync latencyが高い"
        description: "P99 WAL fsync = {{ $value | humanizeDuration }}、diskが過負荷状態の可能性がある"

    # Proposal failures
    - alert: EtcdProposalsFailing
      expr: sum(rate(etcd_server_proposals_failed_total[5m])) by (instance) > 0
      for: 5m
      labels:
        severity: critical
      annotations:
        summary: "{{ $labels.instance }}でetcd proposalsが失敗している"
        description: "{{ $value | humanize }} proposals/s が失敗 — クラスターがquorumを失う可能性がある"

    # Leader changesが多すぎる
    - alert: EtcdHighNumberOfLeaderChanges
      expr: changes(etcd_server_leader_changes_seen_total[1h]) > 3
      labels:
        severity: warning
      annotations:
        summary: "etcd leaderが頻繁に変更されている"
        description: "1時間で{{ $value }}回のleader change — node間のnetworkとdiskを確認すること"

    # leaderが消失
    - alert: EtcdNoLeader
      expr: sum(etcd_server_is_leader) == 0
      for: 1m
      labels:
        severity: critical
      annotations:
        summary: "etcdクラスターにleaderがいない!"
        description: "クラスターがquorumを失い、Kubernetes APIが機能しなくなる"
kubectl apply -f etcd-alerts.yaml

本番環境から学んだ実践Tips

etcd専用のdiskを用意する

VM/cloudを使う場合は、etcdのdata directory(/var/lib/etcd)用に専用ドライブをmountする。AWSではio2で最低3000 IOPS、bare metalではNVMe SSDを使う。etcdとcontainerdで同じドライブを共有しないこと — container imageのpullがetcd fsyncとIOPSを奪い合うことになる。

etcd member間のnetwork latencyを監視する

# etcd peer間のround-trip time
histogram_quantile(0.99, sum(rate(etcd_network_peer_round_trip_time_seconds_bucket[5m])) by (le, To))

member間のRTTが150msを超えると、Raft heartbeat timeoutが発生し始める。etcd nodeは理想的には同じAZまたは近いdatacenterに配置すべきだ。

DB size — 見落とされがちだが重要なmetric

# etcd DBの実際のsize
etcd_mvcc_db_total_size_in_bytes

# compact後のDB size
etcd_mvcc_db_total_size_in_use_in_bytes

etcdはDBを8GB未満に保つことを推奨している。超えると、パフォーマンスが大幅に低下する。auto-compactを設定しておこう:

# etcd flagsに追加する
- --auto-compaction-mode=periodic
- --auto-compaction-retention=1h

何かを変更する前にバックアップを取る

# メンテナンス前にetcdをsnapshotする
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-$(date +%Y%m%d-%H%M%S).db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

6時間ごとにcronjobを設定して7日分のバックアップを保持している。小規模なKubernetesクラスター(<1000 resources)では、snapshotファイルは通常10〜50MBだ。

本当に心配すべき状況

実運用から生まれた、etcdアラートを受けたときのチェックリストだ:

  • WAL fsync P99 > 500ms が10分間継続:I/O contentionを引き起こしているアプリを再起動するか、IOPSを即座にスケールする
  • Proposal failures > 0:まず3つのetcd member間のnetwork connectivityを確認する
  • Leader change >5回/時間:etcdctl endpoint health --clusterを実行して不安定なnodeを特定する
  • DB size > 6GB:今すぐdefragmentする:etcdctl defrag --cluster

etcdはKubernetesの中で、失うとクラスター全体のstateを失う唯一のコンポーネントだ。バックアップがなければ復元できない。良いmonitoringとは多くのアラートを出すことではなく、問題が障害になる前に検知することだ。

Share: