Skip to content
Published on

Observability & モニタリング完全ガイド 2025: ロギング、メトリクス、トレーシング、アラート戦略

Authors

TOC

1. モニタリング vs Observability

1.1 モニタリングの限界(げんかい)

従来(じゅうらい)のモニタリングは、**既知(きち)の問題(もんだい)(known unknowns)**を検出(けんしゅつ)することに焦点(しょうてん)を当(あ)てています。CPU使用率(しようりつ)が90%を超(こ)えたらアラート、ディスク使用量が80%を超えたらアラート -- このような閾値(しきいち)ベースのアプローチです。

しかし、現代(げんだい)の分散(ぶんさん)システムではこれだけでは不十分(ふじゅうぶん)です:

  • マイクロサービス間(かん)の複雑(ふくざつ)な相互作用(そうごさよう)
  • 一時的(いちじてき)なエラーの頻発(ひんぱつ)
  • 予測(よそく)できない問題(unknown unknowns)の増加(ぞうか)
  • 単一(たんいつ)メトリクスでは説明できないパフォーマンス低下(ていか)

1.2 Observabilityとは

Observabilityは、システムの**外部出力(がいぶしゅつりょく)を通(とお)じて内部状態(ないぶじょうたい)**を理解(りかい)できる能力(のうりょく)です。

核心的(かくしんてき)な違(ちが)い:

  • モニタリング: 「何(なに)が壊(こわ)れたか?」
  • Observability: 「なぜ壊れたか?」

Observabilityの三本柱(さんぼんばしら)(Three Pillars):

  1. Logs - 離散(りさん)イベントの記録(きろく)
  2. Metrics - 時系列(じけいれつ)の数値測定(すうちそくてい)
  3. Traces - 分散システムでのリクエストフロー

2. OpenTelemetry (OTel) コア概念(がいねん)

2.1 OpenTelemetryとは

OpenTelemetryはCNCFが管理(かんり)する**ベンダー中立(ちゅうりつ)**なテレメトリ収集(しゅうしゅう)フレームワークです。ログ、メトリクス、トレースを1つの標準(ひょうじゅん)に統合(とうごう)します。

構成要素(こうせいようそ):

  • API: 計装(けいそう)のためのインターフェース
  • SDK: APIの実装(じっそう)
  • Collector: テレメトリデータの収集、処理(しょり)、エクスポート
  • 自動計装: コード変更なしでテレメトリを収集

2.2 OTel SDK設定(せってい)

// Node.js OpenTelemetry設定
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { Resource } from '@opentelemetry/resources';
import {
  ATTR_SERVICE_NAME,
  ATTR_SERVICE_VERSION,
} from '@opentelemetry/semantic-conventions';

const sdk = new NodeSDK({
  resource: new Resource({
    [ATTR_SERVICE_NAME]: 'order-service',
    [ATTR_SERVICE_VERSION]: '1.2.0',
    environment: process.env.NODE_ENV || 'production',
  }),
  traceExporter: new OTLPTraceExporter({
    url: 'http://otel-collector:4318/v1/traces',
  }),
  metricReader: new PeriodicExportingMetricReader({
    exporter: new OTLPMetricExporter({
      url: 'http://otel-collector:4318/v1/metrics',
    }),
    exportIntervalMillis: 30000,
  }),
  instrumentations: [
    getNodeAutoInstrumentations({
      // HTTP、Express、pg、Redisなどを自動計装
      '@opentelemetry/instrumentation-http': {
        ignoreIncomingRequestHook: (req) => {
          // ヘルスチェックパスを除外
          return req.url === '/health';
        },
      },
      '@opentelemetry/instrumentation-express': {
        enabled: true,
      },
    }),
  ],
});

sdk.start();

// 正常終了時にテレメトリをフラッシュ
process.on('SIGTERM', () => {
  sdk.shutdown().then(() => process.exit(0));
});

2.3 手動(しゅどう)Span作成(さくせい)

import { trace, SpanStatusCode, context } from '@opentelemetry/api';

const tracer = trace.getTracer('order-service');

