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

- Name
- Youngju Kim
- @fjvbn20031
TOC
1. モニタリング vs Observability
1.1 モニタリングの限界(げんかい)
従来(じゅうらい)のモニタリングは、**既知(きち)の問題(もんだい)(known unknowns)**を検出(けんしゅつ)することに焦点(しょうてん)を当(あ)てています。CPU使用率(しようりつ)が90%を超(こ)えたらアラート、ディスク使用量が80%を超えたらアラート -- このような閾値(しきいち)ベースのアプローチです。
しかし、現代(げんだい)の分散(ぶんさん)システムではこれだけでは不十分(ふじゅうぶん)です:
- マイクロサービス間(かん)の複雑(ふくざつ)な相互作用(そうごさよう)
- 一時的(いちじてき)なエラーの頻発(ひんぱつ)
- 予測(よそく)できない問題(unknown unknowns)の増加(ぞうか)
- 単一(たんいつ)メトリクスでは説明できないパフォーマンス低下(ていか)
1.2 Observabilityとは
Observabilityは、システムの**外部出力(がいぶしゅつりょく)を通(とお)じて内部状態(ないぶじょうたい)**を理解(りかい)できる能力(のうりょく)です。
核心的(かくしんてき)な違(ちが)い:
- モニタリング: 「何(なに)が壊(こわ)れたか?」
- Observability: 「なぜ壊れたか?」
Observabilityの三本柱(さんぼんばしら)(Three Pillars):
- Logs - 離散(りさん)イベントの記録(きろく)
- Metrics - 時系列(じけいれつ)の数値測定(すうちそくてい)
- 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 アラート疲労(ひろう)の防止(ぼうし)
アラート疲労は、多すぎるアラートにより重要(じゅうよう)なアラートを無視(むし)してしまう現象(げんしょう)です。
原則(げんそく):
- アクショナブルなアラートのみ送信
- 重大度(じゅうだいど)レベルを区分(くぶん)
- 適切(てきせつ)なルーティング(誰(だれ)が、いつ受(う)け取(と)るか)
- アラートのグループ化(同(おな)じ問題のアラートをまとめる)
- 自動解消(じどうかいしょう)(問題解決時にアラートを自動終了)
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ソリューション
| 機能 | Datadog | New Relic | Dynatrace | OSSスタック |
|---|---|---|---|---|
| 価格モデル | ホスト/使用量ベース | 使用量ベース | ホストベース | インフラコストのみ |
| 自動計装 | 優秀 | 優秀 | 最高 | 良好 |
| APM | 含む | 含む | 含む | Jaeger/Tempo |
| ログ管理 | 含む | 含む | 含む | ELK/Loki |
| メトリクス | 含む | 含む | 含む | Prometheus |
| AI分析 | Watchdog | Applied Intelligence | Davis 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. 参考資料(さんこうしりょう)
- OpenTelemetry Documentation - https://opentelemetry.io/docs/
- Prometheus Documentation - https://prometheus.io/docs/
- Grafana Documentation - https://grafana.com/docs/
- Jaeger Documentation - https://www.jaegertracing.io/docs/
- Grafana Loki - https://grafana.com/oss/loki/
- Grafana Tempo - https://grafana.com/oss/tempo/
- ELK Stack - https://www.elastic.co/elk-stack
- Google SRE Book - Monitoring - https://sre.google/sre-book/monitoring-distributed-systems/
- Google SRE Book - Service Level Objectives - https://sre.google/sre-book/service-level-objectives/
- pino Logger - https://getpino.io/
- Alertmanager - https://prometheus.io/docs/alerting/latest/alertmanager/
- PagerDuty Incident Response - https://response.pagerduty.com/
- The RED Method - https://grafana.com/blog/2018/08/02/the-red-method-how-to-instrument-your-services/
- The USE Method - https://www.brendangregg.com/usemethod.html
Observabilityは単(たん)にツールをインストールすることではなく、**文化(ぶんか)**です。すべてのチームメンバーがログを構造化し、メトリクスを定義(ていぎ)し、トレースを活用(かつよう)する習慣(しゅうかん)を身(み)につける必要があります。SLOを中心にアラート戦略を構築(こうちく)し、Error Budgetでリリースと安定性(あんていせい)のバランスを取りましょう。コスト最適化のためにサンプリングと保存ポリシーを積極的(せっきょくてき)に活用することも忘(わす)れないでください。