404 Not Found

404 Not Found


nginx

オブザーバビリティ

オブザーバビリティは本番運用の目です。メトリクスは傾向を示し, ログは原因を特定し, トレースは根本原因を突き止めます。この3本柱はすべて不可欠です。

1. 学ぶこと


2. SREエンジニアの実話

(1) ペインポイント: オンライン問題のブラックボックストラブルシューティング

OrderFlowで本番エラーが発生すると, Bobはアプリケーションログの海を探し回るしかありません。どのAPIが遅いのか?どのサービスに問題があるのか?リクエストがどのマイクロサービスを通過したのか?全く分かりません。AliceとBobは1つの本番問題のトラブルシューティングに4時間費やし, そのうち3時間は問題の場所を「推測」するのに使われます。

(2) オブザーバビリティ3本柱のアプローチ

100%
graph LR
    A["オブザーバビリティ<br/>3本柱"] --> B["メトリクス<br/>何が起きた?<br/>Prometheus"]
    A --> C["ログ<br/>なぜ起きた?<br/>ELK / Loki"]
    A --> D["トレース<br/>どこで起きた?<br/>Jaeger / Zipkin"]

メトリクスで問題を特定し, ログで原因を分析し, トレースで根本原因を突き止めます。

(3) 成果

Bobがオブザーバビリティシステムを構築した後, GrafanaダッシュボードがP99レイテンシとエラー率をリアルタイム表示し, Prometheusアラートが自動通知をトリガーし, OpenTelemetryトレースデータが30秒以内に根本原因を特定。問題解決の平均時間が4時間から15分に短縮されました。


3. Micrometerデータ収集

(1) 4種類のメトリクス

意味 増加のみ 典型的シナリオ
Counter カウンタ (増加のみ) はい 総リクエスト数, 注文作成数
Gauge 現在値 (増減可能) いいえ 現在のコネクション数, キュー長
Timer 所要時間分布 いいえ リクエスト応答時間
DistributionSummary 分布統計 いいえ リクエストボディサイズ分布

(1) ▶ サンプル:カスタムビジネスメトリクス

JAVA
@Service
public class OrderMetrics {

    private final Counter orderCreatedCounter;
    private final Counter orderCancelledCounter;
    private final Timer orderCreationTimer;
    private final Gauge pendingOrdersGauge;

    public OrderMetrics(MeterRegistry registry, OrderRepository orderRepo) {
        this.orderCreatedCounter = Counter.builder("orderflow.orders.created")
            .description("Total orders created")
            .tag("service", "orderflow")
            .register(registry);

        this.orderCancelledCounter = Counter.builder("orderflow.orders.cancelled")
            .description("Total orders cancelled")
            .register(registry);

        this.orderCreationTimer = Timer.builder("orderflow.orders.creation.duration")
            .description("Order creation duration")
            .publishPercentiles(0.5, 0.95, 0.99)
            .publishPercentileHistogram()
            .register(registry);

        this.pendingOrdersGauge = Gauge.builder("orderflow.orders.pending",
                orderRepo, repo -> repo.countByStatus("PENDING"))
            .description("Current pending orders count")
            .register(registry);
    }

    public void recordOrderCreated() {
        orderCreatedCounter.increment();
    }

    public Timer.Sample startCreationTimer() {
        return Timer.start(orderCreationTimer);
    }

    public void recordCreationComplete(Timer.Sample sample) {
        sample.stop(orderCreationTimer);
    }
}

出力:

TEXT
// 実行成功

(2) ▶ サンプル:サービスでのメトリクス使用

JAVA
@Service
public class OrderServiceImpl implements OrderService {

    private final OrderMetrics metrics;

    @Override
    @Transactional
    public Order createOrder(CreateOrderRequest request) {
        Timer.Sample sample = metrics.startCreationTimer();
        try {
            Order order = doCreateOrder(request);
            metrics.recordOrderCreated();
            return order;
        } finally {
            metrics.recordCreationComplete(sample);
        }
    }
}

出力:

TEXT
// 実行成功

4. Prometheus統合

(1) ▶ サンプル:Prometheus依存関係と設定

XML
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

出力:

TEXT
// 実行成功
YAML
# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,prometheus,metrics
  metrics:
    tags:
      application: ${spring.application.name}
    export:
      prometheus:
        enabled: true

(2) ▶ サンプル:Prometheusスクレイプ設定

YAML
# prometheus.yml
scrape_configs:
  - job_name: 'orderflow'
    metrics_path: '/actuator/prometheus'
    scrape_interval: 15s
    static_configs:
      - targets: ['orderflow:8080']

出力:

TEXT
監視設定が読み込まれました
Prometheus targets: 3 active
Grafana dashboard: ready

(3) ▶ サンプル:PromQLクエリとアラート