async function processOrder(orderData) {
  // 手動Span作成
  return tracer.startActiveSpan('processOrder', async (span) => {
    try {
      // Spanに属性を追加
      span.setAttribute('order.id', orderData.id);
      span.setAttribute('order.total', orderData.total);
      span.setAttribute('order.items_count', orderData.items.length);

      // 子Span:在庫確認
      const inventory = await tracer.startActiveSpan(
        'checkInventory',
        async (childSpan) => {
          childSpan.setAttribute('inventory.warehouse', 'us-east-1');
          const result = await inventoryService.check(orderData.items);
          childSpan.end();
          return result;
        }
      );

      if (!inventory.available) {
        span.setStatus({
          code: SpanStatusCode.ERROR,
          message: 'Insufficient inventory',
        });
        throw new Error('Insufficient inventory');
      }

      // 子Span:決済処理
      const payment = await tracer.startActiveSpan(
        'processPayment',
        async (childSpan) => {
          childSpan.setAttribute('payment.method', orderData.paymentMethod);
          childSpan.setAttribute('payment.amount', orderData.total);
          const result = await paymentService.charge(orderData);
          childSpan.end();
          return result;
        }
      );

      // Spanイベント追加
      span.addEvent('order_completed', {
        'order.id': orderData.id,
        'payment.transaction_id': payment.transactionId,
      });

      span.setStatus({ code: SpanStatusCode.OK });
      return { orderId: orderData.id, transactionId: payment.transactionId };
    } catch (error) {
      span.setStatus({
        code: SpanStatusCode.ERROR,
        message: error.message,
      });
      span.recordException(error);
      throw error;
    } finally {
      span.end();
    }
  });
}

2.4 OTel Collector設定

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

processors:
  batch:
    send_batch_size: 1024
    timeout: 5s

  memory_limiter:
    check_interval: 1s
    limit_mib: 512
    spike_limit_mib: 128

  # テールサンプリング(エラートレースを優先収集)
  tail_sampling:
    decision_wait: 10s
    policies:
      - name: error-policy
        type: status_code
        status_code:
          status_codes: [ERROR]
      - name: slow-policy
        type: latency
        latency:
          threshold_ms: 1000
      - name: probabilistic-policy
        type: probabilistic
        probabilistic:
          sampling_percentage: 10

exporters:
  otlp/jaeger:
    endpoint: jaeger:4317
    tls:
      insecure: true

  prometheus:
    endpoint: 0.0.0.0:8889

  loki:
    endpoint: http://loki:3100/loki/api/v1/push

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch, tail_sampling]
      exporters: [otlp/jaeger]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [prometheus]
    logs:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [loki]

3. ロギング (Logging)

3.1 構造化(こうぞうか)ロギング

// pinoを活用した構造化ロギング
import pino from 'pino';

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  formatters: {
    level(label) {
      return { level: label };
    },
    bindings(bindings) {
      return {
        service: 'order-service',
        version: '1.2.0',
        host: bindings.hostname,
        pid: bindings.pid,
      };
    },
  },
  timestamp: pino.stdTimeFunctions.isoTime,
  // 機密情報の除去
  redact: ['req.headers.authorization', 'req.headers.cookie', '*.password'],
});

// Correlation IDミドルウェア
function correlationMiddleware(req, res, next) {
  const correlationId = req.headers['x-correlation-id'] || generateId();
  req.correlationId = correlationId;
  res.setHeader('x-correlation-id', correlationId);

  // リクエスト毎のchild logger
  req.log = logger.child({
    correlationId,
    requestId: generateId(),
    method: req.method,
    path: req.path,
    userAgent: req.headers['user-agent'],
  });

  next();
}

// 使用例
app.post('/api/orders', correlationMiddleware, async (req, res) => {
  req.log.info({ body: req.body }, 'Order creation started');

  try {
    const order = await createOrder(req.body);
    req.log.info(
      { orderId: order.id, duration: Date.now() - req.startTime },
      'Order created successfully'
    );
    res.json(order);
  } catch (error) {
    req.log.error(
      { err: error, body: req.body },
      'Order creation failed'
    );
    res.status(500).json({ error: 'Internal server error' });
  }
});

3.2 ログレベル戦略(せんりゃく)

レベル用途
FATALシステム停止データベース接続完全失敗
ERRORエラー発生決済処理失敗
WARN潜在的問題リトライ成功、キャッシュミス
INFO主要ビジネスイベント注文作成、ユーザーログイン
DEBUGデバッグ情報SQLクエリ、APIリクエスト/レスポンス
TRACE詳細追跡関数エントリ/イグジット、変数値

