TraefikリバースプロキシをPrometheusとGrafanaで監視する:Request Rate、Error Rate、Latencyの追跡

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

実際に直面した問題

VPS上で約8つのコンテナのreverse proxyとしてTraefikをデプロイしていた。最初の2週間は問題なく動いていた。ある朝、ユーザーからサイトが異常に遅いと報告があった――コンテナはrunning状態で、Traefik側に明確なエラーもない。サーバーにSSHしてdocker statsを実行し、コンテナのログを1つずつ確認した。ボトルネックを見つけるまで約30分かかった。あるserviceがrequestの処理に時間をかけすぎていたのが原因だった。

問題はツールが足りないことではなかった。問題はTraefikがブラックボックスとして動作することだ。起動していることはわかるが、何件のrequestが通過しているか、5xxエラーが何件発生しているか、平均latencyが何ミリ秒なのかがわからない。その数値がなければ、デバッグは手探りになってしまう。

Traefikに不足しているvisibilityとは

デフォルトではTraefikはmetricsを外部に公開しない。コンテナが正常であっても、見えるのはテキスト形式のログだけ――時系列グラフを描くための数値がない。具体的に不足しているもの:

  • Request Rate:各entrypointやrouterを通過するrequestは毎秒何件か?
  • Error Rate:4xx/5xxレスポンスの割合は?どのserviceが最も多くエラーを返しているか?
  • Latencyパーセンタイル:各backendサービスのP50、P95、P99 latencyはどのくらいか?
  • Active connections:ある時点でいくつのTCP connectionが開いているか?

TraefikはPrometheus、Datadog、InfluxDBへのmetricsエクスポートをサポートしている――ただしデフォルトではすべて無効だ。以下の2つの方法は、手軽なものから本格的なものへと、実際のニーズに応じて選べる。

解決方法

方法1:access logを有効化して手動で分析する

即時デバッグに最も手早い方法だ。traefik.ymlに以下を追加する:

accessLog:
  filePath: "/var/log/traefik/access.log"
  format: json
  fields:
    headers:
      defaultMode: drop

5xxエラーをカウントする:

grep '"DownstreamStatus":5' /var/log/traefik/access.log | wc -l

問題を素早く特定したいときに便利だ。ただし、ダッシュボードもアラートもなく、時系列のトレンドも把握できない――長期的なproduction環境には適さない。

方法2:Prometheus metrics + Grafana

TraefikにはPrometheus形式の/metricsエンドポイントが組み込まれている――有効化して、Prometheusにscrapeさせ、Grafanaダッシュボードを作成すれば完了だ。セットアップに約30分かかるが、一度構築すれば永続的に使える。これが自分がproductionで実際に使っている方法だ。

Docker Composeを使った完全なセットアップ

ステップ1:TraefikにPrometheus metricsを公開する設定を行う

traefik.ymlにmetricsセクションを追加する。注意:addRoutersLabelsaddServicesLabelsは最も見落としやすい2つのオプションだ――これらがないと、metricsはentrypoint単位でのaggregateのみになり、service単位のbreakdownができなくなる:

# traefik.yml
api:
  dashboard: true
  insecure: true

entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"
  metrics:
    address: ":8082"

metrics:
  prometheus:
    entryPoint: metrics
    addEntryPointsLabels: true
    addRoutersLabels: true
    addServicesLabels: true
    buckets:
      - 0.1
      - 0.3
      - 1.2
      - 5.0

providers:
  docker:
    exposedByDefault: false

ステップ2:全スタックのDocker Compose設定

Traefik + Prometheus + Grafana用のdocker-compose.ymlファイル:

version: '3.8'

networks:
  proxy:
    external: true
  monitoring:
    internal: true

volumes:
  prometheus_data:
  grafana_data:

services:
  traefik:
    image: traefik:v3.0
    container_name: traefik
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"   # dashboard
      - "8082:8082"   # metrics
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/traefik.yml:ro
    networks:
      - proxy
      - monitoring

  prometheus:
    image: prom/prometheus:v2.51.0
    container_name: prometheus
    restart: unless-stopped
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.retention.time=15d'
    networks:
      - monitoring

  grafana:
    image: grafana/grafana:10.4.0
    container_name: grafana
    restart: unless-stopped
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=your_strong_password
      - GF_USERS_ALLOW_SIGN_UP=false
    volumes:
      - grafana_data:/var/lib/grafana
    networks:
      - monitoring
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.grafana.rule=Host(`grafana.yourdomain.com`)"
      - "traefik.http.routers.grafana.entrypoints=websecure"