YAML
# PromQLクエリ
# 注文APIのP99レイテンシ
histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket{uri=~"/api/v1/orders.*"}[5m])) by (le, uri))

# エラー率 (5xxレスポンス)
sum(rate(http_server_requests_seconds_total{status=~"5.."}[5m]))
/
sum(rate(http_server_requests_seconds_total[5m]))

# 1分あたりの注文数
rate(orderflow_orders_created_total[1m]) * 60

出力:

TEXT
設定が正常に適用されました
YAML
# alert_rules.yml
groups:
- name: orderflow
  rules:
  - alert: HighErrorRate
    expr: |
      sum(rate(http_server_requests_seconds_total{status=~"5.."}[5m]))
      / sum(rate(http_server_requests_seconds_total[5m])) > 0.001
    for: 5m
    labels:
      severity: critical
    annotations:
      summary: "OrderFlow error rate exceeds 0.1%"

  - alert: HighP99Latency
    expr: |
      histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket[5m])) by (le)) > 0.1
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: "OrderFlow P99 latency exceeds 100ms"

5. Grafanaダッシュボード

(1) 主要ダッシュボードメトリクス

ダッシュボード メトリクス PromQL
リクエストレート QPS sum(rate(http_server_requests_seconds_total[5m]))
P50/P95/P99レイテンシ 応答時間分布 histogram_quantile(0.99, ...)
エラー率 5xx比率 rate(...{status=~"5.."})/rate(...)
JVMヒープメモリ メモリ使用量 jvm_memory_used_bytes{area="heap"}
GCポーズ GC時間 rate(jvm_gc_pause_seconds_sum[5m])
HikariCPアクティブコネクション コネクションプール使用量 hikaricp_connections_active
注文作成レート ビジネスメトリクス rate(orderflow_orders_created_total[1m])

(1) ▶ サンプル:GrafanaダッシュボードJSONスニペット

JSON
{
  "dashboard": {
    "title": "OrderFlow Observability",
    "panels": [
      {
        "title": "Request Rate (QPS)",
        "type": "timeseries",
        "targets": [{
          "expr": "sum(rate(http_server_requests_seconds_total{application=\"orderflow-service\"}[5m]))"
        }]
      },
      {
        "title": "P99 Latency",
        "type": "timeseries",
        "targets": [{
          "expr": "histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket{application=\"orderflow-service\"}[5m])) by (le))"
        }]
      },
      {
        "title": "Error Rate",
        "type": "gauge",
        "targets": [{
          "expr": "sum(rate(http_server_requests_seconds_total{application=\"orderflow-service\",status=~\"5..\"}[5m])) / sum(rate(http_server_requests_seconds_total{application=\"orderflow-service\"}[5m]))"
        }],
        "fieldConfig": {
          "defaults": {
            "thresholds": {
              "steps": [
                {"value": 0, "color": "green"},
                {"value": 0.001, "color": "red"}
              ]
            }
          }
        }
      }
    ]
  }
}

出力:

JSON
{
  "dashboard": {
    "title": "OrderFlow Observability",
    "panels": [
      {
        "title": "Request Rate (QPS)",
        "type": "timeseries",
        "targets": [
          {
            "expr": "sum(rate(http_server_requests_seconds_total{application=\"orderflow-service\"}[5m]))"
          }
        ]
      },
      {
        "title": "P99 Latency",
        "type": "timeseries",
        "targets": [
          {
            "expr": "histogram_quantile(0.99, sum(rate(http_server_request

6. OpenTelemetryトレース

(1) 分散トレーシングの概念

100%
graph LR
    A["Client"] --> B["API Gateway<br/>Trace: abc123<br/>Span 1"]
    B --> C["Order Service<br/>Span 2<br/>parent: Span 1"]
    C --> D["Product Service<br/>Span 3<br/>parent: Span 2"]
    C --> E["Database<br/>Span 4<br/>parent: Span 2"]
概念 意味
Trace 単一リクエストの完全な経路
Span チェーン内の1操作
Context Span間を伝播するトレースコンテキスト
SpanId 現在のSpanの一意識別子
TraceId トレース全体の一意識別子

(1) ▶ サンプル:OpenTelemetry依存関係

XML
<dependency>
    <groupId>io.opentelemetry.instrumentation</groupId>
    <artifactId>opentelemetry-spring-boot-starter</artifactId>
</dependency>

出力:

TEXT
// 実行成功
YAML
# application.yml
otel:
  exporter:
    otlp:
      endpoint: http://otel-collector:4317
  resource:
    attributes:
      service.name: orderflow-service
  traces:
    exporter: otlp

(2) ▶ サンプル:カスタムSpan

JAVA
@Service
public class OrderService {

    private final Tracer tracer;

    public OrderService(Tracer tracer) {
        this.tracer = tracer;
    }

    public Order createOrder(CreateOrderRequest request) {
        Span span = tracer.spanBuilder("create-order")
            .setAttribute("product.id", request.productId())
            .setAttribute("quantity", request.quantity())
            .startSpan();

        try (Scope scope = span.makeCurrent()) {
            Order order = doCreateOrder(request);
            span.setAttribute("order.id", order.getId());
            return order;
        } catch (Exception e) {
            span.recordException(e);
            span.setStatus(StatusCode.ERROR, e.getMessage());
            throw e;
        } finally {
            span.end();
        }
    }
}

出力:

TEXT
// 実行成功

7. 総合サンプル:OrderFlow SLOダッシュボード

YAML
# docker-compose.observability.yml
version: "3.9"
services:
  prometheus:
    image: prom/prometheus:latest
    ports: ["9090:9090"]
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - ./alert_rules.yml:/etc/prometheus/alert_rules.yml

  grafana:
    image: grafana/grafana:latest
    ports: ["3000:3000"]
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin
    volumes:
      - grafana-data:/var/lib/grafana

  otel-collector:
    image: otel/opentelemetry-collector:latest
    ports: ["4317:4317", "4318:4318"]
    volumes:
      - ./otel-collector-config.yml:/etc/otelcol/config.yaml

  jaeger:
    image: jaegertracing/all-in-one:latest
    ports: ["16686:16686"]

  app:
    build: .
    ports: ["8080:8080"]
    environment:
      OTEL_EXPORTER_OTLP_ENDPOINT: http://otel-collector:4317
      OTEL_SERVICE_NAME: orderflow-service
      MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE: health,prometheus,metrics
SLOメトリクス 目標 アラート閾値 PromQL
P99レイテンシ < 100 ms 5分間 > 100 ms histogram_quantile(0.99, ...)
エラー率 < 0.1% 5分間 > 0.1% rate(5xx)/rate(all)
可用性 > 99.9% 5分間 < 99.9% 1 - rate(5xx)/rate(all)
注文スループット > 1,000/分 10分間 < 500/分 rate(orders_created)[1m]*60

❓ よくある質問

Q メトリクスとログの違いは?
A メトリクスは集計値 (カウンタ, ヒストグラム)で, 傾向の監視とアラートのトリガーに適しています。ログは離散イベントの記録で, 具体的な原因の分析に適しています。メトリクスで問題を特定し, ログで原因を分析します。
Q PrometheusとGrafanaの役割はそれぞれ何ですか?
A Prometheusはデータ収集と保存 (時系列データベース)およびアラートルールを担当します。Grafanaは可視化 (ダッシュボード)を担当します。組み合わせて使用する場合, Prometheusがデータソース, Grafanaがプレゼンテーション層となります。
Q OpenTelemetryとJaegerの関係は?
A OpenTelemetryは「収集」の標準 (SDK + API)で, Jaegerは「保存と可視化」のバックエンドです。アプリケーションはOTel SDKでトレースデータを収集し, Jaegerに送信して保存とクエリを行います。
Q publishPercentileshistogram_quantileの違いは?
A publishPercentilesはアプリケーション側でパーセンタイルを計算 (Prometheusのストレージを節約, 複数インスタンス間では精度が低下)。histogram_quantileはPrometheus側で計算 (複数インスタンスの集約をサポート, より高精度)。本番環境ではPrometheus側での計算を推奨します。
Q SLOとSLAの違いは?
A SLO (Service Level Objective)は内部目標で, 例えばP99 < 100ms。SLA (Service Level Agreement)は顧客への誓約で, 違反時の補償を含みます。SLOがSLAの基盤となります。
Q ログバックエンドの選び方は?
A ELK (Elasticsearch + Logstash + Kibana)は強力ですがリソース消費が大きい; Loki (Grafanaエコシステムの一部)は軽量ですがクエリ機能が限定的。中小規模プロジェクトにはLoki, 大規模プロジェクトにはELKを推奨します。

📖 まとめ


📝 練習問題

  1. 基本問題 (難易度:⭐): OrderFlowにMicrometer + Prometheusを設定し, /actuator/prometheusエンドポイントを公開し, 3つのカスタムビジネスメトリクス (注文作成数, 注文処理時間, 保留中の注文数)を定義してください。

  2. 応用問題 (難易度:⭐⭐): Prometheusのデータ収集を設定し, Grafanaダッシュボードを構築してHTTP QPS, P99レイテンシ, エラー率, JVMヒープメモリを表示してください。2つのPrometheusアラートルール (高エラー率と高レイテンシ)を書いてください。

  3. チャレンジ (難易度:⭐⭐⭐): OpenTelemetryとJaegerを統合して分散トレーシングを実装してください。OrderServiceにカスタムSpanを追加して注文フロー全体 (Controller → Service → Repository)をトレースし, Jaeger UIでトレース詳細を確認してください。

Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%