3.3 ELK Stack

# docker-compose.yml - ELK Stack
version: '3.8'
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.12.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    ports:
      - "9200:9200"
    volumes:
      - es-data:/usr/share/elasticsearch/data

  logstash:
    image: docker.elastic.co/logstash/logstash:8.12.0
    volumes:
      - ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
    depends_on:
      - elasticsearch

  kibana:
    image: docker.elastic.co/kibana/kibana:8.12.0
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    depends_on:
      - elasticsearch

volumes:
  es-data:
# logstash.conf
input {
  tcp {
    port => 5044
    codec => json
  }
}

filter {
  # タイムスタンプパース
  date {
    match => [ "timestamp", "ISO8601" ]
    target => "@timestamp"
  }

  # エラースタックトレースのパース
  if [level] == "error" {
    grok {
      match => {
        "stack" => "%{GREEDYDATA:error_class}: %{GREEDYDATA:error_message}"
      }
    }
  }
}

output {
  elasticsearch {
    hosts => ["elasticsearch:9200"]
    index => "logs-%{+YYYY.MM.dd}"
  }
}

3.4 Grafana Loki(軽量(けいりょう)ログ収集)

// Lokiに直接ログ送信 (winston-loki)
import winston from 'winston';
import LokiTransport from 'winston-loki';

const logger = winston.createLogger({
  transports: [
    new LokiTransport({
      host: 'http://loki:3100',
      labels: {
        service: 'order-service',
        environment: 'production',
      },
      json: true,
      batching: true,
      interval: 5,
    }),
  ],
});

4. メトリクス (Metrics)

4.1 Prometheus基本(きほん)

Prometheusはプルベースの時系列データベースです。

メトリクスタイプ:

  • Counter: 単調増加(たんちょうぞうか)する値(リクエスト数、エラー数)
  • Gauge: 増減(ぞうげん)可能な値(現在の接続数、メモリ使用量)
  • Histogram: 値の分布(ぶんぷ)(応答時間、ペイロードサイズ)
  • Summary: クライアント側の分位数(ぶんいすう)計算
// Node.js Prometheusクライアント
import { Registry, Counter, Gauge, Histogram, Summary } from 'prom-client';

const register = new Registry();

// デフォルトメトリクス収集(CPU、メモリ、イベントループなど)
import { collectDefaultMetrics } from 'prom-client';
collectDefaultMetrics({ register });

// Counter: HTTPリクエスト数
const httpRequestsTotal = new Counter({
  name: 'http_requests_total',
  help: 'Total number of HTTP requests',
  labelNames: ['method', 'path', 'status'],
  registers: [register],
});

// Gauge: 現在のアクティブ接続数
const activeConnections = new Gauge({
  name: 'active_connections',
  help: 'Number of active connections',
  registers: [register],
});

// Histogram: リクエスト応答時間
const httpRequestDuration = new Histogram({
  name: 'http_request_duration_seconds',
  help: 'HTTP request duration in seconds',
  labelNames: ['method', 'path', 'status'],
  buckets: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
  registers: [register],
});

// Expressミドルウェア
function metricsMiddleware(req, res, next) {
  const start = process.hrtime.bigint();
  activeConnections.inc();

  res.on('finish', () => {
    const duration = Number(process.hrtime.bigint() - start) / 1e9;
    const labels = {
      method: req.method,
      path: req.route?.path || req.path,
      status: res.statusCode.toString(),
    };

    httpRequestsTotal.inc(labels);
    httpRequestDuration.observe(labels, duration);
    activeConnections.dec();
  });

  next();
}

// メトリクスエンドポイント
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', register.contentType);
  res.end(await register.metrics());
});

4.2 PromQL核心(かくしん)クエリ

# 秒間リクエスト数 (RPS)
rate(http_requests_total[5m])

# サービス別エラー率
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service)

# p99応答時間
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))

# p95応答時間(パス別)
histogram_quantile(0.95,
  sum(rate(http_request_duration_seconds_bucket[5m])) by (le, path)
)

# メモリ使用量の増加率
deriv(process_resident_memory_bytes[1h])