ステップ3:PrometheusにTraefikをscrapeさせる設定

prometheus.ymlファイル。TraefikとPrometheusが同じmonitoringネットワーク上にあるため、IPアドレスの代わりにサービス名を使用できる――アドレスをハードコードする必要がない:

global:
  scrape_interval: 15s
  evaluation_interval: 15s

rule_files:
  - "traefik_alerts.yml"

scrape_configs:
  - job_name: 'traefik'
    static_configs:
      - targets: ['traefik:8082']
    metrics_path: /metrics

TraefikがmetricsをExposeしているか確認する:

curl http://localhost:8082/metrics | grep traefik_entrypoint

出力例:

traefik_entrypoint_requests_total{code="200",entrypoint="websecure",method="GET",protocol="http"} 1523
traefik_entrypoint_requests_total{code="404",entrypoint="websecure",method="GET",protocol="http"} 42
traefik_entrypoint_request_duration_seconds_bucket{entrypoint="websecure",le="0.1"} 1401

ステップ4:Grafanaダッシュボード用のPromQLクエリ

GrafanaにPrometheusデータソースを追加し(http://prometheus:9090)、以下のパネルでダッシュボードを作成する:

Request Rate — entrypoint別の合計request/秒:

sum(rate(traefik_entrypoint_requests_total[2m])) by (entrypoint)

Error Rate — service別の5xxエラーrequest割合:

100 * sum(rate(traefik_service_requests_total{code=~"5.."}[2m])) by (service)
/
sum(rate(traefik_service_requests_total[2m])) by (service)

P95 Latency — service別の95パーセンタイルlatency:

histogram_quantile(0.95,
  sum(rate(traefik_service_request_duration_seconds_bucket[5m])) by (le, service)
)

entrypoint別のActive connections:

traefik_entrypoint_open_connections{entrypoint="websecure"}

ゼロから作りたくない場合は?Grafana dashboard ID 17346をインポートしよう――公式Traefikダッシュボードで、すぐに使えてあとからカスタマイズできる。

ステップ5:自動アラートのためのalert rules設定

prometheus.ymlと同じディレクトリにtraefik_alerts.ymlファイルを作成する:

groups:
  - name: traefik_alerts
    rules:
      - alert: TraefikHighErrorRate
        expr: |
          100 * sum(rate(traefik_service_requests_total{code=~"5.."}[5m])) by (service)
          /
          sum(rate(traefik_service_requests_total[5m])) by (service)
          > 5
        for: 2m
        labels:
          severity: warning
        annotations:
          summary: "サービス {{ $labels.service }} のerror rateが5%を超えました"
          description: "現在のerror rate: {{ $value | printf \"%.1f\" }}%"

      - alert: TraefikHighLatency
        expr: |
          histogram_quantile(0.95,
            sum(rate(traefik_service_request_duration_seconds_bucket[5m])) by (le, service)
          ) > 2
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "{{ $labels.service }} のP95 latencyが2秒を超えました"

実際の成果

ダッシュボードが稼働すると、状況は大きく変わった。service別のrequest rate、backendコンテナがクラッシュしたときのerror rate、P50/P95/P99によるlatencyのbreakdown――すべてが1画面でリアルタイムに表示される。もうサーバーにSSHする必要はない。外出中でもスマートフォンでダッシュボードを開いて、ユーザーから報告が来る前に問題を発見できるようになった。

一点注意が必要なのは:traefik_service_*のmetricsは、そのserviceに少なくとも1件の実際のrequestが通過して初めて表示される。セットアップ直後にGrafanaパネルが「No data」と表示されるのは正常だ――まずテスト用のrequestをいくつか送ってみよう:

curl -s https://yourdomain.com/healthz
# 15〜30秒待ってから、Grafanaで再確認する

リソースについて:Prometheus + GrafanaはRAMを約300MB消費する――2GB VPSなら問題ない。--storage.tsdb.retention.time=15d15日分のデータを保持でき、トレンドの確認や週末に誰も気づかないうちに発生したインシデントの追跡に十分な期間だ。

Share: