ストーリー
可観測性の3本柱
マイクロサービスの可観測性はメトリクス、ログ、トレースの3つで構成されます。
graph TD
subgraph OBS["可観測性(Observability)"]
M["メトリクス(Metrics)<br/>「今何が起きているか」<br/>Prometheus + Grafana"]
L["ログ(Logs)<br/>「何が起きたのか」<br/>Fluentd / Loki"]
T["トレース(Traces)<br/>「リクエストはどう流れたか」<br/>Jaeger / Zipkin"]
end
classDef metrics fill:#d4edda,stroke:#28a745,color:#333
classDef logs fill:#e8f4fd,stroke:#2196f3,color:#333
classDef traces fill:#fff3cd,stroke:#f0ad4e,color:#333
class M metrics
class L logs
class T traces
| 柱 | 目的 | データの性質 | 代表ツール |
|---|---|---|---|
| メトリクス | システムの状態を数値で把握 | 集約データ(時系列) | Prometheus, Grafana |
| ログ | イベントの詳細を記録 | 個別イベント(構造化/非構造化) | Fluentd, Loki, ELK |
| トレース | リクエストの経路を追跡 | 因果関係のあるスパン群 | Jaeger, Zipkin, Tempo |
分散トレーシング
なぜ分散トレーシングが必要か
モノリスでは1つのスタックトレースで問題箇所を特定できますが、マイクロサービスではリクエストが複数のサービスを横断するため、分散トレーシングが必要です。
graph LR
REQ["ユーザーリクエスト<br/>POST /api/orders"] --> GW["API Gateway<br/>50ms"]
GW --> ORD["Order Svc"]
ORD -->|200ms| PAY["Payment Svc<br/>(遅い!)"]
ORD -->|30ms| INV["Inventory Svc"]
INV -->|20ms| NOT["Notification Svc"]
classDef slow fill:#f8d7da,stroke:#dc3545,color:#333
classDef normal fill:#d4edda,stroke:#28a745,color:#333
classDef req fill:#e8f4fd,stroke:#2196f3,color:#333
class PAY slow
class GW,ORD,INV,NOT normal
class REQ req
トレースの構造
graph TD
Trace["Trace: abc123\n1つのリクエスト全体"]
GW["API Gateway\nspan-001 / 310ms"]
Order["Order Service\nspan-002 / 260ms"]
Pay["Payment Service\nspan-003 / 200ms\nボトルネック"]
Inv["Inventory Service\nspan-004 / 50ms"]
Notif["Notification Service\nspan-005 / 20ms"]
Trace --> GW
GW --> Order
Order --> Pay
Order --> Inv
Inv --> Notif
style Trace fill:#1e293b,stroke:#475569,color:#f8fafc
style GW fill:#dbeafe,stroke:#2563eb,color:#1e40af
style Order fill:#dbeafe,stroke:#2563eb,color:#1e40af
style Pay fill:#fee2e2,stroke:#dc2626,stroke-width:3px,color:#991b1b
style Inv fill:#d1fae5,stroke:#059669,color:#065f46
style Notif fill:#d1fae5,stroke:#059669,color:#065f46
Jaeger/Zipkinの比較
| 特性 | Jaeger | Zipkin |
|---|---|---|
| 開発元 | Uber(現CNCF) | |
| 言語 | Go | Java |
| ストレージ | Cassandra, Elasticsearch, Kafka | Cassandra, Elasticsearch, MySQL |
| UI | 高機能(依存関係グラフ) | シンプル |
| サンプリング | アダプティブサンプリング対応 | 固定レート |
| Istio統合 | ネイティブ対応 | ネイティブ対応 |
トレーシングヘッダーの伝播
Istioのサイドカーは自動でトレーシングヘッダーを注入しますが、アプリケーション内でヘッダーを伝播する責任はアプリケーション側にあります。
// アプリケーションコードでのヘッダー伝播
// Istioが認識するヘッダーを受信リクエストから送信リクエストに転送する
const TRACE_HEADERS = [
'x-request-id',
'x-b3-traceid',
'x-b3-spanid',
'x-b3-parentspanid',
'x-b3-sampled',
'x-b3-flags',
'b3',
'traceparent', // W3C Trace Context
'tracestate',
];
function propagateTraceHeaders(
incomingHeaders: Record<string, string>,
outgoingRequest: Record<string, string>
): void {
for (const header of TRACE_HEADERS) {
if (incomingHeaders[header]) {
outgoingRequest[header] = incomingHeaders[header];
}
}
}
// Express.js ミドルウェアの例
app.use((req, res, next) => {
// コンテキストにトレーシングヘッダーを保存
req.traceContext = {};
for (const header of TRACE_HEADERS) {
if (req.headers[header]) {
req.traceContext[header] = req.headers[header] as string;
}
}
next();
});
// 他サービスへのリクエスト時にヘッダーを伝播
async function callPaymentService(req: Request, amount: number) {
return axios.post('http://payment-service/api/charge', { amount }, {
headers: req.traceContext, // トレーシングヘッダーを転送
});
}
メトリクス収集(Prometheus)
Istioが自動収集するメトリクス
Istioのサイドカーは、追加コードなしで以下のメトリクスを収集します。
| メトリクス | 説明 | タイプ |
|---|---|---|
istio_requests_total | リクエスト総数 | Counter |
istio_request_duration_milliseconds | レスポンスタイム | Histogram |
istio_request_bytes | リクエストサイズ | Histogram |
istio_response_bytes | レスポンスサイズ | Histogram |
istio_tcp_connections_opened_total | TCP接続開始数 | Counter |
istio_tcp_connections_closed_total | TCP接続終了数 | Counter |
PromQLクエリ例
# サービスごとのリクエスト成功率(過去5分間)
sum(rate(istio_requests_total{
response_code!~"5.*",
destination_service_name="order-service"
}[5m])) /
sum(rate(istio_requests_total{
destination_service_name="order-service"
}[5m]))
# P99レイテンシ(99パーセンタイル)
histogram_quantile(0.99,
sum(rate(istio_request_duration_milliseconds_bucket{
destination_service_name="order-service"
}[5m])) by (le)
)
# エラー率が5%を超えているサービスを検出
sum(rate(istio_requests_total{response_code=~"5.*"}[5m])) by (destination_service_name)
/
sum(rate(istio_requests_total{}[5m])) by (destination_service_name)
> 0.05
GoldenSignals(4つの黄金シグナル)
Googleが提唱するサービス監視の4つの黄金シグナルです。
graph TD
GS["4つの黄金シグナル<br/>(Golden Signals)"]
GS --> LAT["レイテンシ(Latency)<br/>リクエスト処理にかかる時間<br/>→ P50, P95, P99 で監視"]
GS --> TRF["トラフィック(Traffic)<br/>システムへのリクエスト量<br/>→ RPS(秒間リクエスト数)で監視"]
GS --> ERR["エラー率(Errors)<br/>失敗したリクエストの割合<br/>→ 5xx率、タイムアウト率で監視"]
GS --> SAT["飽和度(Saturation)<br/>リソースの使用度合い<br/>→ CPU、メモリ、接続数で監視"]
classDef title fill:#e8f4fd,stroke:#2196f3,color:#333
classDef signal fill:#fff3cd,stroke:#f0ad4e,color:#333
class GS title
class LAT,TRF,ERR,SAT signal
ログ集約
構造化ログの重要性
マイクロサービスでは、構造化ログ(JSON形式)を採用し、集約・検索を容易にすることが不可欠です。
// 悪い例: 非構造化ログ
console.log(`Order created: ${orderId} for user ${userId} total=${total}`);
// 良い例: 構造化ログ(JSON)
const logger = createLogger({ format: 'json' });
logger.info({
event: 'order.created',
orderId: 'ORD-12345',
userId: 'USR-67890',
total: 15000,
currency: 'JPY',
traceId: req.traceContext['x-b3-traceid'],
spanId: req.traceContext['x-b3-spanid'],
service: 'order-service',
timestamp: new Date().toISOString(),
});
// 出力(JSON):
// {
// "event": "order.created",
// "orderId": "ORD-12345",
// "userId": "USR-67890",
// "total": 15000,
// "currency": "JPY",
// "traceId": "abc123def456",
// "spanId": "span-002",
// "service": "order-service",
// "timestamp": "2025-01-15T10:30:00.000Z"
// }
ログ集約アーキテクチャ
graph TD
SA["Service A<br/>(stdout)"] --> FL["Fluentd / Fluent Bit(収集)<br/>DaemonSet(各ノードに配置)"]
SB["Service B<br/>(stdout)"] --> FL
SC["Service C<br/>(stdout)"] --> FL
FL --> ES["Elasticsearch / Grafana Loki<br/>← 保存・インデックス"]
ES --> KIB["Kibana / Grafana<br/>← 検索・可視化"]
classDef svc fill:#d4edda,stroke:#28a745,color:#333
classDef collect fill:#fff3cd,stroke:#f0ad4e,color:#333
classDef store fill:#e8f4fd,stroke:#2196f3,color:#333
classDef viz fill:#f3e8fd,stroke:#9c27b0,color:#333
class SA,SB,SC svc
class FL collect
class ES store
class KIB viz
サービス依存関係の可視化(Kiali)
Kialiは、Istioのメッシュをリアルタイムで可視化するダッシュボードツールです。
Kialiが提供する情報
| 機能 | 説明 |
|---|---|
| サービスグラフ | サービス間の通信経路と状態をリアルタイム表示 |
| トラフィックフロー | リクエスト量、成功率、レイテンシの表示 |
| ヘルスチェック | 各サービスの健全性状態 |
| 設定検証 | Istioリソースの設定エラー検出 |
| ワークロード詳細 | Pod、レプリカの状態表示 |
graph TD
GW["API Gateway"] -->|"200 RPS, 99.8%"| Order["Order Service"]
GW -->|"100 RPS, 99.9%"| User["User Service"]
Order -->|"150 RPS, 99.5%"| Inv["Inventory"]
Order -->|"50 RPS, 98.0%"| Pay["Payment"]
Pay -->|"50 RPS, 97.0%"| Notif["Notification"]
style GW fill:#d1fae5,stroke:#059669,stroke-width:2px,color:#065f46
style Order fill:#d1fae5,stroke:#059669,color:#065f46
style User fill:#d1fae5,stroke:#059669,color:#065f46
style Inv fill:#d1fae5,stroke:#059669,color:#065f46
style Pay fill:#fef3c7,stroke:#d97706,stroke-width:2px,color:#92400e
style Notif fill:#fee2e2,stroke:#dc2626,stroke-width:2px,color:#991b1b
凡例: 緑=正常(>99%) / 黄=警告(95-99%) / 赤=異常(<95%)
レイテンシ分析
レイテンシバジェット
各サービスにレイテンシの「予算」を割り当て、SLOを管理します。
// レイテンシバジェットの設計
interface LatencyBudget {
service: string;
p50Target: number; // ミリ秒
p95Target: number;
p99Target: number;
}
const budgets: LatencyBudget[] = [
{ service: 'api-gateway', p50Target: 5, p95Target: 10, p99Target: 20 },
{ service: 'order-service', p50Target: 20, p95Target: 50, p99Target: 100 },
{ service: 'payment-service', p50Target: 100, p95Target: 200, p99Target: 500 },
{ service: 'inventory-service', p50Target: 10, p95Target: 30, p99Target: 50 },
{ service: 'notification-service', p50Target: 50, p95Target: 100, p99Target: 200 },
];
// 全体のP99目標: 870ms以下(各サービスの直列分の合算)
// ※並列呼び出しの場合は最遅のサービスに依存
ヘルスチェックパターン
// サービスのヘルスチェックエンドポイント
interface HealthCheckResponse {
status: 'healthy' | 'degraded' | 'unhealthy';
version: string;
uptime: number;
checks: {
name: string;
status: 'pass' | 'warn' | 'fail';
responseTime?: number;
message?: string;
}[];
}
// /health エンドポイントの実装例
app.get('/health', async (req, res) => {
const checks = await Promise.all([
checkDatabase(),
checkRedis(),
checkExternalAPI(),
]);
const status = checks.every(c => c.status === 'pass')
? 'healthy'
: checks.some(c => c.status === 'fail')
? 'unhealthy'
: 'degraded';
res.status(status === 'unhealthy' ? 503 : 200).json({
status,
version: process.env.APP_VERSION || 'unknown',
uptime: process.uptime(),
checks,
});
});
ダッシュボード設計の原則
REDメソッド
マイクロサービスのダッシュボードにはREDメソッド(Rate, Errors, Duration)を適用します。
| 指標 | 説明 | PromQLクエリ例 |
|---|---|---|
| Rate | 秒間リクエスト数 | sum(rate(istio_requests_total[5m])) |
| Errors | エラーリクエスト率 | sum(rate(istio_requests_total{response_code=~"5.*"}[5m])) |
| Duration | レスポンスタイム分布 | histogram_quantile(0.99, ...) |
ダッシュボードの階層構造
graph TD
L1["Level 1: 全体概要"] --> L2["Level 2: サービス別"]
L2 --> L3["Level 3: 詳細調査"]
L1 -.- L1a["全サービスの健全性サマリー\n全体のエラー率・レイテンシ\nアラート一覧"]
L2 -.- L2a["個別サービスのRED指標\nサービス間通信の状態\nリソース使用率(CPU, Memory)"]
L3 -.- L3a["個別リクエストのトレース\nログ詳細\nPod/コンテナレベルのメトリクス"]
style L1 fill:#dbeafe,stroke:#2563eb,stroke-width:2px,color:#1e40af
style L2 fill:#fef3c7,stroke:#d97706,color:#92400e
style L3 fill:#fee2e2,stroke:#dc2626,color:#991b1b
style L1a fill:#f3f4f6,stroke:#9ca3af,color:#374151
style L2a fill:#f3f4f6,stroke:#9ca3af,color:#374151
style L3a fill:#f3f4f6,stroke:#9ca3af,color:#374151
アラート設計
# Prometheusアラートルール例
groups:
- name: microservice-alerts
rules:
# エラー率が5%を超えたら警告
- alert: HighErrorRate
expr: |
sum(rate(istio_requests_total{response_code=~"5.*"}[5m])) by (destination_service_name)
/
sum(rate(istio_requests_total{}[5m])) by (destination_service_name)
> 0.05
for: 5m
labels:
severity: warning
annotations:
summary: "{{ $labels.destination_service_name }} のエラー率が5%超過"
# P99レイテンシが1秒を超えたら重大
- alert: HighLatency
expr: |
histogram_quantile(0.99,
sum(rate(istio_request_duration_milliseconds_bucket[5m]))
by (le, destination_service_name)
) > 1000
for: 5m
labels:
severity: critical
annotations:
summary: "{{ $labels.destination_service_name }} のP99レイテンシが1秒超過"
まとめ
| ポイント | 内容 |
|---|---|
| 可観測性の3本柱 | メトリクス、ログ、トレースを組み合わせて運用する |
| 分散トレーシング | TraceID/SpanIDでリクエストの全経路を追跡する |
| ヘッダー伝播 | アプリケーションがトレーシングヘッダーを転送する責任を持つ |
| Golden Signals | レイテンシ、トラフィック、エラー率、飽和度の4指標で監視 |
| ダッシュボード設計 | REDメソッドを基本に、3階層で構成する |
| アラート設計 | SLOに基づいた閾値でアラートを設定する |
チェックリスト
- 可観測性の3本柱とそれぞれの目的を説明できる
- 分散トレーシングのTrace/Spanの構造を理解した
- トレーシングヘッダーの伝播が必要な理由を理解した
- Golden Signalsの4つの指標を列挙できる
- 構造化ログの利点を説明できる
- REDメソッドに基づくダッシュボード設計を理解した
次のステップへ
可観測性の全体像を把握したところで、次は「サービスディスカバリとロードバランシング」を学びます。サービスがお互いをどうやって見つけ、トラフィックをどう分散するのか、その仕組みを深掘りしましょう。
推定読了時間: 25分