# Apdexスコア(目標応答時間0.5秒)
(
  sum(rate(http_request_duration_seconds_bucket{le="0.5"}[5m]))
  +
  sum(rate(http_request_duration_seconds_bucket{le="2.0"}[5m]))
) / 2
/
sum(rate(http_request_duration_seconds_count[5m]))

4.3 Recording Rules

# prometheus-rules.yml
groups:
  - name: request_rates
    interval: 30s
    rules:
      # 事前計算されたリクエスト率
      - record: service:http_requests:rate5m
        expr: sum(rate(http_requests_total[5m])) by (service)

      # 事前計算されたエラー率
      - record: service:http_errors:rate5m
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
          /
          sum(rate(http_requests_total[5m])) by (service)

      # 事前計算されたレイテンシパーセンタイル
      - record: service:http_latency:p99
        expr: |
          histogram_quantile(0.99,
            sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
          )

5. 可視化(かしか) (Grafana)

5.1 RED Methodダッシュボード

RED Methodはサービスの核心的パフォーマンス指標(しひょう)をモニタリングする方法論(ほうほうろん)です:

  • Rate: 秒間リクエスト数
  • Errors: エラー率
  • Duration: 応答時間
{
  "panels": [
    {
      "title": "Request Rate (RPS)",
      "type": "timeseries",
      "targets": [
        {
          "expr": "sum(rate(http_requests_total[5m])) by (service)",
          "legendFormat": "service"
        }
      ]
    },
    {
      "title": "Error Rate (%)",
      "type": "timeseries",
      "targets": [
        {
          "expr": "100 * sum(rate(http_requests_total{status=~\"5..\"}[5m])) by (service) / sum(rate(http_requests_total[5m])) by (service)",
          "legendFormat": "service"
        }
      ]
    },
    {
      "title": "Response Time (p99)",
      "type": "timeseries",
      "targets": [
        {
          "expr": "histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service))",
          "legendFormat": "service"
        }
      ]
    }
  ]
}

5.2 USE Method(インフラモニタリング)

USE Methodはインフラリソースをモニタリングする方法論です:

  • Utilization: リソース使用率
  • Saturation: キュー長
  • Errors: エラー数
# CPU Utilization
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)

# Memory Utilization
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100

# Disk I/O Saturation
rate(node_disk_io_time_weighted_seconds_total[5m])

# Network Errors
rate(node_network_receive_errs_total[5m])
+ rate(node_network_transmit_errs_total[5m])

6. 分散(ぶんさん)トレーシング

6.1 核心概念

  • Trace: 1つのリクエストがシステムを通過(つうか)する全体(ぜんたい)経路(けいろ)
  • Span: Trace内の個別(こべつ)作業(さぎょう)単位(たんい)
  • Context Propagation: サービス間のトレースコンテキスト伝搬(でんぱん)
  • Trace ID: リクエスト全体を識別(しきべつ)する一意(いちい)のID
  • Span ID: 個別作業を識別する一意のID

6.2 Context Propagation

// W3C Trace Context伝搬
import { propagation, context, trace } from '@opentelemetry/api';

// HTTPクライアントでContext注入
async function makeRequest(url, data) {
  const headers = {};

  // 現在のコンテキストからトレース情報をヘッダーに注入
  propagation.inject(context.active(), headers);

  return fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      ...headers, // traceparent, tracestate を含む
    },
    body: JSON.stringify(data),
  });
}

6.3 Jaeger設定

# docker-compose.yml - Jaeger
services:
  jaeger:
    image: jaegertracing/all-in-one:1.53
    ports:
      - "16686:16686"  # UI
      - "4317:4317"    # OTLP gRPC
      - "4318:4318"    # OTLP HTTP
    environment:
      - COLLECTOR_OTLP_ENABLED=true
      - SPAN_STORAGE_TYPE=elasticsearch
      - ES_SERVER_URLS=http://elasticsearch:9200

6.4 サンプリング戦略

// サンプリング設定
import { ParentBasedSampler, TraceIdRatioBasedSampler } from '@opentelemetry/sdk-trace-base';

// 比率ベースサンプリング(10%のみ収集)
const sampler = new TraceIdRatioBasedSampler(0.1);

// 親ベースサンプリング(親がサンプリングされたら子もサンプリング)
const parentBasedSampler = new ParentBasedSampler({
  root: new TraceIdRatioBasedSampler(0.1),
});

