- Authors

- Name
- Youngju Kim
- @fjvbn20031
はじめに:オブザーバビリティが重要(じゅうよう)な理由(りゆう)
「モニタリングはシステムが正(ただ)しく動作(どうさ)しているか確認(かくにん)すること。オブザーバビリティはシステムがなぜ正(ただ)しく動作(どうさ)していないかを理解(りかい)すること。」
現代(げんだい)の分散(ぶんさん)システムでは、単(たん)にCPU使用率(しようりつ)やメモリをモニタリングするだけでは不十分(ふじゅうぶん)です。マイクロサービスアーキテクチャ、コンテナ、サーバーレス環境(かんきょう)では、一(ひと)つのリクエストが数十(すうじゅう)のサービスを経由(けいゆ)するため、問題(もんだい)の原因(げんいん)を把握(はあく)するにはシステム内部(ないぶ)を見(み)通(とお)せるオブザーバビリティが必要(ひつよう)です。
1. オブザーバビリティの3つの柱(はしら)(Three Pillars)
メトリクス(Metrics)
数値(すうち)で表現(ひょうげん)される時系列(じけいれつ)データです。システム状態(じょうたい)の集約(しゅうやく)ビューを提供(ていきょう)します。
- Counter:単調増加(たんちょうぞうか)する値(あたい)(例(れい):総(そう)リクエスト数(すう))
- Gauge:上下(じょうげ)する値(あたい)(例(れい):現在(げんざい)のメモリ使用量(しようりょう))
- Histogram:値(あたい)の分布(ぶんぷ)(例(れい):レスポンス時間(じかん)の分布(ぶんぷ))
- Summary:クライアント側(がわ)で計算(けいさん)されたパーセンタイル
# Counter例
http_requests_total{method="GET", path="/api/users", status="200"} 15234
# Gauge例
node_memory_usage_bytes{instance="web-01"} 1073741824
# Histogram例
http_request_duration_seconds_bucket{le="0.1"} 24054
http_request_duration_seconds_bucket{le="0.5"} 33444
http_request_duration_seconds_bucket{le="1.0"} 34055
ログ(Logs)
イベントのテキスト記録(きろく)です。個別(こべつ)イベントの詳細(しょうさい)情報(じょうほう)を提供(ていきょう)します。
{
"timestamp": "2025-03-15T10:30:45.123Z",
"level": "ERROR",
"service": "payment-service",
"traceId": "abc123def456",
"spanId": "span789",
"message": "Payment processing failed",
"userId": "user-42",
"orderId": "order-1234",
"error": "Timeout connecting to payment gateway",
"duration_ms": 5000
}
**構造化(こうぞうか)ロギング(Structured Logging)**を使用(しよう)すると、検索(けんさく)と分析(ぶんせき)がはるかに容易(ようい)になります。
トレース(Traces)
リクエストが複数(ふくすう)のサービスを経由(けいゆ)する全体(ぜんたい)の経路(けいろ)を追跡(ついせき)します。
[Trace: abc123def456]
|-- [Span: API Gateway] 2ms
| |-- [Span: Auth Service] 5ms
| | +-- [Span: Redis Cache Lookup] 1ms
| |-- [Span: User Service] 15ms
| | +-- [Span: PostgreSQL Query] 8ms
| +-- [Span: Payment Service] 5003ms <-- ボトルネック!
| +-- [Span: External Payment API] 5000ms (TIMEOUT)
+-- Total: 5025ms
3つの柱(はしら)が組(く)み合(あ)わさると、「何(なに)が(What)問題(もんだい)で、なぜ(Why)問題(もんだい)で、どこで(Where)問題(もんだい)なのか」をすべて把握(はあく)できます。
2. Prometheus
アーキテクチャ
PrometheusはPullベースのモニタリングシステムです。
+-------------+ +--------------+ +-----------+
| Targets |---->| Prometheus |---->| Grafana |
| (exporters)|pull | Server |query| |
+-------------+ | - TSDB | +-----------+
| - Rules |
| - AlertMgr |
+--------------+
|
+-----v-----+
| AlertMgr |
| - Routing |
| - Silence |
+-----------+
Prometheus設定(せってい)
# prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "alert_rules.yml"
- "recording_rules.yml"
alerting:
alertmanagers:
- static_configs:
- targets: ['alertmanager:9093']
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
- job_name: 'app-service'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
target_label: __address__
regex: (.+)
PromQL核心(かくしん)クエリ
# 1. 現在の秒間リクエスト数 (rate)
rate(http_requests_total[5m])
# 2. サービス別エラー率
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service)
# 3. 95パーセンタイル応答時間
histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service)
)
# 4. メモリ使用率 (%)
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes)
/ node_memory_MemTotal_bytes * 100
# 5. CPU使用率
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# 6. ディスク空き容量が10%未満のノード
node_filesystem_avail_bytes / node_filesystem_size_bytes * 100 < 10
# 7. Pod再起動回数(過去1時間)
increase(kube_pod_container_status_restarts_total[1h]) > 3
# 8. サービス可用性(過去30日間)
1 - (
sum(increase(http_requests_total{status=~"5.."}[30d]))
/
sum(increase(http_requests_total[30d]))
)
Recording Rules(パフォーマンス最適化(さいてきか))
# recording_rules.yml
groups:
- name: service_metrics
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)
- record: service:http_error_rate:ratio
expr: service:http_errors:rate5m / service:http_requests:rate5m
- record: service:http_latency:p95
expr: histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, service))
Alert Rules
# alert_rules.yml
groups:
- name: service_alerts
rules:
- alert: HighErrorRate
expr: service:http_error_rate:ratio > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "High error rate on service {{ $labels.service }}"
description: "Error rate is {{ $value | humanizePercentage }} for 5+ minutes"
- alert: HighLatency
expr: service:http_latency:p95 > 2.0
for: 5m
labels:
severity: warning
annotations:
summary: "High p95 latency on {{ $labels.service }}"
description: "P95 latency is {{ $value }}s (threshold: 2s)"
- alert: PodCrashLooping
expr: increase(kube_pod_container_status_restarts_total[1h]) > 5
for: 10m
labels:
severity: critical
annotations:
summary: "Pod {{ $labels.pod }} is crash looping"
3. Grafana
ダッシュボード設計(せっけい)原則(げんそく)
USEメソッド:Utilization(使用率(しようりつ))、Saturation(飽和度(ほうわど))、Errors(エラー) REDメソッド:Rate(レート)、Errors(エラー)、Duration(所要時間(しょようじかん))
Grafanaダッシュボード JSON構造(こうぞう)
{
"dashboard": {
"title": "Service Overview",
"panels": [
{
"title": "Request Rate",
"type": "timeseries",
"datasource": "Prometheus",
"targets": [
{
"expr": "sum(rate(http_requests_total[5m])) by (service)",
"legendFormat": "{{ service }}"
}
]
},
{
"title": "Error Rate",
"type": "stat",
"datasource": "Prometheus",
"targets": [
{
"expr": "sum(rate(http_requests_total{status=~\"5..\"}[5m])) / sum(rate(http_requests_total[5m])) * 100"
}
],
"fieldConfig": {
"defaults": {
"thresholds": {
"steps": [
{ "value": 0, "color": "green" },
{ "value": 1, "color": "yellow" },
{ "value": 5, "color": "red" }
]
},
"unit": "percent"
}
}
}
],
"templating": {
"list": [
{
"name": "service",
"type": "query",
"query": "label_values(http_requests_total, service)",
"refresh": 2
},
{
"name": "environment",
"type": "custom",
"options": ["production", "staging", "development"]
}
]
}
}
}
Grafana Alerting
# Grafana Alert Rule (provisioning)
apiVersion: 1
groups:
- orgId: 1
name: service_alerts
folder: Production
interval: 1m
rules:
- uid: high-error-rate
title: High Error Rate
condition: C
data:
- refId: A
datasourceUid: prometheus
model:
expr: sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
- refId: B
datasourceUid: prometheus
model:
expr: sum(rate(http_requests_total[5m])) by (service)
- refId: C
datasourceUid: __expr__
model:
type: math
expression: "$A / $B > 0.05"
for: 5m
labels:
severity: critical
annotations:
summary: "Error rate exceeds 5%"
4. OpenTelemetry
OpenTelemetry概要(がいよう)
OpenTelemetry(OTel)はメトリクス、ログ、トレースを収集(しゅうしゅう)するベンダー中立(ちゅうりつ)な標準(ひょうじゅん)です。
+--------------+ +----------------+ +-------------+
| Application |---->| OTel |---->| Backend |
| + OTel SDK | | Collector | | - Jaeger |
| | | - Receivers | | - Tempo |
| | | - Processors | | - Prometheus|
| | | - Exporters | | - Loki |
+--------------+ +----------------+ +-------------+
SDK計装(けいそう)(Node.js)
// tracing.ts - アプリケーション起動時に最初にimport
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';
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]: 'payment-service',
[ATTR_SERVICE_VERSION]: '1.2.0',
environment: 'production',
}),
traceExporter: new OTLPTraceExporter({
url: 'http://otel-collector:4317',
}),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: 'http://otel-collector:4317',
}),
exportIntervalMillis: 30000,
}),
instrumentations: [
getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-http': { enabled: true },
'@opentelemetry/instrumentation-express': { enabled: true },
'@opentelemetry/instrumentation-pg': { enabled: true },
'@opentelemetry/instrumentation-redis': { enabled: true },
}),
],
});
sdk.start();
手動(しゅどう)計装(けいそう)(Custom Spans)
import { trace, SpanStatusCode, context } from '@opentelemetry/api';
const tracer = trace.getTracer('payment-service');
async function processPayment(orderId: string, amount: number) {
return tracer.startActiveSpan('processPayment', async (span) => {
try {
span.setAttribute('order.id', orderId);
span.setAttribute('payment.amount', amount);
span.setAttribute('payment.currency', 'USD');
// 子Spanを生成
const validationResult = await tracer.startActiveSpan(
'validatePayment',
async (validationSpan) => {
const result = await validatePaymentDetails(orderId);
validationSpan.setAttribute('validation.result', result.valid);
validationSpan.end();
return result;
}
);
if (!validationResult.valid) {
span.setStatus({
code: SpanStatusCode.ERROR,
message: 'Payment validation failed',
});
throw new Error('Invalid payment');
}
const result = await chargePayment(orderId, amount);
span.setAttribute('payment.transactionId', result.transactionId);
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (error) {
span.recordException(error);
span.setStatus({
code: SpanStatusCode.ERROR,
message: error.message,
});
throw error;
} finally {
span.end();
}
});
}
OTel Collector設定(せってい)
# otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
prometheus:
config:
scrape_configs:
- job_name: 'otel-collector'
scrape_interval: 10s
static_configs:
- targets: ['0.0.0.0:8888']
processors:
batch:
timeout: 5s
send_batch_size: 1000
memory_limiter:
check_interval: 1s
limit_mib: 512
attributes:
actions:
- key: environment
value: production
action: upsert
exporters:
otlp/jaeger:
endpoint: jaeger:4317
tls:
insecure: true
prometheusremotewrite:
endpoint: http://prometheus:9090/api/v1/write
loki:
endpoint: http://loki:3100/loki/api/v1/push
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, batch, attributes]
exporters: [otlp/jaeger]
metrics:
receivers: [otlp, prometheus]
processors: [memory_limiter, batch]
exporters: [prometheusremotewrite]
logs:
receivers: [otlp]
processors: [memory_limiter, batch]
exporters: [loki]
5. 分散(ぶんさん)トレーシング(Distributed Tracing)
JaegerとGrafana Tempo
Jaeger:スタンドアロンの分散(ぶんさん)トレーシングシステム。UIが内蔵(ないぞう)されており、すぐに始(はじ)められます。
Grafana Tempo:Grafanaエコシステムに統合(とうごう)されたトレーシングバックエンド。インデックスレスのため、ストレージコストが低(ひく)くなっています。
Docker Composeでトレーシングスタック構成(こうせい)
# docker-compose.yaml
version: '3.8'
services:
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686" # Jaeger UI
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
environment:
- COLLECTOR_OTLP_ENABLED=true
otel-collector:
image: otel/opentelemetry-collector-contrib:latest
command: ["--config=/etc/otel-collector-config.yaml"]
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
ports:
- "4317:4317"
- "4318:4318"
depends_on:
- jaeger
トレース分析(ぶんせき)のヒント
- 遅(おそ)いSpanを見(み)つける:トレース全体(ぜんたい)で最(もっと)も長(なが)いspanを特定(とくてい)します
- エラーSpanフィルタ:status=ERRORでフィルタリングして障害(しょうがい)箇所(かしょ)を特定(とくてい)します
- サービスマップ:サービス間(かん)の依存(いぞん)関係(かんけい)と呼(よ)び出(だ)しパターンを可視化(かしか)します
- 比較(ひかく)分析(ぶんせき):正常(せいじょう)なトレースと問題(もんだい)のあるトレースを並(なら)べて比較(ひかく)します
6. ロギング(Logging)
ELK vs Loki vs CloudWatch
| 区分 | ELK Stack | Grafana Loki | CloudWatch Logs |
|---|---|---|---|
| インデックス | 全文インデックス | ラベルベース | ロググループ |
| ストレージコスト | 高い | 低い | 中程度 |
| クエリ言語 | KQL/Lucene | LogQL | Insights |
| Grafana統合 | プラグイン | ネイティブ | プラグイン |
| 適合規模 | 大規模 | 中小規模 | AWSネイティブ |
Grafana Loki + LogQL
# サービス別エラーログ
{service="payment-service"} |= "ERROR"
# JSONパース後フィルタ
{service="api-gateway"} | json | status >= 500
# エラー発生頻度(1分あたり)
count_over_time({service="payment-service"} |= "ERROR" [1m])
# 遅いリクエストフィルタ(1秒以上)
{service="api-gateway"} | json | duration > 1000
# 特定トレースIDで全ログ検索
{service=~".+"} |= "trace_id=abc123def456"
構造化(こうぞうか)ロギング実装(じっそう)(Node.js)
import pino from 'pino';
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
formatters: {
level(label) {
return { level: label };
},
},
timestamp: pino.stdTimeFunctions.isoTime,
base: {
service: 'payment-service',
version: '1.2.0',
environment: process.env.NODE_ENV,
},
});
// リクエストごとのコンテキスト付与
function createRequestLogger(req) {
return logger.child({
requestId: req.id,
traceId: req.headers['x-trace-id'],
userId: req.user?.id,
method: req.method,
path: req.url,
});
}
// 使用例
app.use((req, res, next) => {
req.log = createRequestLogger(req);
req.log.info('Request received');
res.on('finish', () => {
req.log.info({
statusCode: res.statusCode,
duration: Date.now() - req.startTime,
}, 'Request completed');
});
next();
});
7. SRE核心(かくしん)概念(がいねん)
SLI(Service Level Indicator)
サービス品質(ひんしつ)を測定(そくてい)する具体的(ぐたいてき)な指標(しひょう)です。
# 可用性SLI:成功リクエスト比率
sum(rate(http_requests_total{status!~"5.."}[30d]))
/
sum(rate(http_requests_total[30d]))
# レイテンシSLI:P99 < 300msのリクエスト比率
sum(rate(http_request_duration_seconds_bucket{le="0.3"}[30d]))
/
sum(rate(http_request_duration_seconds_count[30d]))
SLO(Service Level Objective)
SLIの目標値(もくひょうち)です。
- 可用性(かようせい)SLO:99.9%(月間(げっかん)ダウンタイム43分(ぷん))
- レイテンシSLO:P99応答時間(おうとうじかん)300ms未満(みまん)
# SLO定義 (Sloth形式)
version: "prometheus/v1"
service: "payment-service"
labels:
team: "platform"
slos:
- name: "availability"
objective: 99.9
sli:
events:
error_query: sum(rate(http_requests_total{status=~"5..",service="payment"}[{{.window}}]))
total_query: sum(rate(http_requests_total{service="payment"}[{{.window}}]))
alerting:
page_alert:
labels:
severity: critical
ticket_alert:
labels:
severity: warning
Error Budget(エラーバジェット)
SLO 99.9%なら、エラーバジェットは0.1%です。
- 30日(にち)基準(きじゅん):43.2分(ぷん)のダウンタイム許容(きょよう)
- エラーバジェットが残(のこ)っていれば:新機能(しんきのう)デプロイ、実験(じっけん)が可能(かのう)
- エラーバジェットを使(つか)い切(き)ったら:安定化(あんていか)に集中(しゅうちゅう)、デプロイ凍結(とうけつ)
# 残りエラーバジェット (%)
1 - (
(1 - service:availability:ratio30d)
/
(1 - 0.999)
)
SLA(Service Level Agreement)
顧客(こきゃく)との契約(けいやく)です。SLOより緩(ゆる)く設定(せってい)します。
SLA > SLO > SLI (測定)
例:
- SLA: 99.9% (契約、違反時に返金)
- SLO: 99.95% (内部目標、SLAより厳格)
- SLI: 99.97% (実測値)
8. アラート戦略(せんりゃく)(Alerting Strategy)
アラートピラミッド
/ P1: Page \ -> 即時対応 (PagerDuty)
/ (重大、顧客影響) \
/------------------\
/ P2: Ticket \ -> 業務時間内処理 (Jira)
/ (性能低下、潜在リスク) \
/----------------------\
/ P3: Notification \ -> 認知のみ (Slack)
/ (警告、トレンド変化) \
/--------------------------\
/ P4: Dashboard only \ -> ダッシュボード確認
/ (参考指標、自動復旧可能) \
AlertManagerルーティング
# alertmanager.yml
global:
resolve_timeout: 5m
route:
receiver: 'default-slack'
group_by: ['alertname', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- match:
severity: critical
receiver: 'pagerduty-critical'
group_wait: 10s
repeat_interval: 1h
- match:
severity: warning
receiver: 'slack-warnings'
repeat_interval: 4h
- match:
severity: info
receiver: 'slack-info'
repeat_interval: 12h
receivers:
- name: 'pagerduty-critical'
pagerduty_configs:
- service_key: 'your-pagerduty-key'
severity: critical
- name: 'slack-warnings'
slack_configs:
- api_url: 'https://hooks.slack.com/services/xxx'
channel: '#alerts-warning'
title: '[WARNING] {{ .GroupLabels.alertname }}'
- name: 'slack-info'
slack_configs:
- api_url: 'https://hooks.slack.com/services/xxx'
channel: '#alerts-info'
- name: 'default-slack'
slack_configs:
- api_url: 'https://hooks.slack.com/services/xxx'
channel: '#alerts'
inhibit_rules:
- source_match:
severity: critical
target_match:
severity: warning
equal: ['alertname', 'service']
良(よ)いアラートの条件(じょうけん)
- 実行可能(じっこうかのう)(Actionable):アラートを受(う)けたら実行(じっこう)できることがあるべきです
- 緊急度(きんきゅうど)の区別(くべつ):本当(ほんとう)に緊急(きんきゅう)なものだけをページ(呼(よ)び出(だ)し)します
- コンテキスト含(ふく)む:Runbookリンク、関連(かんれん)ダッシュボードリンクを含(ふく)めます
- アラート疲労(ひろう)防止(ぼうし):多(おお)すぎるアラートはすべてのアラートを無視(むし)させます
- 自動復旧(じどうふっきゅう)優先(ゆうせん):可能(かのう)であれば自動復旧(じどうふっきゅう)してから通知(つうち)します
9. オンコール(On-Call)文化(ぶんか)
オンコールローテーション設計(せっけい)
週次ローテーション例:
- Primary: 第一対応者(5分以内に応答)
- Secondary: バックアップ対応者(Primary未応答時、10分後にエスカレーション)
- マネージャー: 30分以上未解決時にエスカレーション
交代周期: 1週間
ハンドオフ: 毎週月曜日午前10時
報酬: オンコール手当、代替休暇
インシデント対応(たいおう)プロセス
1. 検知(Detect)
+-- アラート受信、影響範囲の初期把握
2. 対応(Respond)
+-- インシデントチャンネル作成、役割割り当て
- IC (Incident Commander): 調整
- Tech Lead: 技術調査
- Comms: 顧客/ステークホルダーへの連絡
3. 緩和(Mitigate)
+-- 即時対応(ロールバック、スケールアウト等)
4. 解決(Resolve)
+-- 根本原因の修正、サービス復旧確認
5. 事後分析(Postmortem)
+-- 非難なき振り返り、再発防止アクションアイテムの導出
10. プロダクションモニタリングスタックアーキテクチャ
推奨(すいしょう)スタック構成(こうせい)
+---------------------------------------------+
| Grafana |
| (ダッシュボード、アラート、探索) |
+-------+-------------+------ --------+------+
| | |
+----v----+ +-----v-----+ +------v---+
|Prometheus| | Loki | | Tempo |
|(Metrics) | | (Logs) | |(Traces) |
+----^----+ +-----^-----+ +-----^----+
| | |
+----+-------------+--------------+----+
| OpenTelemetry Collector |
| (収集、処理、ルーティング) |
+----^-------------^--------------^----+
| | |
+----+----+ +-----+-----+ +----+----+
|Service A| |Service B | |Service C|
|+OTel SDK| |+OTel SDK | |+OTel SDK|
+---------+ +-----------+ +---------+
Kubernetes環境(かんきょう)モニタリング
# kube-prometheus-stack values.yaml (Helm)
prometheus:
prometheusSpec:
retention: 15d
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: gp3
resources:
requests:
storage: 100Gi
grafana:
dashboardProviders:
dashboardproviders.yaml:
apiVersion: 1
providers:
- name: 'default'
folder: ''
type: file
options:
path: /var/lib/grafana/dashboards
alertmanager:
config:
route:
receiver: 'slack'
group_by: ['alertname', 'namespace']
receivers:
- name: 'slack'
slack_configs:
- api_url: 'https://hooks.slack.com/services/xxx'
channel: '#k8s-alerts'
11. 実務(じつむ)面接(めんせつ)質問(しつもん)15選(せん)
基礎(きそ)(1-5)
Q1. オブザーバビリティの3つの柱(はしら)を説明(せつめい)してください。
Metrics(メトリクス)、Logs(ログ)、Traces(トレース)です。メトリクスは数値(すうち)の時系列(じけいれつ)データでシステム状態(じょうたい)の集約(しゅうやく)ビューを、ログはイベントの詳細(しょうさい)なテキスト記録(きろく)を、トレースはリクエストが複数(ふくすう)のサービスを経由(けいゆ)する経路(けいろ)を示(しめ)します。
Q2. PrometheusのPullモデルを説明(せつめい)してください。
Prometheusがターゲットサービスの/metricsエンドポイントを定期的(ていきてき)にスクレイピングします。Pushモデルと異(こと)なり、サーバーが収集(しゅうしゅう)対象(たいしょう)を制御(せいぎょ)し、サービスディスカバリと組(く)み合(あ)わせて動的(どうてき)環境(かんきょう)をサポートします。
Q3. Counter、Gauge、Histogramの違(ちが)いを説明(せつめい)してください。
Counterは単調増加(たんちょうぞうか)する値(あたい)(総(そう)リクエスト数(すう))、Gaugeは上下(じょうげ)する現在値(げんざいち)(メモリ使用量(しようりょう))、Histogramは値(あたい)の分布(ぶんぷ)をバケットで観測(かんそく)する型(かた)(レスポンス時間(じかん)の分布(ぶんぷ))です。
Q4. 構造化(こうぞうか)ロギングが重要(じゅうよう)な理由(りゆう)は?
JSONなどの一貫(いっかん)した形式(けいしき)でログを記録(きろく)すると、自動(じどう)パース、フィルタリング、検索(けんさく)が可能(かのう)になります。traceIdを含(ふく)めると分散(ぶんさん)システムでログとトレースを紐(ひも)付(づ)け、デバッグが大幅(おおはば)に速(はや)くなります。
Q5. SLI、SLO、SLAの違(ちが)いを説明(せつめい)してください。
SLI(Service Level Indicator)は実際(じっさい)の測定(そくてい)指標(しひょう)、SLO(Service Level Objective)は内部(ないぶ)目標値(もくひょうち)、SLA(Service Level Agreement)は顧客(こきゃく)との法的(ほうてき)契約(けいやく)です。SLAはSLOより緩(ゆる)く設定(せってい)します。
中級(ちゅうきゅう)(6-10)
Q6. PromQLのrate()とincrease()の違(ちが)いは?
rate()は秒間(びょうかん)平均(へいきん)増加率(ぞうかりつ)を返(かえ)し、increase()は指定(してい)時間(じかん)範囲(はんい)での総(そう)増加量(ぞうかりょう)を返(かえ)します。rate()はグラフに、increase()はトータルカウントに適(てき)しています。
Q7. OpenTelemetry Collectorの役割(やくわり)を説明(せつめい)してください。
テレメトリデータ(メトリクス、ログ、トレース)をReceiverで受信(じゅしん)し、Processorで処理(しょり)(バッチ、フィルタリング)した後(あと)、Exporterで複数(ふくすう)のバックエンドに送信(そうしん)します。アプリケーションとバックエンド間(かん)の中間層(ちゅうかんそう)として、ベンダーロックインを防止(ぼうし)します。
Q8. Error Budgetの概念(がいねん)と活用法(かつようほう)を説明(せつめい)してください。
SLOで許容(きょよう)されるエラー比率(ひりつ)です。99.9% SLOならエラーバジェットは0.1%(月(つき)43分(ぷん))。バジェットが残(のこ)っていれば新機能(しんきのう)をデプロイし、使(つか)い切(き)ったら安定化(あんていか)に集中(しゅうちゅう)します。開発速度(かいはつそくど)と信頼性(しんらいせい)のバランスを数値(すうち)で管理(かんり)します。
Q9. Distributed TracingにおけるSpanとTraceの関係(かんけい)は?
Traceは一(ひと)つのリクエストがシステムを通過(つうか)する全体(ぜんたい)の旅(たび)で、Spanはその旅(たび)の中(なか)の個別(こべつ)の作業(さぎょう)単位(たんい)です。Spanは親子(おやこ)関係(かんけい)でツリーを形成(けいせい)し、各(かく)Spanには開始(かいし)/終了(しゅうりょう)時刻(じこく)、属性(ぞくせい)、ステータスがあります。
Q10. Grafana LokiとELKの主(おも)な違(ちが)いは?
ELKはログテキストを全文(ぜんぶん)インデックスし強力(きょうりょく)な検索(けんさく)を提供(ていきょう)しますが、ストレージコストが高(たか)いです。Lokiはラベルのみインデックスし、ログテキストは圧縮(あっしゅく)保存(ほぞん)するためコストが低(ひく)いですが、ラベルベースのフィルタリング後(ご)にテキスト検索(けんさく)が必要(ひつよう)です。
上級(じょうきゅう)(11-15)
Q11. アラート疲労(ひろう)(Alert Fatigue)をどう防止(ぼうし)しますか?
実行可能(じっこうかのう)なアラートのみ設定(せってい)し、重大度(じゅうだいど)を明確(めいかく)に区分(くぶん)します。inhibit rulesで重複(じゅうふく)アラートを抑制(よくせい)し、groupingで類似(るいじ)アラートをまとめます。定期的(ていきてき)にアラートをレビューしてノイズを除去(じょきょ)します。
Q12. PrometheusのRecording Rulesはなぜ必要(ひつよう)ですか?
複雑(ふくざつ)なPromQLクエリを事前(じぜん)計算(けいさん)して新(あたら)しい時系列(じけいれつ)として保存(ほぞん)します。ダッシュボードの読(よ)み込(こ)み時間(じかん)を短縮(たんしゅく)し、同(おな)じクエリの繰(く)り返(かえ)し実行(じっこう)を防止(ぼうし)します。特(とく)にSLOダッシュボードのような長期間(ちょうきかん)クエリに効果的(こうかてき)です。
Q13. OpenTelemetryにおけるContext Propagationとは?
トレースコンテキスト(trace ID、span ID)をサービス間(かん)で伝播(でんぱ)するメカニズムです。HTTPヘッダー(W3C Trace Context)やメッセージキューのメタデータを通(つう)じて伝播(でんぱ)し、分散(ぶんさん)システムで一(ひと)つのリクエストをエンドツーエンドで追跡(ついせき)できるようにします。
Q14. Golden SignalsとRED/USEメソッドを比較(ひかく)してください。
GoogleのGolden SignalsはLatency、Traffic、Errors、Saturationです。RED(Rate、Errors、Duration)はサービス観点(かんてん)、USE(Utilization、Saturation、Errors)はインフラ観点(かんてん)に適(てき)しています。サービスにはRED、インフラにはUSEを適用(てきよう)するのが一般的(いっぱんてき)です。
Q15. 非難(ひなん)なき(Blameless)Postmortemの核心(かくしん)原則(げんそく)は?
個人(こじん)を非難(ひなん)せず、システムの障害(しょうがい)に焦点(しょうてん)を当(あ)てます。タイムラインを再構成(さいこうせい)し、寄与(きよ)要因(よういん)を分析(ぶんせき)し、具体的(ぐたいてき)で測定可能(そくていかのう)なアクションアイテムを導出(どうしゅつ)します。目標(もくひょう)は同(おな)じ問題(もんだい)が再発(さいはつ)しないようにシステムを改善(かいぜん)することです。
12. 実践(じっせん)クイズ5問(もん)
Q1. Prometheusのメトリクスタイプのうち、「現在のメモリ使用量」のように上下する値を表現するのに最も適したタイプは?
正解(せいかい):Gauge
Gaugeは上下(じょうげ)する瞬間(しゅんかん)値(ち)を表(あらわ)します。Counterは単調増加(たんちょうぞうか)のみなので、メモリ使用量(しようりょう)のように減少(げんしょう)し得(う)る値(あたい)には適(てき)していません。Histogramは分布(ぶんぷ)の測定(そくてい)に使用(しよう)します。
Q2. SLOが99.9%の場合、30日間のエラーバジェット(許容ダウンタイム)は約何分ですか?
正解(せいかい):約(やく)43分(ぷん)
30日(にち) = 43,200分(ぷん)。エラーバジェット = 43,200 x 0.001 = 43.2分(ぷん)。この時間内(じかんない)の障害(しょうがい)はSLOに違反(いはん)しません。
Q3. OpenTelemetry Collectorの3つの主要構成要素は?
正解(せいかい):Receivers、Processors、Exporters
Receiversはデータを受信(じゅしん)し(OTLP、Prometheus等(など))、Processorsはデータを処理(しょり)し(バッチ、フィルタリング、属性(ぞくせい)追加(ついか)等(など))、Exportersはデータをバックエンドに送信(そうしん)します(Jaeger、Prometheus、Loki等(など))。
Q4. 次のPromQLクエリは何を計算しますか? histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
正解(せいかい):過去(かこ)5分間(ふんかん)のHTTPリクエスト応答時間(おうとうじかん)の95パーセンタイル(P95)
histogram_quantileはヒストグラムバケットからパーセンタイルを計算(けいさん)します。0.95は95%を意味(いみ)し、le(less than or equal)ラベルでグループ化(か)されたバケットデータからP95を抽出(ちゅうしゅつ)します。
Q5. Distributed Tracingにおいて「Context Propagation」がないとどのような問題が発生しますか?
正解(せいかい):サービス間(かん)のリクエストを一(ひと)つのトレースとして接続(せつぞく)できなくなります。
Context Propagationがないと、各(かく)サービスが独立(どくりつ)したトレースを生成(せいせい)します。一(ひと)つのユーザーリクエストが複数(ふくすう)のサービスを経由(けいゆ)する際(さい)、全体(ぜんたい)の経路(けいろ)を把握(はあく)できず、分散(ぶんさん)システムでのデバッグが極(きわ)めて困難(こんなん)になります。
参考(さんこう)資料(しりょう)(References)
- Prometheus公式ドキュメント
- Grafana公式ドキュメント
- OpenTelemetry公式ドキュメント
- Jaeger公式ドキュメント
- Grafana Lokiドキュメント
- Grafana Tempoドキュメント
- Google SRE Book
- Google SRE Workbook
- PromQLチートシート
- Sloth - SLO Generator
- OpenTelemetry Collector設定
- Alertmanagerルーティングツリー
- LogQLドキュメント
- Pino Logger (Node.js)
- kube-prometheus-stack Helm Chart
- DORA Metricsガイド