マイクロサービスのモニタリング実践ガイド

OpenTelemetryを中心に、マイクロサービスアーキテクチャにおける効果的なモニタリング戦略を解説します。

技術ブログ

はじめに

マイクロサービスアーキテクチャでは、システムの状態を把握することが 非常に重要です。この記事では、OpenTelemetryを中心に、 効果的なモニタリング戦略を解説します。

モニタリングの重要性

  • システムの健全性確認
  • パフォーマンスの最適化
  • 問題の早期発見
  • インシデント対応の効率化
  • キャパシティプランニング

OpenTelemetryの導入

1. 基本セットアップ

# OpenTelemetryコレクターの設定(docker-compose.yml)
version: '3'
services:
  otel-collector:
    image: otel/opentelemetry-collector:latest
    command: ["--config=/etc/otel-collector-config.yaml"]
    volumes:
      - ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
    ports:
      - "4317:4317"   # OTLP gRPC
      - "4318:4318"   # OTLP HTTP
      - "8888:8888"   # メトリクスエンドポイント
      - "8889:8889"   # プロメテウスエクスポート

2. Node.jsでの実装

import { NodeSDK } from '@opentelemetry/sdk-node'
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'
import { Resource } from '@opentelemetry/resources'
import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'

const sdk = new NodeSDK({
  resource: new Resource({
    [SemanticResourceAttributes.SERVICE_NAME]: 'my-service',
    [SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0',
  }),
  traceExporter: new OTLPTraceExporter({
    url: 'http://localhost:4318/v1/traces',
  }),
  instrumentations: [getNodeAutoInstrumentations()],
})

sdk.start()

分散トレーシング

1. トレースの作成

import { trace } from '@opentelemetry/api'

async function processOrder(orderId: string) {
  const tracer = trace.getTracer('order-processor')
  
  const span = tracer.startSpan('process-order')
  span.setAttribute('orderId', orderId)
  
  try {
    // 支払い処理
    await processPayment(orderId)
    
    // 在庫確認
    await checkInventory(orderId)
    
    // 配送手配
    await arrangeShipping(orderId)
    
    span.setStatus({ code: SpanStatusCode.OK })
  } catch (error) {
    span.setStatus({
      code: SpanStatusCode.ERROR,
      message: error.message
    })
    throw error
  } finally {
    span.end()
  }
}

2. コンテキストの伝播

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

async function makeHttpRequest(url: string) {
  const currentSpan = trace.getSpan(context.active())
  
  // HTTPヘッダーにトレースコンテキストを追加
  const headers = {
    'traceparent': currentSpan.spanContext().traceparent,
    'tracestate': currentSpan.spanContext().tracestate,
  }
  
  const response = await fetch(url, { headers })
  return response
}

メトリクス収集

1. カスタムメトリクスの定義

import { metrics } from '@opentelemetry/api'

// カウンターの作成
const requestCounter = metrics
  .getMeter('my-service')
  .createCounter('http.requests', {
    description: 'Count of HTTP requests',
  })

// ヒストグラムの作成
const responseTimeHistogram = metrics
  .getMeter('my-service')
  .createHistogram('http.response.time', {
    description: 'HTTP response time',
    unit: 'ms',
  })

// メトリクスの記録
app.use((req, res, next) => {
  const startTime = Date.now()
  
  requestCounter.add(1, {
    method: req.method,
    path: req.path,
  })
  
  res.on('finish', () => {
    const duration = Date.now() - startTime
    responseTimeHistogram.record(duration, {
      method: req.method,
      path: req.path,
      status: res.statusCode,
    })
  })
  
  next()
})

2. プロメテウス形式のエクスポート

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
      http:

processors:
  batch:

exporters:
  prometheus:
    endpoint: "0.0.0.0:8889"
    namespace: "my_service"

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [prometheus]

ログ管理

1. 構造化ロギング

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

const logger = pino({
  mixin() {
    const span = trace.getSpan(context.active())
    if (!span) return {}
    
    const { traceId, spanId } = span.spanContext()
    return {
      traceId,
      spanId,
      service: 'my-service',
    }
  },
})

// ログの出力
logger.info({
  event: 'order_processed',
  orderId: 'order-123',
  amount: 1000,
  currency: 'JPY',
})

2. ログの集約

# fluentd.conf
<source>
  @type forward
  port 24224
</source>

<filter **>
  @type parser
  key_name log
  <parse>
    @type json
  </parse>
</filter>

<match **>
  @type elasticsearch
  host elasticsearch
  port 9200
  logstash_format true
  logstash_prefix my-service
  include_tag_key true
</match>

可視化とダッシュボード

1. Grafanaダッシュボードの設定

{
  "annotations": {
    "list": []
  },
  "panels": [
    {
      "title": "HTTP Request Rate",
      "type": "graph",
      "datasource": "Prometheus",
      "targets": [
        {
          "expr": "rate(http_requests_total[5m])",
          "legendFormat": "{{method}} {{path}}"
        }
      ]
    },
    {
      "title": "Response Time Distribution",
      "type": "heatmap",
      "datasource": "Prometheus",
      "targets": [
        {
          "expr": "rate(http_response_time_bucket[5m])",
          "format": "heatmap"
        }
      ]
    }
  ]
}

効果的なダッシュボード設計

  • 重要なメトリクスを上部に配置
  • 関連する指標をグループ化
  • 適切な時間範囲の設定
  • アラートしきい値の可視化
  • カスタムテンプレートの活用

アラート設定

1. Prometheusアラートルール

groups:
- name: service-alerts
  rules:
  - alert: HighErrorRate
    expr: |
      sum(rate(http_requests_total{status=~"5.."}[5m])) 
      / 
      sum(rate(http_requests_total[5m])) > 0.1
    for: 5m
    labels:
      severity: critical
    annotations:
      summary: High error rate detected
      description: Error rate is above 10% for 5 minutes

  - alert: SlowResponses
    expr: |
      histogram_quantile(0.95, 
        rate(http_response_time_bucket[5m])
      ) > 500
    for: 5m
    labels:
      severity: warning
    annotations:
      summary: Slow response times detected
      description: 95th percentile latency is above 500ms

2. アラート通知の設定

# alertmanager.yml
global:
  resolve_timeout: 5m

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

receivers:
- name: 'slack'
  slack_configs:
  - api_url: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXX'
    channel: '#alerts'
    title: '{{ .GroupLabels.alertname }}'
    text: >
      {{ range .Alerts }}
        *Alert:* {{ .Annotations.summary }}
        *Description:* {{ .Annotations.description }}
        *Severity:* {{ .Labels.severity }}
      {{ end }}

ベストプラクティス

モニタリング戦略

  • 重要なメトリクスの特定
  • 適切な粒度の設定
  • コンテキスト情報の付加
  • データ保持期間の最適化
  • 自動化の活用

パフォーマンス考慮事項

  • サンプリングの適切な設定
  • バッチ処理の活用
  • ストレージの最適化
  • ネットワーク帯域の考慮

セキュリティ考慮事項

  • 機密情報の適切な処理
  • アクセス制御の実装
  • 暗号化の適用
  • 監査ログの保持

トラブルシューティング

一般的な問題と解決策

  • データ欠損: バッファサイズとフラッシュ間隔の調整
  • 高負荷: サンプリングレートの最適化
  • 誤検知: アラートしきい値の調整
  • ストレージ不足: 保持期間とアグリゲーションの見直し

デバッグのヒント

  • 詳細なログレベルの一時的な有効化
  • テストモードでの動作確認
  • メトリクスの手動検証
  • コンポーネント単位での切り分け