サンプリング戦略比較:

戦略説明メリットデメリット
Head Samplingトレース開始時に決定シンプル、低オーバーヘッドエラートレースの見落とし
Tail Samplingトレース完了後に決定エラー/遅いトレースを保存高メモリ使用量
Rate Limiting秒間収集量を制限予測可能なコストトラフィック急増時の見落とし
Probabilistic確率ベースの収集均等なサンプルレアイベントの見落とし

7. アラート戦略

7.1 アラート疲労(ひろう)の防止(ぼうし)

アラート疲労は、多すぎるアラートにより重要(じゅうよう)なアラートを無視(むし)してしまう現象(げんしょう)です。

原則(げんそく):

  1. アクショナブルなアラートのみ送信
  2. 重大度(じゅうだいど)レベルを区分(くぶん)
  3. 適切(てきせつ)なルーティング(誰(だれ)が、いつ受(う)け取(と)るか)
  4. アラートのグループ化(同(おな)じ問題のアラートをまとめる)
  5. 自動解消(じどうかいしょう)(問題解決時にアラートを自動終了)

7.2 重大度レベル

# Prometheusアラートルール
groups:
  - name: service_alerts
    rules:
      # Critical:即座に対応が必要
      - alert: HighErrorRate
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
          /
          sum(rate(http_requests_total[5m])) by (service)
          > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "High error rate on service"
          description: "Error rate is above 5% for 5 minutes"
          runbook_url: "https://wiki.example.com/runbooks/high-error-rate"

      # Warning:注意が必要
      - alert: HighLatency
        expr: |
          histogram_quantile(0.99,
            sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
          ) > 2
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "High latency on service"

      # Info:情報提供
      - alert: PodRestart
        expr: |
          increase(kube_pod_container_status_restarts_total[1h]) > 3
        labels:
          severity: info
        annotations:
          summary: "Pod restarting frequently"

7.3 Alertmanager設定

# alertmanager.yml
global:
  resolve_timeout: 5m
  slack_api_url: 'https://hooks.slack.com/services/xxx'

route:
  group_by: ['alertname', 'service']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 4h
  receiver: 'default-slack'

  routes:
    # Critical -> PagerDuty + Slack
    - match:
        severity: critical
      receiver: 'pagerduty-critical'
      repeat_interval: 1h
      continue: true

    - match:
        severity: critical
      receiver: 'slack-critical'

    # Warning -> Slack
    - match:
        severity: warning
      receiver: 'slack-warning'
      repeat_interval: 4h

receivers:
  - name: 'default-slack'
    slack_configs:
      - channel: '#alerts-general'

  - name: 'pagerduty-critical'
    pagerduty_configs:
      - service_key: 'YOUR_PAGERDUTY_KEY'
        severity: critical

  - name: 'slack-critical'
    slack_configs:
      - channel: '#alerts-critical'
        color: 'danger'

  - name: 'slack-warning'
    slack_configs:
      - channel: '#alerts-warning'
        color: 'warning'

inhibit_rules:
  # Criticalが発生したら同じサービスのWarningを抑制
  - source_match:
      severity: critical
    target_match:
      severity: warning
    equal: ['service']

8. SLO / SLI / SLA

8.1 用語定義(ようごていぎ)

  • SLI (Service Level Indicator): 測定(そくてい)可能なサービス品質指標
    • 例:成功率(せいこうりつ)、応答時間、可用性(かようせい)
  • SLO (Service Level Objective): SLIに対する目標値(もくひょうち)
    • 例:可用性99.9%、p99応答時間200ms以下
  • SLA (Service Level Agreement): 顧客(こきゃく)との契約(けいやく)
    • SLO違反(いはん)時の補償(ほしょう)条件を含む

8.2 Error Budget(エラー予算(よさん))

SLO: 99.9%可用性
= 1ヶ月(30日)に許容されるダウンタイム:43.2= Error Budget: 0.1%

Error Budget消費率:
- 全体の50%消費:注意
- 全体の75%消費:機能リリース凍結
- 全体の100%消費:安定性作業にのみ集中

8.3 Burn Rateアラート

