はじめに
マイクロサービスアーキテクチャでは、システムの状態を把握することが 非常に重要です。この記事では、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 }}
ベストプラクティス
モニタリング戦略
- 重要なメトリクスの特定
- 適切な粒度の設定
- コンテキスト情報の付加
- データ保持期間の最適化
- 自動化の活用
パフォーマンス考慮事項
- サンプリングの適切な設定
- バッチ処理の活用
- ストレージの最適化
- ネットワーク帯域の考慮
セキュリティ考慮事項
- 機密情報の適切な処理
- アクセス制御の実装
- 暗号化の適用
- 監査ログの保持
トラブルシューティング
一般的な問題と解決策
- データ欠損: バッファサイズとフラッシュ間隔の調整
- 高負荷: サンプリングレートの最適化
- 誤検知: アラートしきい値の調整
- ストレージ不足: 保持期間とアグリゲーションの見直し
デバッグのヒント
- 詳細なログレベルの一時的な有効化
- テストモードでの動作確認
- メトリクスの手動検証
- コンポーネント単位での切り分け