Skip to content
Published on

オブザーバビリティ(Observability)完全ガイド2025:Prometheus、Grafana、OpenTelemetryでシステムを透明に

Authors

はじめに:オブザーバビリティが重要(じゅうよう)な理由(りゆう)

「モニタリングはシステムが正(ただ)しく動作(どうさ)しているか確認(かくにん)すること。オブザーバビリティはシステムがなぜ正(ただ)しく動作(どうさ)していないかを理解(りかい)すること。」

現代(げんだい)の分散(ぶんさん)システムでは、単(たん)に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

トレース分析(ぶんせき)のヒント

  1. 遅(おそ)いSpanを見(み)つける:トレース全体(ぜんたい)で最(もっと)も長(なが)いspanを特定(とくてい)します
  2. エラーSpanフィルタ:status=ERRORでフィルタリングして障害(しょうがい)箇所(かしょ)を特定(とくてい)します
  3. サービスマップ:サービス間(かん)の依存(いぞん)関係(かんけい)と呼(よ)び出(だ)しパターンを可視化(かしか)します
  4. 比較(ひかく)分析(ぶんせき):正常(せいじょう)なトレースと問題(もんだい)のあるトレースを並(なら)べて比較(ひかく)します

6. ロギング(Logging)

ELK vs Loki vs CloudWatch

区分ELK StackGrafana LokiCloudWatch Logs
インデックス全文インデックスラベルベースロググループ
ストレージコスト高い低い中程度
クエリ言語KQL/LuceneLogQLInsights
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']

良(よ)いアラートの条件(じょうけん)

  1. 実行可能(じっこうかのう)(Actionable):アラートを受(う)けたら実行(じっこう)できることがあるべきです
  2. 緊急度(きんきゅうど)の区別(くべつ):本当(ほんとう)に緊急(きんきゅう)なものだけをページ(呼(よ)び出(だ)し)します
  3. コンテキスト含(ふく)む:Runbookリンク、関連(かんれん)ダッシュボードリンクを含(ふく)めます
  4. アラート疲労(ひろう)防止(ぼうし):多(おお)すぎるアラートはすべてのアラートを無視(むし)させます
  5. 自動復旧(じどうふっきゅう)優先(ゆうせん):可能(かのう)であれば自動復旧(じどうふっきゅう)してから通知(つうち)します

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)

  1. Prometheus公式ドキュメント
  2. Grafana公式ドキュメント
  3. OpenTelemetry公式ドキュメント
  4. Jaeger公式ドキュメント
  5. Grafana Lokiドキュメント
  6. Grafana Tempoドキュメント
  7. Google SRE Book
  8. Google SRE Workbook
  9. PromQLチートシート
  10. Sloth - SLO Generator
  11. OpenTelemetry Collector設定
  12. Alertmanagerルーティングツリー
  13. LogQLドキュメント
  14. Pino Logger (Node.js)
  15. kube-prometheus-stack Helm Chart
  16. DORA Metricsガイド