# Burn Rateベースのアラート
groups:
  - name: slo_alerts
    rules:
      # 高速消費(1時間で2%消費)- 即座に対応
      - alert: SLOBurnRateCritical
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[1h]))
            /
            sum(rate(http_requests_total[1h]))
          ) > (14.4 * 0.001)
        for: 2m
        labels:
          severity: critical
        annotations:
          summary: "SLO burn rate critical - error budget exhausting fast"

      # 低速消費(6時間で5%消費)- 注意
      - alert: SLOBurnRateWarning
        expr: |
          (
            sum(rate(http_requests_total{status=~"5.."}[6h]))
            /
            sum(rate(http_requests_total[6h]))
          ) > (6 * 0.001)
        for: 15m
        labels:
          severity: warning
        annotations:
          summary: "SLO burn rate elevated"

9. APMツール比較

9.1 主要(しゅよう)APMソリューション

機能DatadogNew RelicDynatraceOSSスタック
価格モデルホスト/使用量ベース使用量ベースホストベースインフラコストのみ
自動計装優秀優秀最高良好
APM含む含む含むJaeger/Tempo
ログ管理含む含む含むELK/Loki
メトリクス含む含む含むPrometheus
AI分析WatchdogApplied IntelligenceDavis AIなし
セットアップ複雑度低い低い中程度高い
ベンダーロックイン高い高い高いなし

9.2 オープンソーススタック構成

+-----------------+     +------------------+
| Application     |---->| OTel Collector   |
| (OTel SDK)      |     | (収集/処理)      |
+-----------------+     +--------+---------+
                                 |
                    +------------+------------+
                    |            |            |
              +-----v-----+ +---v---+ +-----v-----+
              | Prometheus | | Tempo | | Loki      |
              | (Metrics)  | |(Trace)| | (Logs)    |
              +-----+------+ +---+---+ +-----+-----+
                    |            |            |
                    +------+-----+------+-----+
                           |
                      +----v----+
                      | Grafana |
                      | (統合可視化)|
                      +---------+

10. コスト最適化(さいてきか)

10.1 データボリューム管理

# ログ保存ポリシー
retention_policy:
  hot_tier: 7d        # 直近7日:高速検索
  warm_tier: 30d       # 直近30日:低速検索
  cold_tier: 90d       # 直近90日:アーカイブ
  delete_after: 365d   # 1年後に削除

10.2 サンプリングによるコスト削減(さくげん)

// 適応型サンプリング
class AdaptiveSampler {
  constructor(targetRate = 100) {
    this.targetRate = targetRate; // 秒間目標収集量
    this.currentRate = 0;
    this.samplingProbability = 1.0;

    // 10秒ごとにサンプリング確率を調整
    setInterval(() => this.adjust(), 10000);
  }

  shouldSample() {
    this.currentRate++;
    return Math.random() < this.samplingProbability;
  }

  adjust() {
    if (this.currentRate > this.targetRate * 10) {
      this.samplingProbability = this.targetRate / this.currentRate;
    } else {
      this.samplingProbability = Math.min(1.0, this.samplingProbability * 1.1);
    }
    this.currentRate = 0;
  }
}

10.3 メトリクス集約(しゅうやく)

# Prometheus Recording Rulesで元データを集約
groups:
  - name: aggregation
    interval: 1m
    rules:
      # サービス別集約(インスタンスラベル削除でカーディナリティ削減)
      - record: service:requests:rate5m
        expr: sum(rate(http_requests_total[5m])) by (service, method, status)

      # 時間解像度を下げる(5分 -> 1時間)
      - record: service:requests:rate1h
        expr: sum(rate(http_requests_total[1h])) by (service)

11. プロダクションチェックリスト

11.1 デプロイ前確認事項(かくにんじこう)

  • すべてのサービスに構造化ロギングを適用
  • Correlation ID伝搬を確認
  • Prometheusメトリクスエンドポイントを公開
  • OTel自動計装を有効化
  • ヘルスチェックエンドポイントを実装
  • SLOを定義しError Budgetを設定

11.2 運用確認事項

  • Grafanaダッシュボード(RED/USE method)
  • アラートルール設定(Critical/Warning/Info)
  • Runbook作成完了
  • オンコールローテーション設定
  • ログ保存ポリシー設定
  • コストモニタリング

11.3 定期(ていき)レビュー事項

  • 月次SLOレビュー
  • アラートノイズ分析
  • 未使用ダッシュボード/アラートの整理
  • コスト最適化レビュー
  • インシデントポストモーテム

12. クイズ

Q1: Observabilityの Three Pillars(三本柱)は何ですか?

回答:Logs、Metrics、Traces

  • Logs: 個別イベントの記録。デバッグと監査に使用。
  • Metrics: 時系列の数値測定。システムパフォーマンスのトレンド把握。
  • Traces: 分散システムでのリクエストの全体経路を追跡。ボトルネックの特定。

この3つを組み合わせることで、「何が壊れたか」だけでなく「なぜ壊れたか」を理解できます。

Q2: Prometheusでp99応答時間を取得するPromQLを記述してください。

回答:

histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))

histogram_quantile関数はHistogramメトリクスからパーセンタイルを計算します。0.99は99パーセンタイルを意味し、rate()で5分間の変化率を求め、le(less than or equal)ラベルでグループ化します。

Q3: Head SamplingとTail Samplingの違いは何ですか?

回答:

Head Samplingはトレースの開始時に収集するかどうかを決定します。シンプルでオーバーヘッドが低いですが、エラーが発生したトレースを見落とす可能性があります。

Tail Samplingはトレースの完了後に収集するかどうかを決定します。エラーや遅いレスポンスのトレースを確実に保存できますが、すべてのSpanをメモリに保持する必要があるため、リソース使用量が高くなります。

プロダクションでは通常、OTel CollectorでTail Samplingを実行し、エラートレースを優先的に収集します。

Q4: SLOが99.9%の場合、1ヶ月(30日)に許容されるダウンタイムは?

回答:約43.2分

計算:30日 x 24時間 x 60分 = 43,200分 Error Budget = 43,200 x 0.001 = 43.2分

つまり、1ヶ月に43.2分までのダウンタイムはSLO範囲内です。Error Budgetが枯渇したら、新機能リリースを停止して安定性改善に集中すべきです。

Q5: RED MethodとUSE Methodの違いを説明してください。

回答:

RED Methodサービスモニタリングに使用されます:

  • Rate: 秒間リクエスト数
  • Errors: エラー率
  • Duration: 応答時間

USE Methodインフラリソースモニタリングに使用されます:

  • Utilization: リソース使用率(CPU、メモリなど)
  • Saturation: キュー長、過負荷の度合い
  • Errors: ハードウェア/システムエラー

REDはユーザー体験の観点からサービスをモニタリングし、USEはシステムの観点からインフラをモニタリングします。両方を組み合わせることで、問題の根本原因を迅速に特定できます。


13. 参考資料(さんこうしりょう)

  1. OpenTelemetry Documentation - https://opentelemetry.io/docs/
  2. Prometheus Documentation - https://prometheus.io/docs/
  3. Grafana Documentation - https://grafana.com/docs/
  4. Jaeger Documentation - https://www.jaegertracing.io/docs/
  5. Grafana Loki - https://grafana.com/oss/loki/
  6. Grafana Tempo - https://grafana.com/oss/tempo/
  7. ELK Stack - https://www.elastic.co/elk-stack
  8. Google SRE Book - Monitoring - https://sre.google/sre-book/monitoring-distributed-systems/
  9. Google SRE Book - Service Level Objectives - https://sre.google/sre-book/service-level-objectives/
  10. pino Logger - https://getpino.io/
  11. Alertmanager - https://prometheus.io/docs/alerting/latest/alertmanager/
  12. PagerDuty Incident Response - https://response.pagerduty.com/
  13. The RED Method - https://grafana.com/blog/2018/08/02/the-red-method-how-to-instrument-your-services/
  14. The USE Method - https://www.brendangregg.com/usemethod.html

Observabilityは単(たん)にツールをインストールすることではなく、**文化(ぶんか)**です。すべてのチームメンバーがログを構造化し、メトリクスを定義(ていぎ)し、トレースを活用(かつよう)する習慣(しゅうかん)を身(み)につける必要があります。SLOを中心にアラート戦略を構築(こうちく)し、Error Budgetでリリースと安定性(あんていせい)のバランスを取りましょう。コスト最適化のためにサンプリングと保存ポリシーを積極的(せっきょくてき)に活用することも忘(わす)れないでください。