サイト信頼性

目次

概要

まず、この章の中心構造を図で確認します。細部に入る前に、どの概念がどこへつながるかをつかむための地図です。

flowchart LR A["ユーザー体験"] --> B["SLI"] B --> C["SLO"] C --> D["エラーバジェット"] D --> E{"余裕あり?"} E -->|はい| F["機能開発を進める"] E -->|いいえ| G["信頼性改善を優先"] G --> H["トイル削減"] G --> I["インシデント対策"] H --> B I --> B
コード例の読み方

コード例は、そのまま写すためだけのものではありません。直前の本文で「何を確かめる例か」を押さえ、直後の説明で「どの性質が見えるか」を確認してください。実務では、ここに入力の境界、失敗時の挙動、依存する実行環境を足して読むと判断しやすくなります。

要点

サイト信頼性は、サービスの信頼性をSLO・エラーバジェット・トイル削減・インシデント対応で継続的に扱うための考え方です。

SREは、運用を人手の頑張りではなく、測定・自動化・設計の問題として扱います。可用性を数値で定義し、許容できる失敗の範囲をエラーバジェットとして管理し、障害から学んで仕組みを改善していく点が中心です。

この章で重視すること

  • SLOSLISLAエラーバジェットの関係を区別する
  • トイルを見つけ、運用作業をコードと仕組みに置き換える
  • インシデント対応とポストモーテムを、学習のサイクルとして捉える
  • Kubernetes、データベース、ネットワーク、セキュリティ、コストを信頼性の観点から読む

1. SREとは何か

Site Reliability Engineering(SRE) とは、ソフトウェアエンジニアリングの考え方を IT インフラおよびオペレーションの問題に適用する規律・職種である。

一言で言うと

「運用をソフトウェア問題として扱ったらどうなるか」 を実践する仕事。

従来の運用(Ops)チームは、トラブルシューティング・手動デプロイ・監視など人力作業を担ってきた。SRE は同じ責務を持ちながら、自動化・計測・エンジニアリング によってシステムの信頼性を向上させることに重点を置く。

SREが担う主な責務

領域 内容
可用性の確保 SLO(サービスレベル目標)の定義・監視・達成
インシデント対応 オンコール、障害対応、ポストモーテム
トイル削減 手動作業の自動化によるエンジニアリング時間の確保
変更管理 安全なデプロイ戦略・ロールバック設計
キャパシティ計画 リソース予測・負荷テスト・スケーリング設計
パフォーマンス レイテンシ最適化・ボトルネック分析
セキュリティ システム全体のセキュリティ態勢の維持

SREが扱うシステムの特性

  • 大規模分散システム — 単一サーバーではなく、マイクロサービス・コンテナ・クラウドネイティブ環境
  • 高い変更頻度 — 継続的デプロイが行われる本番環境
  • 厳しい可用性要件 — 99.9%〜99.999% の稼働率を求められるサービス

2. SREの歴史と起源

Googleでの誕生(2003年)

SRE は 2003 年、Google の Ben Treynor Sloss によって生み出された。彼はもともとソフトウェアエンジニアとして採用され、7人からなる「プロダクションチーム」の運用を任された。

彼の発想は単純だった:「ソフトウェアエンジニアが運用チームを設計したらどうなるか?」

当時の Google が直面していた課題:

  • 急速な成長(ユーザー・トラフィックの爆発的増加)
  • 従来の手動運用では人員がスケールしない
  • 大規模分散インフラの管理が複雑化
  • 高い信頼性を保ちながら新機能をリリースする必要性

業界への普及

出来事
2003 Google が SRE チームを創設(Ben Treynor Sloss)
2010 Facebook が SRE チームを設立
2016 Netflix が Core SRE チームを設立
2016 Google が “Site Reliability Engineering” 書籍を O’Reilly から出版(無料公開)
2018 Google が “The Site Reliability Workbook” を出版
2020年代 SRE は大規模テック企業から中小企業まで業界標準の役割に

なぜ生まれたのか

従来の運用モデルでは 「人を増やすことでスケールする」 という考え方が中心だった。しかしこれには根本的な問題がある:

  • 従来モデル: ユーザー数 ∝ 運用チームの人数(線形スケール)
  • SREモデル: ユーザー数 → 自動化 → チームは劣線形スケール

SRE の本質は、人力作業をコードに置き換えることで、システムが成長しても人員の増加を抑制する ことにある。


3. SREの哲学と原則

核心哲学:「Hope is not a strategy(希望は戦略ではない)」

信頼性は偶然の産物ではなく、意図的に設計・測定・改善されるもの でなければならない。

5つの核心原則

原則1:信頼性を数値で定義する

「高い可用性」という曖昧な言葉を排除し、SLO(Service Level Objective) として数値化する。

  • 例: 「99.9%の可用性」= 月間43.8分のダウンタイムまで許容

原則2:エラーバジェットで開発と運用のバランスを取る

100%の信頼性は不可能であり、かつ不要である。100%を目指すと開発速度が犠牲になる。 エラーバジェットとは「許容できる不信頼性の残高」であり、開発チームと運用チームの共通通貨となる。

原則3:50%ルール(トイルの上限)

SRE チームのエンジニアが運用作業(チケット対応・オンコール・手作業)に費やす時間は 50% 以下 に制限する。残りの50%はエンジニアリング(自動化・改善)に使う。

  • もし運用作業 > 50% の状態が続くなら、
  • それは開発チームに問題を戻す(または人員追加する)シグナル

原則4:自動化ファースト

繰り返し発生する手作業はすべて自動化の候補である。自動化には優先順位があり、単に時間削減だけでなく一貫性・再現性・スケーラビリティをもたらす。

原則5:ブレームレス(責任追及しない)文化

障害は個人の失敗ではなく システムの失敗 として扱う。ポストモーテムでは「誰が悪いか」ではなく「システムをどう改善するか」に焦点を当てる。

SREの7つの基本活動

1. SLO の定義と監視
2. エラーバジェット管理
3. オンコールと障害対応
4. ポストモーテム(障害分析)
5. トイル削減のための自動化
6. キャパシティプランニング
7. 変更管理とリリースエンジニアリング

4. SREとDevOpsの違い

SRE と DevOps はよく混同されるが、それぞれ異なるアプローチを持つ。

哲学レベルの違い

観点 DevOps SRE
性質 文化・運動・哲学 具体的な実践・職種
起源 開発と運用の文化的分断を解消するため 大規模分散システムの信頼性を工学的に解決するため
主眼 開発速度とコラボレーション 信頼性とエラーバジェット管理
関係 DevOps は「何を目指すか」を定義 SRE は「どのように実現するか」を定義

Google の公式見解

「SREとはDevOpsの原則を実装する一つの方法である」 クラス(DevOps)とインスタンス(SRE)の関係に例えられる。

責務の分担(典型的な組織)

[DevOps エンジニア]
  ├── CI/CDパイプライン構築・維持
  ├── 開発→本番へのデプロイ自動化
  ├── Infrastructure as Code(Terraform等)
  └── 開発チームとの連携

[SRE エンジニア]
  ├── SLO/SLI の定義・監視
  ├── エラーバジェット管理
  ├── オンコール・インシデント対応
  ├── ポストモーテムの主導
  └── 運用トイルの削減

5. SREに必要な知識・スキル

スキル全体像

SRE スキルマップ
├── 技術スキル
│   ├── プログラミング・スクリプティング
│   ├── Linux/Unix システム管理
│   ├── ネットワーク
│   ├── クラウドプラットフォーム
│   ├── コンテナ・オーケストレーション
│   ├── モニタリング・オブザーバビリティ
│   ├── データベース
│   └── セキュリティ
└── ソフトスキル
    ├── 問題解決・デバッグ能力
    ├── コミュニケーション
    ├── オンコール判断力(プレッシャー下での意思決定)
    └── ドキュメンテーション

技術スキル詳細

1. プログラミング・スクリプティング

SRE の最大の特徴は「エンジニアリングで運用問題を解く」点にある。

言語 用途
Python 自動化スクリプト・データ解析・ツール開発
Go 高性能ツール・インフラツール(Prometheus, Terraform 等が Go 製)
Bash/Shell システム管理・CI/CD スクリプト
SQL データベース操作・障害調査

最低限のレベル感

  • 1,000行以上のプロジェクトを自力で書ける
  • 既存コードを読んで修正できる
  • REST API を叩く自動化スクリプトを書ける

2. Linux/Unix システム管理

SRE の仕事の大部分は Linux サーバー上で行われる。

必須知識:

  • プロセス管理(ps, top, htop, kill, systemctl)
  • ファイルシステム・パーミッション・inode
  • ネットワーク診断(netstat, ss, tcpdump, strace)
  • パフォーマンス分析(perf, vmstat, iostat, sar)
  • ログ管理(journalctl, logrotate)
  • cron・シェルスクリプト

3. ネットワーク

必須知識:
├── TCP/IP プロトコルスタック
├── DNS の仕組み(正引き・逆引き・TTL・キャッシュ)
├── HTTP/HTTPS・TLS/SSL
├── ロードバランシング(L4/L7)
├── CDN の仕組み
├── BGP・ルーティングの基礎
├── ファイアウォール・セキュリティグループ
└── gRPC・WebSocket等の近代プロトコル

4. クラウドプラットフォーム

主要3社いずれかの深い知識と、残り2社の基礎知識が求められる。

プロバイダ SRE が使う主要サービス
AWS EC2, EKS, RDS, CloudWatch, ALB, Lambda, S3
GCP GKE, Cloud Run, BigQuery, Monitoring, Cloud SQL
Azure AKS, Azure Monitor, App Service, Azure SQL

5. コンテナ・オーケストレーション

現代のSREにとってKubernetesは必須に近い。

Docker:

  • Dockerfile の作成・最適化
  • コンテナのデバッグ(docker exec, docker logs)
  • イメージレイヤーの理解

Kubernetes:

  • Pod / Deployment / Service / Ingress の設計
  • kubectl による運用操作
  • RBAC・NetworkPolicy
  • HPA(水平ポッドオートスケーリング)
  • Helm によるパッケージ管理
  • トラブルシューティング(kubectl describe, kubectl logs, events)

6. モニタリング・オブザーバビリティ(詳細は第8章)

ツール 役割
Prometheus メトリクス収集・時系列DB
Grafana ダッシュボード・可視化
Loki ログ集約
Tempo / Jaeger 分散トレーシング
OpenTelemetry 計装標準
PagerDuty / OpsGenie アラート・オンコール管理

7. データベース

DB 知識レベル
PostgreSQL / MySQL クエリ最適化・インデックス設計・レプリケーション・バックアップ
Redis / Memcached キャッシュ設計・eviction ポリシー
NoSQL (MongoDB, Cassandra) データモデリング・一貫性トレードオフ
時系列DB (InfluxDB, TimescaleDB) メトリクスストア

8. セキュリティ

  • シークレット管理(HashiCorp Vault, AWS Secrets Manager)
  • 最小権限の原則・IAM 設計
  • TLS 証明書管理
  • セキュリティスキャン(Trivy, Snyk)
  • 監査ログの設計

ソフトスキル

SRE はしばしば障害対応という高ストレス状況下で判断を求められる。

プレッシャー下での意思決定

  • 不完全な情報から仮説を立てる能力
  • 「止める」か「続ける」かの判断基準を持つ
  • エスカレーションのタイミングを判断する

コミュニケーション

  • ステータスページ・インシデント通知の文章(非技術者向け)
  • ポストモーテムレポートの執筆
  • SLO の説明(経営陣・プロダクトマネージャーへ)

6. SLI / SLO / SLA とエラーバジェット

これら4つの概念は SRE の中核をなす「信頼性の測定・管理フレームワーク」である。

定義と関係

SLI(何を測るか)
  ↓ 目標値を設定
SLO(どれだけ達成するか)
  ↓ 法的契約化
SLA(違反したらどうなるか)

SLO から導出
  ↓
エラーバジェット(どれだけ失敗を許容できるか)

SLI(Service Level Indicator)

SLI = 実際のサービス品質を計測する定量的指標

形式

SLI = 良いイベント数 ÷ 全イベント数

代表的なSLI:

SLI 種別 計測方法
可用性 成功レスポンス率 成功リクエスト / 全リクエスト
レイテンシ p99 応答時間 < 200ms パーセンタイル計算
スループット 毎秒処理リクエスト数 RPS メトリクス
エラー率 5xx エラー率 < 0.1% エラーカウント / 全カウント
鮮度 データ更新の遅延 < 1時間 タイムスタンプ差分

計測ソース別トレードオフ:

ソース データ品質 カバレッジ コスト
アプリケーションログ Good 部分的
ロードバランサーメトリクス Better 広い
ブラックボックス監視 Good 外部視点
クライアントサイド計装 Best ユーザー視点

SLO(Service Level Objective)

SLO = SLI に対する内部目標値。チームが達成すべき信頼性の目標。

重要原則

  • SLO は必ず 100% 未満 に設定する
  • SLA より常に厳しい値にする(バッファを持つ)
  • 顧客体験に直結する指標にする

SLO の例:

  • ✓ 過去28日間で99.9%のリクエストが200ms以内に応答する
  • ✓ 月次可用性99.95%(月間ダウンタイム22分以内)
  • ✓ 全APIコールの99%が500ms未満

なぜ100%にしないのか:

  1. 分散システムで真の100%は数学的に不可能
  2. 追加の「9」は指数的にコストが上がる
  3. 100% SLO はイノベーションを止める(変更を一切できなくなる)
  4. ユーザーは上流依存のため、完璧なサービスも体験上は100%にならない

SLA(Service Level Agreement)

SLA = サービスプロバイダと顧客間の法的契約。

  • SLO 違反の 経済的ペナルティ(クレジット返金等)を規定

  • SLO より緩い値を設定するのが一般的

  • 例:SLO が 99.95% なら SLA は 99.5% に設定

  • SLA < SLO < 100%

  • (緩い) (厳しい)

エラーバジェット

エラーバジェット = 許容できる不信頼性の「残高」

エラーバジェット = 100% − SLO

例: SLO が 99.9% → エラーバジェット = 0.1%

実際の計算例(Google SRE Book より):

モバイルゲームAPI の例:
- 4週間のリクエスト数: 3,663,253
- SLO: 97%(可用性)
- 許容失敗リクエスト数: 3,663,253 × 0.03 = 109,897

ある障害で14,066件の失敗が発生
→ 4週間バジェットの 12.8% を消費

可用性別の年間ダウンタイム換算:

SLO 年間ダウンタイム 月間ダウンタイム
99% 3日15時間36分 7時間18分
99.9% 8時間45分36秒 43分49秒
99.95% 4時間22分48秒 21分54秒
99.99% 52分35秒 4分22秒
99.999% 5分15秒 26秒

エラーバジェットポリシー

エラーバジェットが枯渇・逼迫した場合の対応方針を事前に決めておく:

バジェット残 > 50%:  通常運用。新機能リリース自由。
バジェット残 10-50%: 注意。リリース頻度を検討。
バジェット残 < 10%:  リリースフリーズ。信頼性改善最優先。
バジェット = 0:      緊急停止。開発は信頼性バグのみ対応。

SLO実装のステップ

  1. ユーザーの幸福と相関するSLIを選ぶ(ユーザーが感じる品質に直結)
  2. ベースライン計測(現状を測り、達成可能な初期SLOを設定)
  3. ステークホルダーの合意(プロダクト・開発・SRE 全員が納得)
  4. ダッシュボード化(常に見える状態に)
  5. 反復改善(最初のSLOが正しい必要はない)

7. トイル(Toil)とその削減

トイルとは

トイル(Toil) = 手動・繰り返し・自動化できる・長期的価値を生まない運用作業

Google SRE Book による定義の6条件:

条件 説明
Manual(手動) 人が手動で実行しなければならない
Repetitive(繰り返し) 毎回同じ、または非常に似た作業
Automatable(自動化可能) 機械で実行できるはず
Tactical(戦術的) 反応的・割り込み的で、戦略的でない
No Enduring Value(長期価値なし) サービスの本質的改善にならない
O(n) with Service Growth サービス成長と比例して増える

トイルの具体例

トイル(悪い例):
├── 毎日手動でログファイルを確認してエラーを報告する
├── デプロイのたびに手動でデータベースマイグレーションを実行する
├── ユーザーからの「パスワードリセット」リクエストを手動処理する
├── 毎週サーバーの空きディスク容量をチェックする
└── アラートを受けるたびに手動でサービス再起動する

非トイル(良い作業):
├── 上記の自動化スクリプトを書く
├── SLO の分析と改善計画の作成
├── アーキテクチャの信頼性設計
└── インシデントのポストモーテムと予防策設計

50%ルールの詳細

SREチームの 運用作業(トイル)は最大50% が原則:

SRE エンジニアの時間配分(理想):
├── 50%: エンジニアリング作業
│   ├── 自動化ツール開発
│   ├── 信頼性改善プロジェクト
│   └── アーキテクチャ改善
└── 50%: 運用作業(最大)
    ├── オンコール対応
    ├── チケット処理
    └── 手動デプロイ等

50% を超えたら:

  • 開発チームに作業を戻す
  • チームメンバーを追加する
  • 自動化プロジェクトを最優先にする

SREがサービスを開発チームに返却する条件

SRE が全サービスを永遠に引き受けるわけではない。トイルが増えすぎたり、開発チームが SLO を無視する場合、SRE はサービスの管理を返却する権利を持つ。これはペナルティではなく、責任の明確化だ。

返却をトリガーする条件(Google の実践より):

□ トイルが SRE 作業時間の 50% を継続して超えている
□ 開発チームがポストモーテムのアクションアイテムを実施しない
□ SLO 目標に同意しない・SLO を無視する
□ SRE のレビューなしに重大な変更を本番に投入した
□ PRR(Production Readiness Review)を省略してリリースした

返却のプロセス:
1. 警告: 問題を文書化して開発チームに正式に通知(2週間前)
2. 猶予期間: 改善計画の合意と実施を待つ(最大4週間)
3. 返却: 権限・アクセス・オンコール責任を開発チームに移管
4. 再受け入れ条件: SLO 設定・テスト・自動化の基準を満たしたら再度引き受ける

※ この制度は「SRE の傲慢さ」ではなく、開発チームが信頼性に責任を持つ
  ための健全な摩擦(productive friction)だ。

トイル削減の自動化階層

自動化の成熟度モデル(低→高):

Lv0: 手作業(ランブックに従って手動実行)
Lv1: スクリプト化(手動トリガーだが処理は自動)
Lv2: 自動トリガー(アラートが自動でスクリプトを実行)
Lv3: 自律的対応(システムが問題を検知・診断・修復)
Lv4: 予測的対応(問題が起きる前に予防的に対処)

8. オブザーバビリティとモニタリング

モニタリング vs オブザーバビリティ

概念 説明
モニタリング 既知の問題を検知するための計測(What is wrong?)
オブザーバビリティ 未知の問題も内部状態から推論できる性質(Why is it wrong?)

オブザーバビリティはモニタリングの上位概念。「何かがおかしい」だけでなく「なぜおかしいか」を内部から理解できる。

可観測性の3本柱(Three Pillars)

Observability
├── Metrics(メトリクス)— 数値で表す状態の変化
├── Logs(ログ)— イベントの記録
└── Traces(トレース)— リクエストの経路追跡

Metrics(メトリクス)

時系列の数値データ。

Prometheusのメトリクス型:

説明
Counter 単調増加のカウンター HTTPリクエスト総数
Gauge 上下する値 CPU使用率、メモリ使用量
Histogram 分布(バケット) リクエストレイテンシのパーセンタイル
Summary パーセンタイル p50, p95, p99 レイテンシ

PromQL(Prometheusクエリ言語)の例:

# 5分間のHTTPエラー率
rate(http_requests_total{status=~"5.."}[5m])
/ rate(http_requests_total[5m])

# p99レイテンシ
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

Logs(ログ)

イベントの記録。構造化ログ(JSON形式)が現代の標準。

良いログの原則:

{
  "timestamp": "2026-04-18T10:30:00Z",
  "level": "ERROR",
  "service": "payment-api",
  "trace_id": "abc123",
  "user_id": "u-456",
  "message": "Payment processing failed",
  "error": "connection timeout",
  "duration_ms": 5000
}

ログスタック:

  • 収集: Fluentd, Filebeat, Vector
  • 保存: Elasticsearch, Loki, CloudWatch Logs
  • 検索: Kibana, Grafana, CloudWatch Insights

Traces(分散トレース)

マイクロサービス環境でリクエストが複数サービスを経由する際の追跡。

ユーザーリクエスト
  └── API Gateway (5ms)
      └── Auth Service (10ms)
          └── User Service (15ms)
              └── Database (8ms)

主要ツール

  • Jaeger / Zipkin: オープンソーストレーシング
  • Tempo: Grafana スタックのトレーシング
  • AWS X-Ray: AWS マネージド

OpenTelemetry(OTel)

ベンダー中立な計装(instrumentation)の標準仕様。

アプリ → OpenTelemetry SDK → OTel Collector → バックエンド
                                               ├── Prometheus (メトリクス)
                                               ├── Loki (ログ)
                                               └── Jaeger/Tempo (トレース)

採用することでベンダーロックインを回避し、バックエンドを後から変更できる。

Grafanaスタック(OSS可観測性の実質標準)

コンポーネント 役割
Prometheus メトリクス収集・保存
Grafana ダッシュボード・アラート
Loki ログ集約(Prometheusのログ版)
Tempo 分散トレーシング
Mimir 長期Prometheusスケール

効果的なアラート設計

悪いアラートの特徴:

  • 頻繁に発火するが実害なし(アラート疲労)
  • 症状でなく原因(内部指標)を監視
  • 対応方法が不明確

良いアラートの設計原則(Google SRE Book):

  1. ユーザーへの影響がある時だけ発火する
  2. アクショナブル(対応方法が明確)
  3. 新しい情報をもたらす(既知なら自動対応を)
  4. 緊急度が適切(P1/P2/P3 を明確に分ける)

SLOベースのアラート(Burn Rate Alert):

SLO: 99.9% → エラーバジェット: 0.1%
30日のバジェット = 43.2分のダウンタイム

バーンレートアラート設定:
- 1時間でバジェットの2%消費 → P2(注意)
- 1時間でバジェットの5%消費 → P1(緊急)

RED / USE メソッド

RED メソッド(サービス指向):

  • Rate - リクエスト/秒
  • Errors - エラー率
  • Duration - リクエストの遅延

USE メソッド(リソース指向):

  • Utilization - リソース使用率
  • Saturation - キューの長さ、待ち状態
  • Errors - エラーカウント

9. インシデント管理とポストモーテム

インシデント管理のフレームワーク

インシデントライフサイクル
1. 検知(Detection)
2. トリアージ(Triage)
3. 対応(Response)
4. 解決(Resolution)
5. 振り返り(Postmortem)

1. 検知と初動

検知の種類:

  • アラートシステムの自動検知(理想)
  • ユーザーからの問い合わせ(避けたい)
  • 外部監視サービス(補完的)

初動の判断(最初の5分):

  • ✓ 影響範囲の確認(一部ユーザー?全員?)
  • ✓ 問題の重大度の判断(P1/P2/P3)
  • ✓ 専門家のエスカレーション判断
  • ✓ ステータスページの更新

2. インシデントコマンダー(IC)制度

Google SRE が採用するロール分担:

ロール 責務
Incident Commander(IC) 対応全体の指揮。判断・優先順位付け
Operations Lead 技術的な対応作業を担当
Communications Lead 内外への情報共有・ステータス更新

ICの行動原則:

  • 自分では手を動かさない(全体を見る)
  • 定期的にステータスを共有(5〜15分おき)
  • 「わからない」を認める

3. 障害対応中のコミュニケーション

War Room でのルール:

  • ✓ 憶測を事実と混同しない(「〜だと思う」と「〜だ」を区別)
  • ✓ 変更を行う前に全員に宣言する
  • ✓ 試みたことをすべて記録する(タイムライン)
  • ✓ 定期的に現状報告する

4. ポストモーテム(事後検証)

ブレームレスポストモーテムの原則:

「障害は個人の失敗ではなく、システムの失敗である」

誰かを責めることは学習を妨げ、次回の報告を萎縮させる。

ポストモーテムのトリガー(実施すべき場合):

  • ユーザーへの影響があるダウンタイム
  • データ損失が発生
  • オンコールエンジニアが手動介入(ロールバック・トラフィック切替)
  • 解決に目標時間を超えた
  • 監視が機能せず手動で発見

ポストモーテムの構成(テンプレート):


## インシデントポストモーテム

### 概要
- 日時: 2026-04-18 02:15 JST
- 影響時間: 47分
- 影響範囲: 決済機能が全ユーザーで利用不可
- 重大度: P1

### タイムライン
| 時刻 | イベント |
|------|---------|
| 02:15 | アラート発火(決済エラー率 > 5%) |
| 02:18 | オンコールエンジニア起床・確認開始 |
| 02:30 | データベース接続枯渇と特定 |
| 02:52 | ロールバック実施 |
| 03:02 | 完全復旧確認 |

### 根本原因
- 新機能デプロイでコネクションプールの設定ミス
- 接続リークが発生し30分でプール枯渇

### 何がうまくいったか
- アラートが迅速に発火(2分以内)
- ICがすばやくエスカレーション判断

### 何がうまくいかなかったか
- ステージング環境で再現できなかった
- ロールバック手順の不備で10分ロス

### アクションアイテム
| # | 内容 | 担当 | 期限 |
|---|------|------|------|
| 1 | コネクションプールの監視アラート追加 | SRE | 2026-04-25 |
| 2 | ステージングの負荷設定を本番相当に変更 | Platform | 2026-05-01 |
| 3 | ロールバック手順書を更新 | SRE | 2026-04-22 |

オンコール設計のベストプラクティス

オンコールエンジニアを守るための原則:

✓ アラートはアクショナブルなものだけ
✓ オンコールシフトは週単位(一人が長期担当しない)
✓ 1日の深夜アラートは 2件以内を目標に
✓ ランブック(対応手順書)を常に最新に
✓ 「Wheel of Misfortune」で定期訓練
✓ オンコール後の回復時間を保証

平均障害対応時間(MTTR)の最小化:

  • MTTD(Mean Time to Detect): アラートの精度向上で改善
  • MTTI(Mean Time to Investigate): ランブック・ダッシュボード整備
  • MTTR(Mean Time to Resolve): 自動ロールバック・フィーチャーフラグ

10. カオスエンジニアリング

カオスエンジニアリングとは

「本番環境に意図的に障害を注入し、システムの回復力を検証する規律」

起源: 2010年、Netflix が “Chaos Monkey” を開発。本番環境のEC2インスタンスをランダムに停止し、システムが自動回復できることを検証した。

計画的停止の重要性:Chubbyの教訓

Google の分散ロックサービス「Chubby」から得られた重要な教訓がある。

Chubby の問題:
  Chubby はほぼ100%の可用性を長期間維持した。
  その結果、依存するサービスが「Chubby は絶対に落ちない」と
  仮定したコードを書き始めた。

  → 障害対応コードのテストがなくなった
  → フォールバック機能が腐った(誰も使わないので)
  → 実際に短時間停止した際に、依存サービスが大規模連鎖障害を起こした

教訓:
  「高可用性は、依存サービスのフォールバック能力を退化させる」

Googleの対処法:計画的停止(Planned Outage)
  Chubby チームは定期的(四半期ごと)に意図的な短時間停止を実施する。
  目的: 依存サービスが「Chubby のダウンを想定した実装」を
       維持していることを確認する。

SRE への応用:
  - SLO を 100% にしてはいけない理由の一つがこれ
  - エラーバジェットがあることで、依存関係のフォールバック能力が維持される
  - 重要なサービスは定期的にカオス実験・DR 訓練を行い、
    「依存している側が絶対に壊れない前提で設計する」ことを防ぐ

なぜカオスエンジニアリングが必要か

テスト環境での問題:
├── 本番環境と負荷・設定が異なる
├── 単体・結合テストでは分散障害は再現できない
└── 「たぶん大丈夫」という思い込みの蓄積

カオスエンジニアリングの解決:
└── 制御された方法で本番の限界を事前に発見する

カオスエンジニアリングの5ステップ

Step 1: ベースライン確立
  └── 正常時のメトリクス・SLIを記録

Step 2: 仮説を立てる
  └── 「DBをフェイルオーバーしても5秒以内に回復するはず」

Step 3: 実験設計
  └── 影響範囲(Blast Radius)を最小化して設計
  └── アボート条件(エラー率 > 10%で即停止)を定義

Step 4: 実験実行
  └── 低トラフィック時間帯に少規模から開始

Step 5: 結果分析
  └── 仮説との差分を記録・改善策を実施

代表的な障害注入パターン

パターン 内容 目的
Pod Termination KubernetesのPodをランダムに削除 レプリカ自動回復確認
Network Latency 特定サービス間に遅延を注入 タイムアウト設定の検証
Disk Fill ディスクをランダムなデータで埋める ディスク枯渇時の挙動確認
CPU Stress CPUを高負荷状態にする スロットリング・スケーリング確認
Region Failover リージョン全体を遮断 DR(災害復旧)手順の検証
DB Failover マスターDBを停止 フェイルオーバー時間の実測
HTTP Errors 特定エンドポイントに500エラーを注入 サーキットブレーカーの動作確認

主要ツール

ツール 特徴
Chaos Monkey Netflix製。EC2インスタンスのランダム停止
Gremlin エンタープライズ向け。GUI操作・豊富な障害パターン
LitmusChaos Kubernetes ネイティブ。OSS・CNCF プロジェクト
AWS Fault Injection Simulator AWS マネージド。EC2/ECS/EKS対応
Azure Chaos Studio Azure マネージド
Chaos Toolkit OSS・Python ベース

信頼性設計パターン

カオスエンジニアリングで検証する主要パターン:

サーキットブレーカー(Circuit Breaker)

  • 通常: A → B(正常)
  • 障害: A → B(失敗続き)→ サーキット開放
  • A → フォールバックレスポンス返却
  • 回復: 一定時間後に再試行 → B が回復したらサーキット閉鎖

バルクヘッド(Bulkhead)

船の隔壁のように、サービスを区画に分け一部の障害が全体に波及しないようにする。

  • 例: スレッドプールをサービスごとに分離
  • → 決済サービスが詰まってもユーザー閲覧サービスは生きている

リトライ(Retry)

失敗したリクエストを再試行する。指数バックオフ+ジッターが標準。

# 指数バックオフの例
delay = min(base_delay * (2 ** attempt) + random.uniform(0, 1), max_delay)

11. キャパシティプランニングとパフォーマンス

キャパシティプランニングとは

現在のトラフィック・成長率・イベントを元に将来のリソース需要を予測し、事前にスケールするプロセス。

目的:
├── 過負荷によるサービス停止を防ぐ
├── 無駄なリソース過剰プロビジョニングを避ける
└── 不確実性(トラフィックスパイク)に備える

キャパシティプランニングのステップ

1. 現状測定
   └── リソース使用率(CPU/Memory/Network/Disk)の時系列把握

2. 需要予測
   └── 過去のトレンドから成長率を推定
   └── 計画されたイベント(キャンペーン・リリース)を考慮

3. 余裕率の設定
   └── 通常: ピーク使用率 < 70% を目標
   └── バースト対応: 瞬間的に 100% になっても耐えられる設計

4. スケーリング戦略
   ├── 垂直スケーリング(Scale Up): インスタンスを大きくする
   └── 水平スケーリング(Scale Out): インスタンス数を増やす

5. 負荷テスト
   └── 計画した容量で実際に負荷をかけて検証

パフォーマンス最適化

ボトルネック特定の手順:

1. USE メソッドで全リソースを確認
   └── Utilization / Saturation / Errors

2. レイテンシの分解
   └── p50 / p95 / p99 の差が大きい場合は outlier の問題

3. 分散トレースで遅いサービスを特定

4. プロファイリング(CPU / メモリ / I/O)

代表的なボトルネックと対策:

ボトルネック 症状 対策
CPU 高負荷 処理が遅い・タイムアウト多発 スケールアウト・コード最適化
メモリ不足 OOMキル・スワップ多発 メモリ増量・メモリリーク調査
DB接続枯渇 接続待ちキューが詰まる コネクションプール設定・N+1クエリ修正
ネットワーク 帯域が詰まる・パケットロス CDN導入・データ圧縮・帯域増強
I/Oウェイト ディスク読み書きで詰まる SSD移行・キャッシュ追加・非同期化

負荷テストツール

ツール 特徴
k6 JavaScript で書けるモダン負荷テスト
Locust Python で書けるOSS負荷テスト
JMeter Java製の老舗ツール
wrk 軽量HTTPベンチマーク
Artillery Node.js製、YAML設定

12. CI/CDとInfrastructure as Code

SREとCI/CDの関係

SRE は CI/CD パイプラインの信頼性と安全性に責任を持つ。

  • 開発チーム: 速くリリースしたい
  • SRE: 安全にリリースしたい
  • 共通目標: 信頼性を下げずに速くリリース

デプロイ戦略

戦略 仕組み リスク ロールバック速度
Recreate 旧バージョン全停止→新バージョン起動 高(ダウンタイム発生) 遅い
Rolling 少しずつ入れ替え
Blue/Green 旧環境を残したまま新環境に切替 即時
Canary 一部ユーザーに新バージョン → 段階的展開 即時
Feature Flag コードは同じ、機能のON/OFFで制御 最低 即時

Infrastructure as Code(IaC)

インフラをコードで定義・管理する手法。

なぜIaCが必要か:

手動操作の問題:
├── 再現性がない(「あのとき何をしたか」不明)
├── 環境間の差異が発生(ドリフト)
└── スケールしない(1台なら手動でも10台では無理)

IaCのメリット:
├── バージョン管理(Git で差分追跡)
├── コードレビューによる変更管理
├── 高い再現性
└── ドキュメントとしての機能

主要ツール比較:

ツール 対象 特徴
Terraform インフラプロビジョニング マルチクラウド・HCL構文・State管理
Pulumi インフラプロビジョニング Python/TypeScript 等の汎用言語で書ける
Ansible 構成管理・設定適用 エージェントレス・YAML冪等性
Helm Kubernetesパッケージ管理 テンプレート化・バージョン管理
ArgoCD / Flux GitOps(K8s継続的デプロイ) Git が唯一の真実(SSOT)

GitOps

GitOps の原則:
1. Git リポジトリが正解(Single Source of Truth)
2. 変更は PR → レビュー → マージのみ
3. デプロイは自動的に Git の状態に収束

フロー:
開発者が PR作成
  └── CI でテスト・バリデーション
      └── マージ
          └── ArgoCD が差分を検知
              └── Kubernetes に自動適用

Terraform × Kubernetes × CI/CD の組み合わせ例

# GitHub Actions での典型的なフロー
on:
  push:
    branches: [main]
jobs:
  terraform:
    steps:
      - terraform init
      - terraform validate
      - terraform plan    # 変更プレビュー
      - terraform apply   # 本番適用(main merge時のみ)
  deploy:
    needs: terraform
    steps:
      - helm upgrade --install my-app ./chart
      - kubectl rollout status deployment/my-app

SRE が CI/CD に追加すべきゲート:

✓ 自動テスト(Unit / Integration / E2E)
✓ セキュリティスキャン(Trivy / Snyk)
✓ 変更差分のレビュー必須
✓ 段階的ロールアウト(Canary → 全体)
✓ ロールアウト後のSLI自動監視
✓ SLO 違反時の自動ロールバック


13. SREスキル詳細

Python自動化の実践

SRE の日常業務では Python が最も広く使われる言語だ。以下に実際に動くスクリプト例を示す。

SLI計算スクリプト

#!/usr/bin/env python3
"""
SLI/SLO 計算スクリプト
Prometheus API から成功率を取得してエラーバジェット残量を計算する
"""

import requests
import datetime
from dataclasses import dataclass
from typing import Optional

@dataclass
class SLOConfig:
    name: str
    slo_target: float          # 例: 0.999 = 99.9%
    window_days: int = 28      # 測定ウィンドウ(日)
    prometheus_url: str = "http://localhost:9090"
    success_query: str = ""    # 成功リクエスト数のPromQLクエリ
    total_query: str = ""      # 総リクエスト数のPromQLクエリ

def query_prometheus(prometheus_url: str, query: str, time: Optional[str] = None) -> float:
    """Prometheus instant query を実行して結果を返す"""
    params = {"query": query}
    if time:
        params["time"] = time

    resp = requests.get(f"{prometheus_url}/api/v1/query", params=params, timeout=10)
    resp.raise_for_status()

    data = resp.json()
    if data["status"] != "success":
        raise ValueError(f"Prometheus query failed: {data}")

    results = data["data"]["result"]
    if not results:
        return 0.0

    return float(results[0]["value"][1])

def calculate_sli(config: SLOConfig) -> dict:
    """SLI を計算してエラーバジェット状況を返す"""
    window_seconds = config.window_days * 24 * 3600

    # 過去 N 日間の成功・総リクエスト数
    success_query = f"increase({config.success_query}[{window_seconds}s])"
    total_query = f"increase({config.total_query}[{window_seconds}s])"

    success_count = query_prometheus(config.prometheus_url, success_query)
    total_count = query_prometheus(config.prometheus_url, total_query)

    if total_count == 0:
        return {"error": "No requests in measurement window"}

    # SLI 計算
    current_sli = success_count / total_count

    # エラーバジェット計算
    error_budget_total = 1.0 - config.slo_target          # 例: 0.001 = 0.1%
    error_budget_consumed = config.slo_target - current_sli  # 消費量
    error_budget_remaining = error_budget_total - max(0, error_budget_consumed)
    error_budget_pct = error_budget_remaining / error_budget_total * 100

    # 許容失敗リクエスト数
    allowed_failures = total_count * error_budget_total
    actual_failures = total_count - success_count

    return {
        "slo_name": config.name,
        "slo_target": f"{config.slo_target * 100:.3f}%",
        "current_sli": f"{current_sli * 100:.4f}%",
        "total_requests": int(total_count),
        "success_requests": int(success_count),
        "actual_failures": int(actual_failures),
        "allowed_failures": int(allowed_failures),
        "error_budget_remaining_pct": f"{error_budget_pct:.1f}%",
        "status": "OK" if current_sli >= config.slo_target else "BREACHED",
    }

def main():
    # 設定例
    configs = [
        SLOConfig(
            name="payment-api-availability",
            slo_target=0.999,
            window_days=28,
            prometheus_url="http://prometheus:9090",
            success_query='http_requests_total{service="payment",status!~"5.."}',
            total_query='http_requests_total{service="payment"}',
        ),
        SLOConfig(
            name="user-api-latency",
            slo_target=0.950,  # 95% of requests under 200ms
            window_days=28,
            prometheus_url="http://prometheus:9090",
            success_query='http_request_duration_seconds_bucket{service="user",le="0.2"}',
            total_query='http_request_duration_seconds_count{service="user"}',
        ),
    ]

    print("=" * 60)
    print("SLO Status Report")
    print(f"Generated: {datetime.datetime.utcnow().isoformat()}Z")
    print("=" * 60)

    for config in configs:
        try:
            result = calculate_sli(config)
            print(f"\n📊 {result['slo_name']}")
            print(f"   SLO Target:        {result['slo_target']}")
            print(f"   Current SLI:       {result['current_sli']}")
            print(f"   Error Budget:      {result['error_budget_remaining_pct']} remaining")
            print(f"   Total Requests:    {result['total_requests']:,}")
            print(f"   Status:            {result['status']}")
        except Exception as e:
            print(f"\n❌ {config.name}: Error - {e}")

if __name__ == "__main__":
    main()

Kubernetes自動再起動スクリプト

#!/usr/bin/env python3
"""
Kubernetes CrashLoopBackOff 自動対応スクリプト
一定回数以上リスタートしているPodを検知して通知・自動対応する
"""

import subprocess
import json
import time
import requests
from datetime import datetime

SLACK_WEBHOOK_URL = "https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK"
NAMESPACES = ["production", "staging"]
RESTART_THRESHOLD = 5      # この回数以上でアラート
AUTO_DELETE_THRESHOLD = 10  # この回数以上で自動削除(Deploymentは再作成)

def get_pods_info(namespace: str) -> list[dict]:
    """kubectl で Pod 情報を取得"""
    cmd = [
        "kubectl", "get", "pods",
        "-n", namespace,
        "-o", "json",
        "--field-selector=status.phase!=Succeeded"
    ]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
    if result.returncode != 0:
        print(f"Error getting pods in {namespace}: {result.stderr}")
        return []

    data = json.loads(result.stdout)
    pods = []

    for item in data.get("items", []):
        pod_name = item["metadata"]["name"]

        for cs in item.get("status", {}).get("containerStatuses", []):
            restart_count = cs.get("restartCount", 0)
            container_name = cs["name"]
            state = cs.get("state", {})

            # CrashLoopBackOff 状態の確認
            is_crashloop = "waiting" in state and \
                state["waiting"].get("reason") == "CrashLoopBackOff"

            if restart_count >= RESTART_THRESHOLD or is_crashloop:
                pods.append({
                    "namespace": namespace,
                    "pod": pod_name,
                    "container": container_name,
                    "restart_count": restart_count,
                    "is_crashloop": is_crashloop,
                    "state": state,
                })

    return pods

def send_slack_alert(pods: list[dict]) -> None:
    """Slack にアラートを送信"""
    if not pods:
        return

    blocks = [
        {
            "type": "header",
            "text": {
                "type": "plain_text",
                "text": "🚨 Kubernetes Pod Restart Alert"
            }
        },
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": f"*{len(pods)}個のPodが高頻度で再起動しています*\n"
                        f"検知時刻: {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S')} UTC"
            }
        }
    ]

    for pod in pods[:10]:  # 最大10件表示
        status = "🔴 CrashLoopBackOff" if pod["is_crashloop"] else "⚠️ 高頻度再起動"
        blocks.append({
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": (
                    f"*{pod['pod']}* ({pod['namespace']})\n"
                    f"コンテナ: `{pod['container']}` | "
                    f"再起動回数: `{pod['restart_count']}` | {status}"
                )
            }
        })

    payload = {"blocks": blocks}
    resp = requests.post(SLACK_WEBHOOK_URL, json=payload, timeout=10)
    resp.raise_for_status()

def auto_delete_pod(namespace: str, pod_name: str) -> bool:
    """Pod を削除(Deployment は自動で再作成)"""
    cmd = ["kubectl", "delete", "pod", pod_name, "-n", namespace, "--grace-period=0"]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
    if result.returncode == 0:
        print(f"Deleted pod {pod_name} in {namespace}")
        return True
    else:
        print(f"Failed to delete pod {pod_name}: {result.stderr}")
        return False

def main():
    print(f"Starting pod restart monitor at {datetime.utcnow().isoformat()}")

    all_problematic_pods = []

    for namespace in NAMESPACES:
        pods = get_pods_info(namespace)
        all_problematic_pods.extend(pods)

        # 閾値超えのPodを自動削除
        for pod in pods:
            if pod["restart_count"] >= AUTO_DELETE_THRESHOLD:
                print(f"Auto-deleting {pod['pod']} (restarts: {pod['restart_count']})")
                auto_delete_pod(namespace, pod["pod"])

    if all_problematic_pods:
        send_slack_alert(all_problematic_pods)
        print(f"Alert sent for {len(all_problematic_pods)} pods")
    else:
        print("No problematic pods found")

if __name__ == "__main__":
    main()

インシデント自動初動スクリプト(Slack Bot連携)

#!/usr/bin/env python3
"""
PagerDuty Webhook → Slack インシデント自動通知・初動対応スクリプト
FastAPI で PagerDuty の webhook を受け取り、Slack に構造化通知を送る
"""

from fastapi import FastAPI, Request, HTTPException
from pydantic import BaseModel
import httpx
import asyncio
import os
from datetime import datetime
from typing import Optional

app = FastAPI()

SLACK_BOT_TOKEN = os.environ["SLACK_BOT_TOKEN"]
INCIDENT_CHANNEL = "#incidents"
RUNBOOK_BASE_URL = "https://wiki.example.com/runbooks"

class PDIncident(BaseModel):
    id: str
    title: str
    severity: str  # critical, high, warning, info
    status: str
    service_name: str
    created_at: str
    html_url: str
    escalation_policy: Optional[str] = None

async def post_slack_message(channel: str, blocks: list, text: str = "") -> str:
    """Slack API でメッセージを送信し、ts(メッセージID)を返す"""
    async with httpx.AsyncClient() as client:
        resp = await client.post(
            "https://slack.com/api/chat.postMessage",
            headers={"Authorization": f"Bearer {SLACK_BOT_TOKEN}"},
            json={
                "channel": channel,
                "text": text,
                "blocks": blocks,
            },
            timeout=10,
        )
        data = resp.json()
        if not data.get("ok"):
            raise HTTPException(500, f"Slack error: {data.get('error')}")
        return data["ts"]

def build_incident_blocks(incident: PDIncident) -> list:
    """インシデント通知の Slack Block Kit を構築"""
    severity_emoji = {
        "critical": "🔴",
        "high": "🟠",
        "warning": "🟡",
        "info": "🔵",
    }.get(incident.severity, "⚪")

    severity_label = {
        "critical": "P1 - 緊急",
        "high": "P2 - 重大",
        "warning": "P3 - 軽微",
        "info": "P4 - 情報",
    }.get(incident.severity, incident.severity)

    # ランブックURL(サービス名でマッピング)
    runbook_url = f"{RUNBOOK_BASE_URL}/{incident.service_name.lower()}"

    return [
        {
            "type": "header",
            "text": {
                "type": "plain_text",
                "text": f"{severity_emoji} インシデント発生: {incident.title}"
            }
        },
        {
            "type": "section",
            "fields": [
                {"type": "mrkdwn", "text": f"*重大度:*\n{severity_label}"},
                {"type": "mrkdwn", "text": f"*サービス:*\n{incident.service_name}"},
                {"type": "mrkdwn", "text": f"*検知時刻:*\n{incident.created_at}"},
                {"type": "mrkdwn", "text": f"*ステータス:*\n{incident.status}"},
            ]
        },
        {
            "type": "actions",
            "elements": [
                {
                    "type": "button",
                    "text": {"type": "plain_text", "text": "📋 ランブック"},
                    "url": runbook_url,
                    "style": "primary",
                },
                {
                    "type": "button",
                    "text": {"type": "plain_text", "text": "🔗 PagerDuty"},
                    "url": incident.html_url,
                },
                {
                    "type": "button",
                    "text": {"type": "plain_text", "text": "📊 ダッシュボード"},
                    "url": f"https://grafana.example.com/d/{incident.service_name}",
                },
            ]
        },
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": (
                    "👇 *初動チェックリスト*\n"
                    "• [ ] Incident Commanderを宣言する\n"
                    "• [ ] 影響範囲を確認する\n"
                    "• [ ] ステータスページを更新する\n"
                    "• [ ] 関係者にエスカレーションする"
                )
            }
        }
    ]

@app.post("/webhook/pagerduty")
async def pagerduty_webhook(request: Request):
    """PagerDuty からの Webhook を受け取って処理"""
    body = await request.json()

    for event in body.get("messages", []):
        incident_data = event.get("incident", {})

        if event.get("event") not in ["incident.trigger", "incident.escalate"]:
            continue

        incident = PDIncident(
            id=incident_data["id"],
            title=incident_data["title"],
            severity=incident_data.get("urgency", "high"),
            status=incident_data["status"],
            service_name=incident_data.get("service", {}).get("name", "unknown"),
            created_at=incident_data["created_at"],
            html_url=incident_data["html_url"],
        )

        blocks = build_incident_blocks(incident)
        ts = await post_slack_message(
            INCIDENT_CHANNEL,
            blocks,
            text=f"インシデント: {incident.title}"
        )

        print(f"Posted incident {incident.id} to Slack (ts={ts})")

    return {"status": "ok"}

Go言語の基礎とSREでの使われ方

Go(Golang)はSRE界隈で急速に普及している。Prometheus、Kubernetes、Terraform、Vault など、SREが日常的に使うツールのほとんどが Go で書かれている。

なぜGo がSREに向いているか

Go の特徴:
├── コンパイル言語 → シングルバイナリ → デプロイが簡単
├── 静的型付け → バグを早期発見
├── ゴルーチン → 並行処理が書きやすい
├── 標準ライブラリが充実 → HTTP サーバー、JSON等がすぐ使える
└── 実行速度 → Python より 10~100x 速い場合がある

Go で書く最小HTTPヘルスチェックサーバー

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "os"
    "runtime"
    "time"
)

// HealthResponse はヘルスチェックのレスポンス構造体
type HealthResponse struct {
    Status    string            `json:"status"`
    Timestamp string            `json:"timestamp"`
    Version   string            `json:"version"`
    Uptime    string            `json:"uptime"`
    Checks    map[string]string `json:"checks"`
}

var startTime = time.Now()
var version = getEnv("APP_VERSION", "v1.0.0")

func getEnv(key, fallback string) string {
    if v := os.Getenv(key); v != "" {
        return v
    }
    return fallback
}

// checkDatabase は DB 接続を確認する(例)
func checkDatabase() string {
    // 実際は DB ping を実行
    return "ok"
}

// checkRedis は Redis 接続を確認する(例)
func checkRedis() string {
    // 実際は Redis PING を実行
    return "ok"
}

func healthHandler(w http.ResponseWriter, r *http.Request) {
    checks := map[string]string{
        "database": checkDatabase(),
        "redis":    checkRedis(),
    }

    // 全チェックが OK かどうか
    status := "healthy"
    for _, v := range checks {
        if v != "ok" {
            status = "degraded"
            w.WriteHeader(http.StatusServiceUnavailable)
            break
        }
    }

    resp := HealthResponse{
        Status:    status,
        Timestamp: time.Now().UTC().Format(time.RFC3339),
        Version:   version,
        Uptime:    time.Since(startTime).String(),
        Checks:    checks,
    }

    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(resp)
}

// Prometheus メトリクスを手動で expose する例
func metricsHandler(w http.ResponseWriter, r *http.Request) {
    var ms runtime.MemStats
    runtime.ReadMemStats(&ms)

    fmt.Fprintf(w, "# HELP app_memory_alloc_bytes Currently allocated memory\n")
    fmt.Fprintf(w, "# TYPE app_memory_alloc_bytes gauge\n")
    fmt.Fprintf(w, "app_memory_alloc_bytes %d\n", ms.Alloc)

    fmt.Fprintf(w, "# HELP app_uptime_seconds Uptime in seconds\n")
    fmt.Fprintf(w, "# TYPE app_uptime_seconds counter\n")
    fmt.Fprintf(w, "app_uptime_seconds %.0f\n", time.Since(startTime).Seconds())

    fmt.Fprintf(w, "# HELP app_goroutines_count Number of goroutines\n")
    fmt.Fprintf(w, "# TYPE app_goroutines_count gauge\n")
    fmt.Fprintf(w, "app_goroutines_count %d\n", runtime.NumGoroutine())
}

func main() {
    port := getEnv("PORT", "8080")

    mux := http.NewServeMux()
    mux.HandleFunc("/health", healthHandler)
    mux.HandleFunc("/metrics", metricsHandler)
    mux.HandleFunc("/ready", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        fmt.Fprint(w, "ok")
    })

    server := &http.Server{
        Addr:         ":" + port,
        Handler:      mux,
        ReadTimeout:  5 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  60 * time.Second,
    }

    log.Printf("Starting health check server on port %s (version: %s)", port, version)
    log.Fatal(server.ListenAndServe())
}

Bash高度なスクリプトテクニック

SREが知っておくべき Bash パターン集

#!/usr/bin/env bash
# SRE 実践的 Bash スクリプト集
# =====================================================

# ===== エラーハンドリングの基本(常に設定する) =====
set -euo pipefail
# -e: コマンドが失敗したら即終了
# -u: 未定義変数をエラー
# -o pipefail: パイプラインの途中でも失敗を検知

# トラップでエラー時に詳細表示
trap 'echo "Error on line $LINENO: command failed with exit code $?" >&2' ERR
trap 'echo "Script interrupted" >&2' INT TERM

# ===== ログ関数 =====
readonly LOG_FILE="/var/log/sre-script.log"

log() {
    local level="$1"
    shift
    local message="$*"
    local timestamp
    timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
    echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}

log_info()  { log "INFO " "$@"; }
log_warn()  { log "WARN " "$@"; }
log_error() { log "ERROR" "$@"; }

# ===== リトライ関数 =====
retry() {
    local max_attempts="${1}"
    local delay="${2}"
    local description="${3}"
    shift 3
    local cmd=("$@")

    local attempt=1
    while true; do
        if "${cmd[@]}"; then
            return 0
        fi

        if (( attempt >= max_attempts )); then
            log_error "$description: 最大試行回数 ($max_attempts) に達しました"
            return 1
        fi

        log_warn "$description: 試行 $attempt/$max_attempts 失敗。${delay}秒後にリトライ..."
        sleep "$delay"
        (( attempt++ ))
    done
}

# 使用例:
# retry 5 10 "データベース接続" check_database_connection

# ===== サービス状態確認 =====
check_service_health() {
    local url="$1"
    local expected_status="${2:-200}"
    local timeout="${3:-5}"

    local actual_status
    actual_status=$(curl -s -o /dev/null -w "%{http_code}" \
        --max-time "$timeout" \
        "$url" 2>/dev/null) || true

    if [[ "$actual_status" == "$expected_status" ]]; then
        return 0
    else
        log_error "Health check failed for $url: expected $expected_status, got $actual_status"
        return 1
    fi
}

# ===== ディスク使用率アラート =====
check_disk_usage() {
    local threshold="${1:-80}"
    local alert_command="${2:-echo}"

    # df の出力を解析
    while IFS= read -r line; do
        local usage filesystem mountpoint
        usage=$(echo "$line" | awk '{print $5}' | tr -d '%')
        filesystem=$(echo "$line" | awk '{print $1}')
        mountpoint=$(echo "$line" | awk '{print $6}')

        if (( usage > threshold )); then
            local message="DISK ALERT: $filesystem ($mountpoint) is ${usage}% full (threshold: ${threshold}%)"
            log_warn "$message"
            $alert_command "$message"
        fi
    done < <(df -h | tail -n +2)
}

# ===== Kubernetes デプロイ待機 =====
wait_for_deployment() {
    local namespace="$1"
    local deployment="$2"
    local timeout="${3:-300}"  # デフォルト5分

    log_info "Waiting for deployment $deployment in $namespace (timeout: ${timeout}s)"

    if kubectl rollout status deployment/"$deployment" \
        -n "$namespace" \
        --timeout="${timeout}s"; then
        log_info "Deployment $deployment is ready"
        return 0
    else
        log_error "Deployment $deployment timed out or failed"
        # デバッグ情報を収集
        kubectl describe deployment "$deployment" -n "$namespace" >> "$LOG_FILE" 2>&1
        kubectl get pods -n "$namespace" -l "app=$deployment" -o wide >> "$LOG_FILE" 2>&1
        return 1
    fi
}

# ===== 並列処理(最大N並列) =====
run_parallel() {
    local max_jobs="$1"
    shift
    local items=("$@")

    local running=0
    local pids=()

    for item in "${items[@]}"; do
        # 最大並列数に達したら待機
        while (( running >= max_jobs )); do
            for i in "${!pids[@]}"; do
                if ! kill -0 "${pids[$i]}" 2>/dev/null; then
                    unset "pids[$i]"
                    (( running-- ))
                fi
            done
            sleep 0.1
        done

        process_item "$item" &
        pids+=($!)
        (( running++ ))
    done

    # 残りのジョブを待機
    wait "${pids[@]}" 2>/dev/null || true
}

# ===== JSON パース(jq を使わずに) =====
# jq が使える場合は jq を推奨。使えない場合の代替:
parse_json_value() {
    local json="$1"
    local key="$2"
    echo "$json" | python3 -c "import sys,json; data=json.load(sys.stdin); print(data.get('$key',''))"
}

# ===== 環境変数の検証 =====
require_env() {
    local var_name="$1"
    local description="${2:-$var_name}"

    if [[ -z "${!var_name:-}" ]]; then
        log_error "Required environment variable not set: $var_name ($description)"
        exit 1
    fi
}

# 使用例:
# require_env "DATABASE_URL" "データベース接続文字列"
# require_env "SLACK_WEBHOOK_URL" "Slack Webhook URL"

# ===== ロック機構(多重起動防止) =====
LOCK_FILE="/tmp/${0##*/}.lock"

acquire_lock() {
    if ! mkdir "$LOCK_FILE" 2>/dev/null; then
        local pid
        pid=$(cat "$LOCK_FILE/pid" 2>/dev/null || echo "unknown")
        log_error "Script is already running (PID: $pid). Lock: $LOCK_FILE"
        exit 1
    fi
    echo $ > "$LOCK_FILE/pid"
    trap 'rm -rf "$LOCK_FILE"' EXIT
    log_info "Lock acquired: $LOCK_FILE"
}

# ===== メイン処理例 =====
main() {
    log_info "Script started (PID: $)"
    acquire_lock

    # 環境変数チェック
    require_env "PROMETHEUS_URL"
    require_env "SLACK_WEBHOOK_URL"

    # サービスヘルスチェック
    local services=(
        "https://api.example.com/health"
        "https://auth.example.com/health"
        "https://payment.example.com/health"
    )

    local failed=0
    for url in "${services[@]}"; do
        if ! retry 3 5 "Health check: $url" check_service_health "$url"; then
            (( failed++ ))
        fi
    done

    if (( failed > 0 )); then
        log_error "$failed サービスの健全性チェックが失敗しました"
        exit 1
    fi

    # ディスク使用率チェック
    check_disk_usage 85 "echo"

    log_info "Script completed successfully"
}

main "$@"

Linuxパフォーマンス分析の詳細(Brendan Gregg メソッド)

Brendan Gregg の60秒チェックリスト

本番サーバーのパフォーマンス問題を報告されたとき、最初の60秒で実行すべきコマンド:

# 1. システム全体の概観(10秒観察)
uptime
# load average の確認。CPUコア数と比較して負荷を判断
# load average: 1.52, 0.89, 0.78 → 1分/5分/15分平均

# 2. エラーメッセージ確認
dmesg | tail -20

# 3. vmstat で仮想メモリ・CPU統計(1秒間隔、5回)
vmstat 1 5
# r: 実行待ちプロセス数
# b: ブロックされたプロセス数
# si/so: スワップイン/アウト(これが高いとメモリ不足)
# us/sy/id/wa: user/system/idle/iowait

# 4. CPU ごとの使用率
mpstat -P ALL 1 3

# 5. プロセス別CPU・メモリ
pidstat 1 5

# 6. ディスクI/O
iostat -xz 1 3
# await: I/O の平均待ち時間(ms)
# %util: ディスクの使用率

# 7. メモリ統計
free -m
# available が少ない → メモリ不足

# 8. ネットワーク統計
sar -n DEV 1 3
# rxkB/s, txkB/s: 受信/送信帯域

# 9. ソケット状態
ss -s
# TIME_WAIT が多い → 接続のリサイクルが追いついていない

# 10. 実行中のプロセス top
top -b -n 1 | head -30

USE メソッドによるリソース分析

USE(Utilization, Saturation, Errors)メソッドを各リソースに体系的に適用する:

# ==== CPU ====
# Utilization(使用率)
grep 'cpu ' /proc/stat  # user + system / total
mpstat -P ALL 1 1

# Saturation(飽和)
vmstat 1 | awk '{print $1}'  # r コラム(実行待ちキュー長)
sar -q 1 3                   # runq-sz

# Errors
perf stat -a sleep 1 2>&1 | grep "Hardware"

# ==== Memory ====
# Utilization
free -m
cat /proc/meminfo | grep -E "MemTotal|MemFree|Cached|Buffers"

# Saturation(スワップ使用 = メモリが足りない)
vmstat 1 | awk '{print $7, $8}'  # si so

# ==== Disk I/O ====
# Utilization
iostat -xz 1 | grep -v "^{{CONTENT}}quot; | awk '$1 != "Device:" {print $1, $14}'
# %util カラム

# Saturation(キュー長)
iostat -xz 1 | awk '{print $1, $9}'  # avgqu-sz

# ==== Network ====
# Utilization(帯域使用率)
sar -n DEV 1 3

# Errors(ドロップ・エラー)
ip -s link
netstat -s | grep -E "errors|dropped"

# ==== File Descriptors ====
# Utilization
cat /proc/sys/fs/file-nr  # 使用中/最大
lsof | wc -l

# プロセス別 fd 数
ls /proc/<PID>/fd | wc -l

フレームグラフ(Flame Graphs)の生成

CPU のホットスポットを視覚的に特定するフレームグラフ:

# ==== perf を使ったフレームグラフ生成 ====

# 1. perf でスタックトレースを収集(30秒間)
sudo perf record -F 99 -a -g -- sleep 30

# 2. FlameGraph ツールをダウンロード
git clone https://github.com/brendangregg/FlameGraph
cd FlameGraph

# 3. perf データをフレームグラフに変換
sudo perf script | ./stackcollapse-perf.pl | ./flamegraph.pl > cpu-flamegraph.svg

# ブラウザで cpu-flamegraph.svg を開く
# → 一番幅が広いバーが CPU を最も消費している関数

# ==== 特定プロセスのフレームグラフ ====
sudo perf record -F 99 -p <PID> -g -- sleep 30
sudo perf script | ./stackcollapse-perf.pl | ./flamegraph.pl > app-flamegraph.svg

# ==== Off-CPU フレームグラフ(ブロック待ち分析)====
# bpftrace を使用
sudo bpftrace -e '
profile:hz:49 /pid == <PID>/ {
    @[kstack, ustack] = count();
}
' > offcpu.txt
# → I/O 待ちやロック待ちのスタックが可視化される

eBPF による高度なトレーシング

eBPF(extended Berkeley Packet Filter)は Linux カーネルの動作をプログラムで観察できる強力な仕組みだ。

# ==== bpftrace サンプル集 ====

# 1. レイテンシが100ms以上の read() システムコールを表示
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_read { @start[tid] = nsecs; }
tracepoint:syscalls:sys_exit_read
/@start[tid]/
{
    $lat = (nsecs - @start[tid]) / 1000000;
    if ($lat > 100) {
        printf("PID %d: read() took %d ms\n", pid, $lat);
    }
    delete(@start[tid]);
}
'

# 2. TCP 接続をリアルタイムで追跡
sudo bpftrace -e '
tracepoint:sock:inet_sock_set_state
/args->newstate == 1/
{
    printf("TCP connect: %s:%d -> %s:%d\n",
        ntop(args->saddr), args->sport,
        ntop(args->daddr), args->dport);
}
'

# 3. ファイルオープンの監視(どのファイルがアクセスされているか)
sudo bpftrace -e '
tracepoint:syscalls:sys_enter_openat
{
    printf("%-6d %-16s %s\n", pid, comm, str(args->filename));
}
' | head -50

# 4. メモリアロケーション追跡(メモリリーク調査)
sudo bpftrace -e '
uprobe:/usr/lib/x86_64-linux-gnu/libc.so.6:malloc
{
    @bytes[pid, comm] = sum(arg0);
}
END { print(@bytes); }
' -- sleep 10

BCC ツールキット(実践コマンド集)

# bcc-tools インストール
sudo apt-get install -y bpfcc-tools linux-headers-$(uname -r)

# ==== ディスク I/O レイテンシ分布 ====
sudo biolatency-bpfcc -D 10 1
# → ヒストグラムでディスクレイテンシの分布を表示

# ==== TCP リトライ監視 ====
sudo tcpretrans-bpfcc
# → TCP 再送が発生するたびに表示

# ==== ファイルシステムレイテンシ ====
sudo ext4slower-bpfcc 10
# → 10ms 以上かかった ext4 操作を表示

# ==== システムコールレイテンシ ====
sudo syscount-bpfcc -L
# → システムコール別の呼び出し回数とレイテンシ

# ==== ブロックデバイス I/O 追跡 ====
sudo biotop-bpfcc 1 10
# → top コマンドのようにプロセス別 I/O を表示

ネットワークのSRE視点での深掘り

TCP チューニング

# ==== Linux TCP チューニングパラメータ ====

# 現在の設定確認
sysctl net.core.somaxconn
sysctl net.ipv4.tcp_max_syn_backlog
sysctl net.ipv4.ip_local_port_range
sysctl net.ipv4.tcp_fin_timeout
sysctl net.ipv4.tcp_tw_reuse

# 高トラフィックサービス向けチューニング(/etc/sysctl.conf に追記)
cat >> /etc/sysctl.conf << 'EOF'
# 接続バックログキューを増やす
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

# TIME_WAIT ソケットの再利用を許可
net.ipv4.tcp_tw_reuse = 1

# ローカルポート範囲を広げる
net.ipv4.ip_local_port_range = 1024 65535

# FIN_WAIT タイムアウトを短縮
net.ipv4.tcp_fin_timeout = 15

# TCP KeepAlive(デッドコネクション検出を速める)
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 10

# 受信/送信バッファを増やす
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

# TIME_WAIT ソケットの最大数
net.ipv4.tcp_max_tw_buckets = 1440000
EOF

# 即時適用
sysctl -p

DNS問題のデバッグ

# ==== DNSトラブルシューティング手順 ====

# 1. 基本的な名前解決確認
dig example.com
nslookup example.com
host example.com

# 2. 特定の DNS サーバーに問い合わせ
dig @8.8.8.8 example.com
dig @1.1.1.1 example.com AAAA  # IPv6 レコード

# 3. DNS 応答時間の確認
dig example.com | grep "Query time"

# 4. DNSSEC 検証
dig example.com +dnssec

# 5. 権威 DNS サーバーへの直接問い合わせ
dig NS example.com                    # NS レコード取得
dig @ns1.example.com example.com A   # 権威サーバーに直接問い合わせ

# 6. DNS キャッシュのチェック
# macOS
sudo killall -HUP mDNSResponder       # キャッシュクリア
# Linux (systemd-resolved)
resolvectl statistics
resolvectl flush-caches

# 7. TTL の確認
dig +ttl example.com
# 低すぎる TTL はパフォーマンス問題、高すぎると障害時の切替が遅い

# 8. Kubernetes の DNS 問題調査
# Pod 内から DNS を確認
kubectl run -it --rm debug --image=busybox --restart=Never -- nslookup kubernetes.default
kubectl run -it --rm debug --image=nicolaka/netshoot --restart=Never -- dig svc-name.namespace.svc.cluster.local

# CoreDNS ログ確認
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50

# 9. DNS over TCP(UDP が詰まっている場合)
dig +tcp example.com

# 10. EDNS 問題の調査(大きなレスポンスが通らない)
dig example.com +bufsize=512
dig example.com +ignore  # truncation を無視

ロードバランサーのデバッグ

# ==== ロードバランサー問題調査 ====

# 1. バックエンドの健全性確認
# AWS ALB の場合
aws elbv2 describe-target-health \
  --target-group-arn arn:aws:elasticloadbalancing:... \
  --query 'TargetHealthDescriptions[*].[Target.Id,TargetHealth.State,TargetHealth.Description]' \
  --output table

# 2. 接続タイムアウトの問題(L4 LB)
# TCP タイムアウトとアプリのタイムアウトを比較
# 多くの LB はデフォルト 60 秒でアイドル接続を切断する
# アプリ側の keepalive がこれより短くなければならない

# 3. スティッキーセッションの確認
# セッションが特定のバックエンドに固定されているか確認
for i in {1..10}; do
    curl -s -I -c cookies.txt -b cookies.txt https://example.com/api | grep X-Backend-Server
done

# 4. ヘルスチェックのデバッグ
# LB がヘルスチェックに使う IP からバックエンドに直接アクセス
curl -H "X-Forwarded-For: LB_IP" http://backend:8080/health

# 5. レスポンスタイムの分布確認(ALB アクセスログから)
# S3 に保存した ALB ログを解析
aws s3 cp s3://my-alb-logs/latest.log.gz - | \
  gunzip | \
  awk '{print $5}' | \  # target_processing_time フィールド
  sort -n | \
  awk 'BEGIN{c=0} {a[c++]=$1} END{
    p50=int(c*0.5); p95=int(c*0.95); p99=int(c*0.99);
    printf "p50=%.3f p95=%.3f p99=%.3f\n", a[p50], a[p95], a[p99]
  }'

14. インシデント管理実践

flowchart LR A["検知"] --> B["トリアージ"] B --> C["指揮系統を確立"] C --> D["影響範囲を把握"] D --> E["緩和・復旧"] E --> F["ユーザー・社内へ共有"] F --> G["ポストモーテム"] G --> H["再発防止アクション"] H --> A

インシデント対応は、原因究明から始めるよりも、まず影響を止めることを優先します。復旧後に学習と再発防止へ進めるため、役割、時系列、判断理由を残しながら進めます。

インシデント重大度の定義(P1〜P4)

┌─────────┬────────────────────────────┬─────────────────────┬────────────────────┐
│ 重大度  │ 定義                       │ 応答時間            │ 対応者             │
├─────────┼────────────────────────────┼─────────────────────┼────────────────────┤
│ P1      │ 本番環境の完全停止または   │ 即時(24時間365日) │ オンコールSRE +    │
│ CRITICAL│ 収益影響 > 1万円/分         │ 応答: 5分以内       │ サービスオーナー   │
│         │ データ損失リスク           │ 解決目標: 60分以内  │ + 管理職           │
├─────────┼────────────────────────────┼─────────────────────┼────────────────────┤
│ P2      │ 主要機能の一部停止         │ 業務時間は即時      │ オンコールSRE +    │
│ HIGH    │ ユーザー影響 > 10%         │ 夜間は翌朝          │ サービスオーナー   │
│         │ パフォーマンス著しく劣化   │ 解決目標: 4時間     │                    │
├─────────┼────────────────────────────┼─────────────────────┼────────────────────┤
│ P3      │ 一部ユーザーへの影響       │ 業務時間内          │ オンコールSRE      │
│ MEDIUM  │ 機能が劣化しているが使える │ 解決目標: 24時間    │                    │
│         │ 内部ツールの問題           │                     │                    │
├─────────┼────────────────────────────┼─────────────────────┼────────────────────┤
│ P4      │ 軽微な問題                 │ 次のスプリント      │ 開発チーム         │
│ LOW     │ ユーザー影響なし           │ 解決目標: 1週間     │                    │
│         │ 改善提案                   │                     │                    │
└─────────┴────────────────────────────┴─────────────────────┴────────────────────┘

インシデントコマンドシステム(ICS)の役割

大規模インシデントでは役割を明確に分けることで、混乱なく対応できる。GoogleのSREが採用するICS(Incident Command System)の役割定義。

ICS の主要役割:

┌─────────────────────────────────────────────────────────────┐
│  IC(Incident Commander)インシデントコマンダー              │
│  ・インシデント対応の最終意思決定者                         │
│  ・War Room のファシリテーター                               │
│  ・エスカレーションの判断                                   │
│  ・ロールバックなど影響の大きい操作の承認                   │
│  ・外部コミュニケーションの監督                             │
│  ※ 技術作業はしない。全体を見ることに専念する              │
├─────────────────────────────────────────────────────────────┤
│  Ops Lead(オペレーションリード)                           │
│  ・技術的な調査・対応作業を主導                             │
│  ・調査結果を IC に報告                                     │
│  ・作業の優先順位を判断してチームに指示                     │
│  ・他のエンジニアにタスクをアサイン                         │
├─────────────────────────────────────────────────────────────┤
│  Comms Lead(コミュニケーションリード)                     │
│  ・Slack への定期更新投稿(15〜30分ごと)                   │
│  ・ステータスページの更新                                   │
│  ・顧客向けサポートへの情報提供                             │
│  ・経営層へのエスカレーション連絡                           │
│  ※ 技術作業に参加しない。コミュニケーションに集中する      │
├─────────────────────────────────────────────────────────────┤
│  Subject Matter Expert(SME)必要に応じて複数               │
│  ・特定システム(DB、ネットワーク、決済等)の専門家         │
│  ・Ops Lead の指示のもとで調査・修正作業を実施              │
└─────────────────────────────────────────────────────────────┘

役割のアサイン原則:
1. IC は技術作業をしない(全体視点を失うため)
2. 小規模インシデント(P2以下)では IC と Ops Lead を兼任してよい
3. P1 では必ず Comms Lead を別途アサインする
4. IC が疲弊したら交代する(2時間を目安)

インシデント宣言の文言:
「[名前] が IC を引き受けます。Ops Lead は [名前]、Comms Lead は [名前] でいきます。
 War Room はこちら: [URL]。15分ごとに更新します。」

エスカレーションマトリクス

発生から N 分後のエスカレーション:

[ 0分 ]   → アラート発火 → オンコールSREへPagerDuty通知
[ 5分 ]   → 未応答の場合 → 二次オンコール(バックアップ)に通知
[ 15分 ]  → P1未解決の場合 → Engineering Manager に通知
[ 30分 ]  → P1未解決の場合 → Director of Engineering に通知
[ 60分 ]  → P1未解決の場合 → CTO に通知 + 顧客向けステータスページに影響を記載
[ 2時間 ] → P1未解決の場合 → 全社インシデント体制(War Room 招集)

P2 のエスカレーション:
[ 0分 ]   → Slack #incidents チャンネルに自動通知
[ 30分 ]  → 未対応の場合 → サービスオーナーにダイレクトメッセージ
[ 4時間 ] → 未解決の場合 → Engineering Manager に通知

コミュニケーションテンプレート集

初動(インシデント発生から5分以内)

【インシデント発生通知】Slack #incidents

:rotating_light: *インシデント発生* :rotating_light:
━━━━━━━━━━━━━━━━━━━━━━━━━━
*重大度:* P1(クリティカル)
*検知時刻:* 2026-04-18 02:15 JST
*影響:* 決済機能が全ユーザーで利用不可
*IC(Incident Commander):* @yamada-taro

*現在の状況:*
調査開始。原因特定中。

*次回更新:* 02:30 JST(15分後)
━━━━━━━━━━━━━━━━━━━━━━━━━━
*War Room:* https://meet.google.com/xxx-yyyy-zzz
*インシデントチケット:* https://jira.example.com/INC-1234

進捗更新(15〜30分ごと)

【インシデント更新 #2】02:30 JST

*現状:*
データベース接続プールの枯渇を確認。
新デプロイ(v2.4.1, 01:45 JST)との相関あり。

*試みた対応:*
- ログ調査: DB エラー急増を確認(02:20 JST)
- メトリクス確認: connection_pool_active が 100% を継続(02:22 JST)

*仮説:*
v2.4.1 のコードに接続リークがある可能性。
ロールバックを検討中。

*次のアクション:*
- @suzuki-hanako: v2.4.0 へのロールバック準備中
- @tanaka-kenji: コードのコネクション管理部分を確認中

*次回更新:* 02:45 JST

解決通知

【インシデント解決】03:02 JST ✅

*解決しました*
決済機能が正常に回復しました。

*対応内容:*
v2.4.0 へのロールバックにより解決

*影響時間:* 47分(02:15〜03:02 JST)
*影響ユーザー:* 全ユーザー(決済機能のみ)
*推定損失取引:* 約 1,200 件(確定額: 調査中)

*根本原因(暫定):*
v2.4.1 の新機能実装でコネクションリークが発生

*次のステップ:*
- ポストモーテムを 2026-04-20 に実施予定
- v2.4.1 の問題修正後、再デプロイ

ご迷惑をおかけしました。

ステータスページ文章(ユーザー向け)

# 障害発生時(技術的でない言葉で)

タイトル: 決済機能の一時停止について

現在、一部のお客様において決済処理が正常に完了しない
障害が発生しております。

【影響する機能】
- 新規注文の決済
- 定期購入の更新処理

【影響しない機能】
- 商品の閲覧・カート追加
- 注文履歴の確認
- カスタマーサポートへのお問い合わせ

現在、技術チームが最優先で対応中です。
次回更新: 30分後(02:45 JST 予定)

---

# 解決時

タイトル: 決済機能の復旧について ✅

先ほど発生しておりました決済機能の障害は、
03:02 JST に解決いたしました。

障害期間中に決済が完了しなかった場合は、
再度お試しいただくか、カスタマーサポートまで
お問い合わせください。

ご不便をおかけし、誠に申し訳ございませんでした。

ポストモーテム実例(詳細版)

# インシデントポストモーテム: 決済API障害

## INC-2026-0418-001

**作成者**: 山田 太郎(IC)
**レビュー**: 鈴木 花子, 田中 健二
**ステータス**: 承認済み
**作成日**: 2026-04-20

---

## エグゼクティブサマリー

2026年4月18日 02:15〜03:02 JST(47分間)、決済APIが全ユーザーで
利用不可になりました。原因はデプロイされた v2.4.1 の
データベースコネクション管理のバグで、約1,200件の取引が
失敗しました。推定売上影響は約240万円です。

---

## 影響範囲

| 指標 | 値 |
|------|-----|
| 影響時間 | 47分 |
| 影響ユーザー | 全ユーザー(決済機能) |
| 失敗した取引 | 約1,200件 |
| 推定売上損失 | 約240万円 |
| SLO へのインパクト | 月間エラーバジェットの 32% を消費 |

---

## タイムライン

| 時刻 (JST) | イベント |
|-----------|---------|
| 01:45 | v2.4.1 を本番環境にデプロイ(Canary 5%) |
| 01:50 | Canary 段階でのエラー率正常 → 100% に昇格 |
| 02:15 | Prometheusアラート発火: payment_error_rate > 5% |
| 02:18 | オンコールエンジニア(山田)が応答・調査開始 |
| 02:20 | Slackに初動通知。P1宣言。 |
| 02:22 | DB コネクションプール使用率が 100% に到達していることを確認 |
| 02:25 | 鈴木・田中をWar Roomに招集 |
| 02:30 | v2.4.1 のデプロイタイミングと障害の相関を確認 |
| 02:35 | v2.4.1 のコードを確認: コネクションリークのバグを発見 |
| 02:45 | v2.4.0 へのロールバック実施(kubectl rollout undo)|
| 02:52 | エラー率が低下開始 |
| 03:02 | エラー率が正常レベルに回復。解決宣言 |
| 03:10 | ステータスページを解決に更新 |

---

## 根本原因分析(5 Whys)

**症状**: 決済API が 5xx エラーを返している

**Why 1**: なぜ 5xx エラーが返っているか?
→ データベースへの接続が取得できていない

**Why 2**: なぜ接続を取得できないか?
→ コネクションプールが枯渇している(使用率 100%)

**Why 3**: なぜプールが枯渇したか?
→ v2.4.1 の新機能(非同期キュー処理)で接続が返却されないリークが発生

**Why 4**: なぜリークするコードがリリースされたか?
→ ステージング環境の DB コネクション設定が本番と大きく異なるため、
  ステージングではリークが顕在化しなかった

**Why 5**: なぜ Canary 段階で検知できなかったか?
→ Canary(5%トラフィック)の観測時間が5分間だったが、
  リークが顕在化するには約25〜30分かかった

---

## コントリビューティングファクター(寄与因子)

1. **ステージング環境と本番の設定差異**
   - ステージングのコネクションプールサイズ: 5
   - 本番のコネクションプールサイズ: 50
   - 差異が大きいため同じバグが異なる挙動を示した

2. **Canary 観測時間の不足**
   - 5分では長期リークを検出できない
   - コネクションプール使用率のアラートが Canary 段階に設定されていなかった

3. **ロールバック手順の不備**
   - kubectl rollout undo を知らないメンバーがいたため、ロールバックに10分かかった
   - 手順書が最新でなかった

4. **アラートの閾値設定**
   - エラー率 > 5% でのアラートは適切だったが、
     コネクションプール使用率のアラートがなかった

---

## 何がうまくいったか

- アラートの発火は迅速(問題発生から約2分)
- IC 制度が機能し、役割分担がスムーズ
- ステータスページの更新が迅速(発生から5分以内)
- ロールバックの実施判断が適切なタイミングで行われた

---

## 何がうまくいかなかったか

- Canary 段階での検出ができなかった
- ロールバック手順に10分かかった(5分が目標)
- 初動の影響範囲見積もりが不正確だった
- ステージング環境の設定が本番と大きく乖離していた

---

## アクションアイテム

| # | 内容 | 担当 | 期限 | 優先度 |
|---|------|------|------|--------|
| 1 | コネクションプール使用率 > 80% のアラートを追加 | SRE チーム | 2026-04-25 | 高 |
| 2 | Canary 観測時間を5分→30分に延長 | Platform チーム | 2026-04-22 | 高 |
| 3 | ステージングの DB 設定を本番と同等にする | インフラ | 2026-05-01 | 高 |
| 4 | ロールバック手順書を更新・全員が実施できるよう訓練 | SRE チーム | 2026-04-22 | 中 |
| 5 | Canary 段階でのコネクションプール監視を追加 | SRE チーム | 2026-04-30 | 中 |
| 6 | コードレビューチェックリストに DB リソース管理を追加 | 全エンジニア | 2026-05-01 | 低 |

---

## 付録: 障害中のメトリクス

```
payment_error_rate:
  02:10: 0.02%(正常)
  02:15: 5.2% → アラート発火
  02:20: 18.4%
  02:30: 45.1%
  02:45: 89.3%(ロールバック開始)
  02:52: 45.0%(回復開始)
  03:02: 0.03%(正常回復)

connection_pool_active:
  01:45: 12%(デプロイ前)
  02:00: 31%
  02:15: 78%
  02:22: 100%(枯渇)
  02:52: 78%(回復開始)
  03:02: 15%(正常)
```

Wheel of Misfortune(不運の車輪)訓練

「Wheel of Misfortune」は Google が実施するインシデント対応訓練だ。架空のインシデントシナリオを使ってオンコール対応の意思決定を練習する。

実施方法

準備:
1. ファシリテーター(経験豊富な SRE)が過去の本物のインシデントを選ぶ
2. 参加者(訓練対象のSRE)には詳細を教えない
3. 必要なツール(Grafana, kubectl, ログ検索)へのアクセスを用意

実施(60〜90分):
1. ファシリテーターが障害の「症状」だけを説明
   例: 「午前3時にアラートが鳴った。決済エラー率が5%を超えている」

2. 参加者がリアルタイムで:
   - どのダッシュボードを見るか?
   - どのコマンドを実行するか?
   - エスカレーションするか?
   - ロールバックするか?
   を決定する

3. ファシリテーターは判断ごとにフィードバックを提供
   - 「いいね。次に何を確認する?」
   - 「実際にはそのコマンドで○○がわかった。そこからは?」

4. 終了後にデブリーフィング
   - 何がよかったか
   - 何を違うやり方にすべきだったか
   - ランブックで不明だった点

評価基準:
- 最初の仮説を立てるまでの時間
- エスカレーション判断の適切さ
- コミュニケーションの頻度・内容
- 変更前の確認行動(チェック→変更→確認のループ)

訓練シナリオ例

シナリオ A: データベースの突然の高レイテンシ
症状: API の p99 が 5秒を超えた(通常 150ms)

準備された事実(参加者には非開示):
- 別チームが本番DBの同じインスタンスで重いレポートクエリを実行中
- モニタリング: DB の CPU が 95%、wait_event_type が "Lock" 多数
- 解決策: そのクエリを kill する

評価ポイント:
- DB のメトリクスをすぐに確認できるか?
- 誰がそのクエリを実行しているか特定できるか?
- kill 前に確認するか?(安全確認)

---

シナリオ B: 特定リージョンでの認証失敗急増
症状: ap-northeast-1 のみ 403 エラーが 10% を超えている

準備された事実:
- AWS IAM ポリシーを前日更新したが、一部リージョンへの反映が遅延
- CloudTrail にポリシー変更ログあり
- 解決策: IAM ポリシーを旧バージョンに戻す

評価ポイント:
- リージョンを絞って調査できるか?
- 最近の変更(デプロイ・設定変更)を確認するか?
- CloudTrail などの監査ログを確認するか?

MTTR/MTTD 改善手法

MTTD(Mean Time To Detect)の改善:
├── アラートカバレッジの向上
│   └── ユーザー影響のある全エラーに対してアラートを設定
├── 合成監視(Synthetic Monitoring)
│   └── 定期的に本番エンドポイントを叩いて確認
├── ユーザー行動分析(UX メトリクス)
│   └── Core Web Vitals, エラー率をクライアント側から計測
└── 異常検知
    └── 正常範囲からの逸脱を自動で検知(動的閾値)

MTTI(Mean Time To Investigate)の改善:
├── ランブック(Runbook)の整備
│   └── アラートごとに対応手順を明記
├── ダッシュボードの改善
│   └── アラートからダッシュボードへのワンクリック遷移
├── 自動診断スクリプト
│   └── アラート発火時に自動で情報収集して Slack に投稿
└── 過去の類似インシデントとのリンク
    └── 「同様の症状のポストモーテムはこちら」

MTTR(Mean Time To Resolve)の改善:
├── フィーチャーフラグによる即時無効化
│   └── killswitch で問題機能を即座に無効化
├── 自動ロールバック
│   └── SLO 違反を検知したら自動でデプロイをロールバック
├── ホットフィックスプロセス
│   └── 緊急パッチのデプロイパスを短縮(レビュー1人でOK等)
└── DR(障害復旧)手順の自動化
    └── フェイルオーバーを自動または半自動で実行

指標の測定:
MTTD = 障害開始から検知まで(分)
MTTI = 検知から原因特定まで(分)
MTTR = 検知から解決まで(分)
MTBF = 障害と障害の間の平均時間(時間)

15. Kubernetes深掘り(SRE視点)

Podのライフサイクル解説

Pod のライフサイクル:

Pending → ContainerCreating → Running → (Succeeded / Failed)

各フェーズの詳細:

【Pending】
  - スケジューラーが Pod を Node に割り当てるまで
  - イメージのプル中もここに含まれる
  - 長時間 Pending の場合: リソース不足 or Taint/Affinity の問題

【ContainerCreating】
  - イメージプル中
  - コンテナランタイムが起動処理中

【Running】
  - コンテナが起動して動いている
  - ただし "Running" でも readiness probe が失敗すればトラフィックは来ない

【Succeeded】
  - 全コンテナが正常終了(exit code 0)
  - Job/CronJob の場合の正常終了

【Failed】
  - 1つ以上のコンテナが異常終了
  - OOMKilled, アプリのクラッシュ等

【Unknown】
  - Node との通信が失われた場合
  - Node が NotReady になった後に発生しやすい

コンテナの状態(container status):
  - Waiting: 起動準備中(ImagePullBackOff, CrashLoopBackOff 等)
  - Running: 動いている
  - Terminated: 終了した(exit code と reason が記録される)

Init コンテナと Lifecycle フック

apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-example
spec:
  # init コンテナ: メインコンテナ前に実行
  initContainers:
    - name: wait-for-db
      image: busybox:1.36
      command:
        - sh
        - -c
        - |
          until nc -z postgres-service 5432; do
            echo "Waiting for PostgreSQL..."
            sleep 2
          done
          echo "PostgreSQL is ready!"

    - name: db-migration
      image: myapp:latest
      command: ["python", "manage.py", "migrate"]
      env:
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: db-secrets
              key: url

  containers:
    - name: app
      image: myapp:latest
      ports:
        - containerPort: 8080

      # Lifecycle フック
      lifecycle:
        # コンテナ起動直後に実行(非同期)
        postStart:
          exec:
            command:
              - sh
              - -c
              - "echo 'Container started' >> /var/log/lifecycle.log"

        # コンテナ停止前に実行(ここで graceful shutdown の猶予を作る)
        preStop:
          exec:
            command:
              - sh
              - -c
              - |
                # 新規リクエストを受け付けないように通知
                curl -X POST http://localhost:8080/drain
                # リクエスト処理完了を待つ
                sleep 15

      # Probe(ヘルスチェック)
      # startupProbe: 起動完了を確認(失敗すると restart)
      startupProbe:
        httpGet:
          path: /health/startup
          port: 8080
        failureThreshold: 30    # 30 × 10s = 最大300秒待つ
        periodSeconds: 10

      # livenessProbe: アプリが生きているか(失敗すると restart)
      livenessProbe:
        httpGet:
          path: /health/live
          port: 8080
        initialDelaySeconds: 30
        periodSeconds: 10
        failureThreshold: 3
        timeoutSeconds: 5

      # readinessProbe: トラフィックを受け入れられるか(失敗すると Service から外す)
      readinessProbe:
        httpGet:
          path: /health/ready
          port: 8080
        initialDelaySeconds: 5
        periodSeconds: 5
        failureThreshold: 3
        timeoutSeconds: 3

      # terminationMessagePath: 終了理由を記録するファイル
      terminationMessagePath: /dev/termination-log
      terminationMessagePolicy: FallbackToLogsOnError

  # graceful shutdown の猶予時間
  terminationGracePeriodSeconds: 60

リソースリクエスト/リミットの設定と計算

リソース設定の失敗は OOMKilled や CPU スロットリングを引き起こす最大の原因だ。

# resources の設定例
resources:
  requests:
    cpu: "500m"      # 0.5 vCPU(スケジューリングの基準)
    memory: "512Mi"  # 512 MiB(スケジューリングの基準)
  limits:
    cpu: "2000m"     # 2 vCPU(超えるとスロットリング)
    memory: "1Gi"    # 1 GiB(超えると OOMKilled)

リソース設定の考え方

requests(要求値):
  - Kubernetes のスケジューラーが Node への配置を決める基準
  - 「このくらいは確実に使う」という保証値
  - 低すぎると: 多くの Pod が同じ Node に詰め込まれてメモリ競合
  - 高すぎると: Node のリソースが無駄になる(使わない分も予約される)

limits(上限値):
  CPU:
    - 超えると: スロットリング(throttle)。コンテナは遅くなるが死なない
    - limits = requests にすると QoS が "Guaranteed"(優先的に維持)
    - CPU limits を設定しないと他の Pod を圧迫する可能性

  Memory:
    - 超えると: OOMKilled(即死)。コンテナは強制終了されて restart
    - limits >= requests である必要がある
    - 実際のピーク使用量 + 20% のマージンを設定する

QoS クラス(優先度):
  Guaranteed: requests == limits(両方設定済み)
    → OOM 発生時に最後まで残る

  Burstable: requests < limits
    → 中間的な優先度

  BestEffort: requests も limits も未設定
    → OOM 発生時に最初に強制終了される

適切なリソース値の計算方法

#!/usr/bin/env python3
"""
Kubernetes リソース推奨値計算スクリプト
Prometheus から実際の使用量を取得して推奨値を計算する
"""

import requests
import statistics

PROMETHEUS_URL = "http://prometheus:9090"
NAMESPACE = "production"
APP_LABEL = "payment-api"
ANALYSIS_DAYS = 14  # 過去14日間のデータで分析

def query_range(query: str, days: int) -> list[float]:
    """Prometheus range query を実行して値のリストを返す"""
    end = "now"
    start = f"now-{days}d"
    step = "1h"  # 1時間ごとのサンプル

    resp = requests.get(
        f"{PROMETHEUS_URL}/api/v1/query_range",
        params={
            "query": query,
            "start": start,
            "end": end,
            "step": step,
        },
        timeout=30,
    )
    resp.raise_for_status()

    data = resp.json()
    values = []
    for result in data["data"]["result"]:
        for _, value in result["values"]:
            try:
                values.append(float(value))
            except (ValueError, TypeError):
                pass

    return values

def calculate_recommendations(namespace: str, app: str) -> dict:
    """リソース推奨値を計算"""

    # CPU 使用量(millicores)
    cpu_query = f"""
        sum by (pod) (
            rate(container_cpu_usage_seconds_total{{
                namespace="{namespace}",
                pod=~"{app}-.*",
                container="{app}"
            }}[5m])
        ) * 1000
    """

    # Memory 使用量(MiB)
    mem_query = f"""
        sum by (pod) (
            container_memory_working_set_bytes{{
                namespace="{namespace}",
                pod=~"{app}-.*",
                container="{app}"
            }}
        ) / 1048576
    """

    cpu_values = query_range(cpu_query, ANALYSIS_DAYS)
    mem_values = query_range(mem_query, ANALYSIS_DAYS)

    if not cpu_values or not mem_values:
        return {"error": "データが不足しています"}

    # 統計計算
    cpu_p50 = statistics.median(cpu_values)
    cpu_p95 = sorted(cpu_values)[int(len(cpu_values) * 0.95)]
    cpu_p99 = sorted(cpu_values)[int(len(cpu_values) * 0.99)]
    cpu_max = max(cpu_values)

    mem_p50 = statistics.median(mem_values)
    mem_p95 = sorted(mem_values)[int(len(mem_values) * 0.95)]
    mem_p99 = sorted(mem_values)[int(len(mem_values) * 0.99)]
    mem_max = max(mem_values)

    # 推奨値の計算
    # CPU request: p50 + 20% バッファ
    cpu_request = int(cpu_p50 * 1.2)
    # CPU limit: p99 + 50% バッファ(スロットリング余裕)
    cpu_limit = int(cpu_p99 * 1.5)

    # Memory request: p95 + 10% バッファ
    mem_request = int(mem_p95 * 1.1)
    # Memory limit: max + 20% バッファ(OOMKilled 防止)
    mem_limit = int(mem_max * 1.2)

    return {
        "app": app,
        "namespace": namespace,
        "analysis_days": ANALYSIS_DAYS,
        "cpu_stats_millicores": {
            "p50": round(cpu_p50, 1),
            "p95": round(cpu_p95, 1),
            "p99": round(cpu_p99, 1),
            "max": round(cpu_max, 1),
        },
        "memory_stats_mib": {
            "p50": round(mem_p50, 1),
            "p95": round(mem_p95, 1),
            "p99": round(mem_p99, 1),
            "max": round(mem_max, 1),
        },
        "recommendations": {
            "resources": {
                "requests": {
                    "cpu": f"{cpu_request}m",
                    "memory": f"{mem_request}Mi",
                },
                "limits": {
                    "cpu": f"{cpu_limit}m",
                    "memory": f"{mem_limit}Mi",
                }
            }
        }
    }

if __name__ == "__main__":
    import json
    result = calculate_recommendations(NAMESPACE, APP_LABEL)
    print(json.dumps(result, indent=2, ensure_ascii=False))

HPA / VPA / KEDA の設定

HPA(Horizontal Pod Autoscaler)

# CPU/メモリベースの HPA
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: payment-api-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: payment-api

  minReplicas: 3    # 最小レプリカ数(可用性のために最低3つ)
  maxReplicas: 50   # 最大レプリカ数(コスト上限)

  metrics:
    # CPU 使用率(requests に対する割合)
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70  # 70% を超えたらスケールアウト

    # メモリ使用率
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80

    # カスタムメトリクス(Prometheus アダプター経由)
    - type: Pods
      pods:
        metric:
          name: http_requests_per_second
        target:
          type: AverageValue
          averageValue: "100"     # Pod あたり100 RPS でスケールアウト

  # スケーリング挙動の制御
  behavior:
    scaleOut:
      stabilizationWindowSeconds: 60    # 60秒安定してからスケールアウト
      policies:
        - type: Percent
          value: 100                    # 一度に最大100%増加
          periodSeconds: 60
        - type: Pods
          value: 5                      # または最大5Pod ずつ
          periodSeconds: 60
      selectPolicy: Max

    scaleIn:
      stabilizationWindowSeconds: 300   # 5分安定してからスケールイン
      policies:
        - type: Percent
          value: 10                     # 一度に最大10%削減(慎重に)
          periodSeconds: 120

KEDA(Kubernetes Event-Driven Autoscaling)

# メッセージキューの深さに応じてスケーリング
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: queue-processor-scaler
  namespace: production
spec:
  scaleTargetRef:
    name: queue-processor

  minReplicaCount: 0    # キューが空の時は0にスケールイン
  maxReplicaCount: 100  # 最大100ポッド

  triggers:
    # SQS キューの深さでスケール
    - type: aws-sqs-queue
      metadata:
        queueURL: "https://sqs.ap-northeast-1.amazonaws.com/123456789/my-queue"
        queueLength: "5"    # Pod あたり5メッセージでスケール
        awsRegion: "ap-northeast-1"

    # RabbitMQ キューの深さでスケール(例)
    - type: rabbitmq
      metadata:
        host: "amqp://rabbitmq:5672"
        queueName: "task-queue"
        queueLength: "10"

  # Prometheus メトリクスでスケール(カスタム)
  - type: prometheus
    metadata:
      serverAddress: "http://prometheus:9090"
      metricName: "pending_jobs_total"
      query: "sum(pending_jobs_total{namespace='production'})"
      threshold: "100"    # 100件以上でスケールアウト

Kubernetesトラブルシューティングガイド

問題別デバッグフロー

# ==== CrashLoopBackOff の調査 ====

# 1. Pod の状態確認
kubectl get pod <pod-name> -n <namespace> -o wide
kubectl describe pod <pod-name> -n <namespace>
# → Events セクションを必ず確認

# 2. 現在と過去のログを確認
kubectl logs <pod-name> -n <namespace>                # 現在のログ
kubectl logs <pod-name> -n <namespace> --previous     # 前回クラッシュのログ

# 3. Exit Code の確認
kubectl describe pod <pod-name> | grep -A5 "Last State"
# Exit Code 1: アプリのエラー
# Exit Code 137: OOMKilled(メモリ不足)
# Exit Code 139: Segmentation fault
# Exit Code 143: 正常終了(SIGTERM)

# 4. コンテナにシェルで入る(起動直後に調査)
# 起動直後に死ぬ場合は sleep で時間を稼ぐ
kubectl debug <pod-name> -it --image=busybox -- /bin/sh

# 5. よくある原因と対処
# 原因: 設定ファイルが見つからない → ConfigMap/Secret のマウントを確認
# 原因: データベースに接続できない → initContainer で待機を追加
# 原因: メモリ不足 → limits を引き上げる
# 原因: コマンドが間違っている → image の CMD/ENTRYPOINT を確認

# ==== OOMKilled の調査 ====

# 1. メモリ使用量の確認
kubectl top pods -n <namespace>

# 2. 現在の limits 確認
kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.spec.containers[*].resources}'

# 3. メモリの実際の使用パターン(Prometheus)
# container_memory_working_set_bytes が limits に近いか確認

# 4. メモリリークの調査
# アプリのヒープダンプ、プロファイリングを実施
# Java: jmap -dump:format=b,file=heap.hprof <PID>
# Python: memory_profiler, tracemalloc
# Go: pprof /debug/pprof/heap

# ==== Evicted の調査 ====

# 1. Evicted Pod の確認
kubectl get pods -A --field-selector=status.phase=Failed \
  -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,REASON:.status.reason \
  | grep Evicted

# 2. Node のリソース状況確認
kubectl describe node <node-name> | grep -A10 "Conditions:"
kubectl describe node <node-name> | grep -A20 "Allocated resources:"

# 3. Eviction の原因ポリシー確認
# memory.available < 100Mi のときに Eviction が発生する
# /etc/kubernetes/manifests/kube-scheduler.yaml の eviction threshold 確認

# 4. 対策
# - requests を適切に設定(too low だと Node に詰め込まれる)
# - PodDisruptionBudget を設定
# - Node のリソースを増やす
# - Pod Priority を適切に設定

# ==== ImagePullBackOff の調査 ====
kubectl describe pod <pod-name> | grep -A5 "Warning"
# → "Failed to pull image" のメッセージを確認

# イメージが存在するか確認
docker pull <image-name>:<tag>

# Secret が正しく設定されているか
kubectl get secret regcred -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d

# ==== Pod が Pending のまま ====

# 1. スケジュール失敗の原因確認
kubectl describe pod <pod-name> | grep "Events:" -A20
# "Insufficient cpu" → Node の CPU が足りない
# "Insufficient memory" → Node のメモリが足りない
# "node(s) had taint" → Taint に合う Toleration がない
# "node(s) didn't match Pod's node affinity" → Affinity 設定を確認

# 2. Node のリソース確認
kubectl describe nodes | grep -A5 "Allocated resources"

# 3. PVC が Pending の場合
kubectl get pvc -n <namespace>
kubectl describe pvc <pvc-name>
# StorageClass が存在するか確認
kubectl get storageclass

# ==== Service に疎通できない ====

# 1. Service の定義確認
kubectl get svc <service-name> -o yaml

# 2. Endpoints が設定されているか
kubectl get endpoints <service-name>
# Endpoints が空の場合は selector と Pod のラベルが一致していない

# 3. Pod のラベル確認
kubectl get pods -l app=<app-name> -n <namespace>

# 4. Service への疎通テスト(一時的な Pod を作成)
kubectl run test --rm -it --image=nicolaka/netshoot -- bash
# Pod 内から:
curl http://<service-name>.<namespace>.svc.cluster.local:<port>/health
nslookup <service-name>.<namespace>.svc.cluster.local

# 5. NetworkPolicy で遮断されていないか確認
kubectl get networkpolicy -n <namespace>

Kubernetes ネットワーキング深掘り

Kubernetes ネットワーキングの全体像:

[外部クライアント]
     │
     ▼
[DNS / グローバルLB]
     │
     ▼
[Ingress Controller(nginx / AWS ALB / Istio)]
     │ HTTP(S) ルーティング
     ▼
[Service(ClusterIP / NodePort / LoadBalancer)]
     │ kube-proxy による iptables/IPVS
     ▼
[Pod(コンテナ)]
     │ CNI プラグイン(Calico / Flannel / Cilium)
     ▼
[Node のネットワーク]

CNI プラグインの比較

CNI 特徴 推奨用途
Cilium eBPF ベース。高性能・高機能な NetworkPolicy。Hubble で可視化 本番環境・セキュリティ重視
Calico BGP ベース。成熟・安定。大規模環境で実績あり 大規模本番環境
Flannel シンプル・軽量。機能は少ない 開発・テスト環境
Weave シンプルなメッシュネットワーク 小規模環境

NetworkPolicy の設定

# 特定の Pod からしかアクセスを許可しない
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: payment-api-netpol
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: payment-api     # このポリシーを適用する Pod

  policyTypes:
    - Ingress
    - Egress

  ingress:
    # api-gateway からの HTTP のみ許可
    - from:
        - podSelector:
            matchLabels:
              app: api-gateway
        - namespaceSelector:
            matchLabels:
              name: production
      ports:
        - protocol: TCP
          port: 8080

    # Prometheus のスクレイプを許可
    - from:
        - namespaceSelector:
            matchLabels:
              name: monitoring
        - podSelector:
            matchLabels:
              app.kubernetes.io/name: prometheus
      ports:
        - protocol: TCP
          port: 9090

  egress:
    # PostgreSQL への接続を許可
    - to:
        - podSelector:
            matchLabels:
              app: postgresql
      ports:
        - protocol: TCP
          port: 5432

    # Redis への接続を許可
    - to:
        - podSelector:
            matchLabels:
              app: redis
      ports:
        - protocol: TCP
          port: 6379

    # DNS を許可(必須)
    - ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

etcdの運用

etcd は Kubernetes の「脳」だ。クラスターの全状態(Pod, Service, ConfigMap 等の定義)がここに保存されている。

# ==== etcd の基本操作 ====

# etcd のヘルスチェック
ETCDCTL_API=3 etcdctl \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/peer.crt \
  --key=/etc/kubernetes/pki/etcd/peer.key \
  endpoint health

# クラスターメンバーの確認
ETCDCTL_API=3 etcdctl \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/peer.crt \
  --key=/etc/kubernetes/pki/etcd/peer.key \
  member list -w table

# ディスク使用量の確認(etcd はデフォルト 2GB まで)
ETCDCTL_API=3 etcdctl endpoint status --write-out=table

# ==== etcd のバックアップ ====
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-$(date +%Y%m%d-%H%M%S).db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

# バックアップの確認
ETCDCTL_API=3 etcdctl snapshot status /backup/etcd-YYYYMMDD.db -w table

# ==== etcd の復元 ====
# ※ 全 Control Plane ノードで実施が必要
ETCDCTL_API=3 etcdctl snapshot restore /backup/etcd-YYYYMMDD.db \
  --name master-1 \
  --initial-cluster master-1=https://10.0.0.1:2380 \
  --initial-cluster-token etcd-cluster-1 \
  --initial-advertise-peer-urls https://10.0.0.1:2380 \
  --data-dir /var/lib/etcd-restored

# ==== etcd のデフラグ(断片化の解消)====
# 定期的に実施して領域を解放する
ETCDCTL_API=3 etcdctl defrag --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/peer.crt \
  --key=/etc/kubernetes/pki/etcd/peer.key

# ==== etcd の monitoring ====
# 重要なメトリクス:
# etcd_disk_wal_fsync_duration_seconds_bucket → WAL 書き込みレイテンシ
# etcd_disk_backend_commit_duration_seconds_bucket → DB コミットレイテンシ
# etcd_server_proposals_failed_total → 提案失敗数(リーダー選挙の問題)
# etcd_network_peer_round_trip_time_seconds → ピア間の RTT

Kubernetes アップグレード手順

# ==== Kubernetes バージョンアップグレード(kubeadm の場合) ====
# 必ず一バージョンずつアップグレード(例: 1.28 → 1.29 → 1.30)

# 前提チェック
kubectl get nodes
kubectl get pods -A | grep -v Running | grep -v Completed

# ==== Step 1: Control Plane のアップグレード ====

# 1. kubeadm をアップグレード
apt-get update && apt-get install -y kubeadm=1.29.0-00
kubeadm version

# 2. アップグレード計画を確認
kubeadm upgrade plan

# 3. アップグレードを実施
kubeadm upgrade apply v1.29.0

# 4. kubelet と kubectl をアップグレード
apt-get update && apt-get install -y \
  kubelet=1.29.0-00 \
  kubectl=1.29.0-00
systemctl daemon-reload
systemctl restart kubelet

# ==== Step 2: Worker Node のアップグレード(1台ずつ実施) ====

# 対象 Node を drain(Pod を退去させる)
kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data

# kubeadm, kubelet, kubectl をアップグレード
# (対象 Node にSSH して実施)
ssh node-1
apt-get update && apt-get install -y kubeadm=1.29.0-00
kubeadm upgrade node

apt-get install -y kubelet=1.29.0-00 kubectl=1.29.0-00
systemctl daemon-reload
systemctl restart kubelet
exit

# Node をスケジュール可能に戻す
kubectl uncordon node-1

# Node の状態確認
kubectl get nodes

# 次の Node に移る前に5〜10分待つ(Pod の安定を確認)
sleep 300
kubectl get pods -A | grep -v Running | grep -v Completed

16. サービスメッシュ(Istio / Linkerd)

サービスメッシュとは何か

マイクロサービスが増えると、サービス間の通信管理(認証・暗号化・観察・トラフィック制御)が複雑になる。サービスメッシュはこれらを各サービスに実装させるのではなく、サイドカープロキシに一括して委託する仕組みだ。

サービスメッシュなし:
  ServiceA → ServiceB (セキュリティ・再試行・観察をアプリ内で実装)
  ServiceB → ServiceC (同じコードを各サービスで重複実装)

サービスメッシュあり:
  ServiceA → [Envoy Proxy] → [Envoy Proxy] → ServiceB
              ↑ サイドカー         ↑ サイドカー
              mTLS, retry, trace, circuit breaker を自動で処理

主なサービスメッシュの比較:

項目 Istio Linkerd
プロキシ Envoy(C++、高機能) linkerd2-proxy(Rust、軽量)
学習コスト 高い 低い
機能の豊富さ 非常に豊富 必要十分
パフォーマンス レイテンシ増加 ~5ms レイテンシ増加 ~0.2ms
CNCF ステータス Graduated Graduated
推奨ユースケース 複雑なトラフィック制御が必要 シンプルに始めたい場合

Istioの主要機能

インストール

# Istio のインストール(istioctl を使用)
curl -L https://istio.io/downloadIstio | sh -
export PATH=$HOME/istio-*/bin:$PATH

# クラスターへのインストール(本番プロファイル)
istioctl install --set profile=production -y

# namespace に自動サイドカー注入を有効化
kubectl label namespace production istio-injection=enabled

# インストール確認
kubectl get pods -n istio-system
istioctl verify-install

Istioの設定例(VirtualService・DestinationRule)

# ==== DestinationRule: バックエンドポリシーの定義 ====
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: payment-api
  namespace: production
spec:
  host: payment-api.production.svc.cluster.local

  # トラフィックポリシー(全バージョン共通)
  trafficPolicy:
    # コネクションプールの設定
    connectionPool:
      tcp:
        maxConnections: 1000
        connectTimeout: 30ms
        tcpKeepalive:
          time: 7200s
          interval: 75s
      http:
        http1MaxPendingRequests: 100
        http2MaxRequests: 1000
        maxRequestsPerConnection: 10

    # サーキットブレーカー設定
    outlierDetection:
      consecutive5xxErrors: 5        # 5回連続エラーでサーキットオープン
      interval: 10s                  # 10秒ごとにチェック
      baseEjectionTime: 30s          # 最小30秒間エジェクト
      maxEjectionPercent: 10         # 最大10%のホストをエジェクト
      minHealthPercent: 50           # 50%以上健全なホストが必要

    # mTLS 設定(相互TLS)
    tls:
      mode: ISTIO_MUTUAL              # Istio が自動でmTLSを管理

  # サブセット(バージョン分け)
  subsets:
    - name: v1
      labels:
        version: "v1"
      trafficPolicy:
        loadBalancer:
          simple: ROUND_ROBIN

    - name: v2
      labels:
        version: "v2"
      trafficPolicy:
        loadBalancer:
          simple: LEAST_CONN         # 最小接続数を選ぶ(v2は新機能)

---

# ==== VirtualService: トラフィックルーティングの定義 ====
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: payment-api
  namespace: production
spec:
  hosts:
    - payment-api.production.svc.cluster.local
    - payment.example.com              # 外部ホスト名

  gateways:
    - istio-system/production-gateway  # 外部からのトラフィック
    - mesh                             # 内部サービス間のトラフィック

  http:
    # テスト用: 特定ヘッダーがある場合は v2 へ
    - match:
        - headers:
            x-canary-user:
              exact: "true"
      route:
        - destination:
            host: payment-api.production.svc.cluster.local
            subset: v2
          weight: 100

    # デフォルト: 95% を v1、5% を v2(カナリアリリース)
    - route:
        - destination:
            host: payment-api.production.svc.cluster.local
            subset: v1
          weight: 95
        - destination:
            host: payment-api.production.svc.cluster.local
            subset: v2
          weight: 5

      # タイムアウト設定
      timeout: 5s

      # リトライ設定
      retries:
        attempts: 3
        perTryTimeout: 2s
        retryOn: "5xx,reset,connect-failure,retriable-4xx"

      # フォールト注入(カオスエンジニアリング用)
      # fault:
      #   delay:
      #     percentage:
      #       value: 5   # 5%のリクエストに遅延を注入
      #     fixedDelay: 2s
      #   abort:
      #     percentage:
      #       value: 1   # 1%のリクエストに503を注入
      #     httpStatus: 503

カナリアリリースをIstioで実装

# ==== 段階的なカナリアリリースの実施手順 ====

# 前提: payment-api v1 が稼働中
kubectl get pods -n production -l app=payment-api

# Step 1: v2 をデプロイ(ただしトラフィックはまだ来ない)
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-api-v2
  namespace: production
spec:
  replicas: 2
  selector:
    matchLabels:
      app: payment-api
      version: v2
  template:
    metadata:
      labels:
        app: payment-api
        version: v2
    spec:
      containers:
        - name: payment-api
          image: payment-api:v2.0.0
          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"
EOF

# Step 2: 5% のトラフィックを v2 に
# (上記 VirtualService で weight: 5 に設定済み)

# Step 3: メトリクスを監視(数分待つ)
# v2 のエラー率・レイテンシを確認
watch -n 5 'kubectl exec -n istio-system deploy/prometheus \
  -- curl -s "http://localhost:9090/api/v1/query?query=\
  rate(istio_requests_total{destination_service_name=\"payment-api\",\
  destination_version=\"v2\",response_code!~\"5..\"}[5m])\
  /rate(istio_requests_total{destination_service_name=\"payment-api\",\
  destination_version=\"v2\"}[5m])"'

# Step 4: メトリクスが正常なら traffic を増加(5% → 20% → 50% → 100%)
# VirtualService の weight を更新
kubectl patch virtualservice payment-api -n production \
  --type='json' \
  -p='[{"op":"replace","path":"/spec/http/1/route/0/weight","value":80},
       {"op":"replace","path":"/spec/http/1/route/1/weight","value":20}]'

# Step 5: 問題があればロールバック(100% を v1 に戻す)
kubectl patch virtualservice payment-api -n production \
  --type='json' \
  -p='[{"op":"replace","path":"/spec/http/1/route/0/weight","value":100},
       {"op":"replace","path":"/spec/http/1/route/1/weight","value":0}]'

17. データベース信頼性エンジニアリング

データベースのSREにおける役割

データベースはシステムの「状態」を持つ最も重要なコンポーネントだ。SRE にとってデータベースの信頼性設計は特に重要な責務となる。

データベースの信頼性を脅かす主なリスク:
├── 単一障害点(Single Point of Failure)
│   └── マスター DB が1台のみ → フェイルオーバー設計が必要
├── データ損失
│   └── バックアップなし → 取り返しのつかない損失
├── パフォーマンス劣化
│   └── スロークエリ、コネクション枯渇 → 連鎖的な障害
└── スキーマ変更リスク
    └── ALTER TABLE が数時間かかる → ダウンタイムの原因

PostgreSQL の運用

レプリケーション設定

# ==== postgresql.conf(マスター側)====

# WAL(Write-Ahead Logging)設定
wal_level = replica                    # レプリケーションに必要
max_wal_senders = 10                   # スタンバイの最大接続数
wal_keep_size = 1GB                    # WAL ファイルの保持量
wal_sender_timeout = 60s

# アーカイブ設定(PITR のために必須)
archive_mode = on
archive_command = 'aws s3 cp %p s3://my-pg-archive/pg_wal/%f'
archive_timeout = 300                  # 5分以内に必ずアーカイブ

# パフォーマンス設定
shared_buffers = 8GB                   # RAM の 25% 程度
effective_cache_size = 24GB            # RAM の 75% 程度
work_mem = 64MB                        # ソート・ハッシュ操作のメモリ
maintenance_work_mem = 2GB             # VACUUM, CREATE INDEX 用
max_connections = 200                  # コネクション数(+接続プール必須)
# ==== pg_hba.conf(マスター側)====
# レプリカからの接続を許可
host    replication     replicator      10.0.0.0/24     scram-sha-256
# ==== postgresql.conf(スタンバイ側)====
# PostgreSQL 12以降は recovery.conf が不要になった
hot_standby = on                       # スタンバイでのRead許可
hot_standby_feedback = on              # スタンバイでの長いクエリを考慮

# primary_conninfo(PostgreSQL 12以降は postgresql.conf に書く)
primary_conninfo = 'host=10.0.0.1 port=5432 user=replicator password=xxx application_name=standby1'

# recovery_target_timeline = 'latest'  # 常に最新タイムラインを追う

PITR(Point-in-Time Recovery)の実施

# ==== PITR(任意の時点への復元)====

# 状況: 午後2時15分に誤って大量のデータを削除してしまった
# 目標: 午後2時10分の状態に復元する

# Step 1: ベースバックアップを特定
# (定期的に pg_basebackup を実行していることが前提)
aws s3 ls s3://my-pg-backup/ --recursive | grep basebackup | sort | tail -5

# Step 2: PostgreSQL を停止
systemctl stop postgresql

# Step 3: データディレクトリを退避
mv /var/lib/postgresql/15/main /var/lib/postgresql/15/main.broken

# Step 4: ベースバックアップを展開
mkdir /var/lib/postgresql/15/main
aws s3 cp s3://my-pg-backup/basebackup-20260418-01:00.tar.gz - | \
  tar -xz -C /var/lib/postgresql/15/main

# Step 5: recovery.conf を作成(PostgreSQL 12未満)
# または postgresql.conf に追加(PostgreSQL 12以降)
cat >> /var/lib/postgresql/15/main/postgresql.conf << 'EOF'
restore_command = 'aws s3 cp s3://my-pg-archive/pg_wal/%f %p'
recovery_target_time = '2026-04-18 14:10:00 JST'
recovery_target_action = 'promote'
EOF

# signal ファイルを作成(PostgreSQL 12以降でのリカバリーモード)
touch /var/lib/postgresql/15/main/recovery.signal

# Step 6: PostgreSQL を起動(リカバリーが自動で開始される)
systemctl start postgresql

# Step 7: リカバリーのログを確認
tail -f /var/log/postgresql/postgresql-15-main.log
# "recovery stopping before commit" が出たら復元ポイント到達

# Step 8: 復元されたデータを確認してからプロモート
psql -U postgres -c "SELECT count(*) FROM important_table;"
psql -U postgres -c "SELECT pg_promote();"

# Step 9: アプリケーションの接続先を切り替え

データベース接続プール(PgBouncer)

# pgbouncer.ini
[databases]
# 本番DBへのプールを設定
production = host=10.0.0.1 port=5432 dbname=myapp

[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432

# 認証設定
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt

# プールモード
pool_mode = transaction         # transaction モードが推奨(connection モードより効率的)

# 接続数設定
max_client_conn = 1000          # クライアント(アプリ)の最大接続数
default_pool_size = 25          # DB への実際の接続数
min_pool_size = 5               # 最小プール数
reserve_pool_size = 5           # 緊急用の予備接続数
reserve_pool_timeout = 5.0      # 予備プールを使う前の待ち時間

# タイムアウト
server_lifetime = 3600          # DB 接続の最大生存時間(秒)
server_idle_timeout = 600       # アイドル接続のタイムアウト
client_idle_timeout = 0         # クライアントのアイドルタイムアウト(0=無効)
query_timeout = 0               # クエリタイムアウト(0=無効)
query_wait_timeout = 120        # 接続待ち最大時間

# ログ設定
log_connections = 0             # 接続ログ(本番では0で無効化)
log_disconnections = 0
log_pooler_errors = 1

[users]
# ユーザーごとのプールサイズ上書き
myapp_user = pool_size=20

Redisの信頼性設計

Redis Sentinel(自動フェイルオーバー)

Redis Sentinel 構成:

[Client] → [Sentinel-1] ─── [Redis Master]
            [Sentinel-2] ─── [Redis Replica-1]
            [Sentinel-3] ─── [Redis Replica-2]

Sentinel の役割:
- Master の監視(is-master-down-by-addr)
- クォーラム(過半数)で障害を判断
- 自動フェイルオーバー(Replica を Master に昇格)
- Client への新しい Master アドレスの通知
# sentinel.conf の設定
sentinel monitor mymaster 10.0.0.1 6379 2  # クォーラム数: 2(3台中2台)
sentinel down-after-milliseconds mymaster 5000   # 5秒応答なし → 障害とみなす
sentinel failover-timeout mymaster 60000        # フェイルオーバーのタイムアウト
sentinel parallel-syncs mymaster 1              # 一度に同期するレプリカ数

Redis Cluster(シャーディング)

Redis Cluster 構成(6台構成):

[Client]
  │
  ├── Shard 1: Master-1 (slot 0-5460)     + Replica-4
  ├── Shard 2: Master-2 (slot 5461-10922) + Replica-5
  └── Shard 3: Master-3 (slot 10923-16383)+ Replica-6

特徴:
- 16384 スロットを分散
- 任意のノードに接続してもリダイレクト(MOVED, ASK)
- Quorum: 過半数のマスターが同意でフェイルオーバー
# Redis Cluster の作成
redis-cli --cluster create \
  10.0.0.1:7000 10.0.0.2:7000 10.0.0.3:7000 \  # Masters
  10.0.0.4:7000 10.0.0.5:7000 10.0.0.6:7000 \  # Replicas
  --cluster-replicas 1

# クラスター状態確認
redis-cli -h 10.0.0.1 -p 7000 cluster info
redis-cli -h 10.0.0.1 -p 7000 cluster nodes | column -t

# スロット分布の確認
redis-cli --cluster check 10.0.0.1:7000

データベースのシャーディング戦略

シャーディングとは:
データを複数の DB に水平分散する手法

【シャーディングキーの選択基準】
1. カーディナリティが高い(ユーザーID, 組織ID等)
2. 均等に分散する(ホットスポットにならない)
3. クエリの大部分で絞り込み可能
4. 変更されない(後から変更は困難)

【シャーディング戦略】

1. ハッシュシャーディング:
   shard = hash(user_id) % num_shards
   → 均等分散が保証される
   → 範囲クエリが非効率(全シャードを見る必要がある)

2. レンジシャーディング:
   shard 1: user_id 1-100万
   shard 2: user_id 100万-200万
   → 範囲クエリが効率的
   → ホットスポットが発生しやすい(最新ユーザーが特定シャードに集中)

3. ディレクトリシャーディング:
   ルーティングテーブル: user_id → shard_id をキャッシュ
   → 柔軟なリシャーディングが可能
   → ルーティングテーブルがSPOFになる

【アプリケーションレベルシャーディング例(Python)】
import hashlib
import redis

class ShardedRedis:
    """Redis シャーディングの実装例"""

    def __init__(self, shard_configs: list[dict]):
        """
        shard_configs = [
            {"host": "10.0.0.1", "port": 6379},
            {"host": "10.0.0.2", "port": 6379},
            {"host": "10.0.0.3", "port": 6379},
        ]
        """
        self.shards = [
            redis.Redis(**config, decode_responses=True)
            for config in shard_configs
        ]
        self.num_shards = len(self.shards)

    def _get_shard(self, key: str) -> redis.Redis:
        """キーのハッシュ値でシャードを選択"""
        hash_value = int(hashlib.md5(key.encode()).hexdigest(), 16)
        shard_index = hash_value % self.num_shards
        return self.shards[shard_index]

    def get(self, key: str):
        return self._get_shard(key).get(key)

    def set(self, key: str, value: str, **kwargs):
        return self._get_shard(key).set(key, value, **kwargs)

    def delete(self, key: str):
        return self._get_shard(key).delete(key)

# 使用例
sharded_redis = ShardedRedis([
    {"host": "10.0.0.1", "port": 6379},
    {"host": "10.0.0.2", "port": 6379},
    {"host": "10.0.0.3", "port": 6379},
])

sharded_redis.set("user:12345:session", "token_abc", ex=3600)
session = sharded_redis.get("user:12345:session")

バックアップと復元のテスト

バックアップはテストされるまで存在しないと思え。

# ==== 定期バックアップスクリプト(PostgreSQL) ====
#!/bin/bash
set -euo pipefail

BACKUP_BUCKET="s3://my-postgres-backups"
DATE=$(date +%Y%m%d-%H%M%S)
DB_HOST="postgres-master"
DB_NAME="myapp"
DB_USER="backup_user"
BACKUP_FILE="/tmp/pg-backup-${DATE}.dump"

# pg_dump でバックアップ
PGPASSWORD="${DB_PASSWORD}" pg_dump \
  -h "$DB_HOST" \
  -U "$DB_USER" \
  -d "$DB_NAME" \
  -F c \                           # カスタム形式(圧縮済み)
  -Z 6 \                           # 圧縮レベル
  --no-password \
  -f "$BACKUP_FILE"

# S3 にアップロード
aws s3 cp "$BACKUP_FILE" "${BACKUP_BUCKET}/${DATE}.dump" \
  --storage-class STANDARD_IA     # 低頻度アクセスで安価に保存

# バックアップの整合性検証
pg_restore --list "$BACKUP_FILE" > /dev/null
echo "Backup verified: ${DATE}.dump"

# 古いバックアップを削除(90日以上前のものを削除)
aws s3 ls "$BACKUP_BUCKET" | \
  while read -r line; do
    createDate=$(echo "$line" | awk '{print $1}')
    if [[ $(date -d "$createDate" +%s) -lt $(date -d "90 days ago" +%s) ]]; then
      fileName=$(echo "$line" | awk '{print $4}')
      aws s3 rm "${BACKUP_BUCKET}/${fileName}"
    fi
  done

# 完了通知
rm -f "$BACKUP_FILE"
echo "Backup completed: ${DATE}"

# ==== バックアップのリストアテスト(毎月実施) ====
test_restore() {
    local backup_file="$1"
    local test_db="restore_test_${RANDOM}"

    # テスト用DBを作成
    psql -h "$DB_HOST" -U postgres -c "CREATE DATABASE $test_db;"

    # リストア
    pg_restore \
      -h "$DB_HOST" \
      -U postgres \
      -d "$test_db" \
      "$backup_file"

    # データ確認(重要テーブルのレコード数チェック)
    local user_count
    user_count=$(psql -h "$DB_HOST" -U postgres -d "$test_db" \
      -t -c "SELECT count(*) FROM users;")

    echo "Restore test: users count = $user_count"

    if [[ "$user_count" -lt 1000 ]]; then
        echo "ERROR: User count too low. Restore may have failed!"
        exit 1
    fi

    # テスト用DBを削除
    psql -h "$DB_HOST" -U postgres -c "DROP DATABASE $test_db;"
    echo "Restore test completed successfully"
}

18. セキュリティ(Security SRE)

セキュリティとSREの交点

SRE とセキュリティは互いに補完し合う領域だ。どちらも「システムを意図した通りに動かし続ける」という目標を持つ。

SRE の3大敵:
  1. バグ
  2. パフォーマンス問題
  3. セキュリティインシデント

セキュリティ×SREの合言葉:
  可用性(Availability) = セキュリティインシデントのない時間
  DDoS攻撃やデータ漏洩は最大の可用性への脅威

CIAトライアドと多層防御

セキュリティの基本概念であるCIAトライアドと多層防御はSREが押さえておくべき基礎だ。

CIAトライアド(情報セキュリティの3原則):

C - Confidentiality(機密性)
  → 認可されていない者には情報を開示しない
  → 実装: 暗号化(AES-256)、アクセス制御(RBAC)、シークレット管理(Vault)

I - Integrity(完全性)
  → データが改ざんされていないことを保証する
  → 実装: ハッシュ検証(SHA-256)、デジタル署名、監査ログ、Git コミット署名

A - Availability(可用性)
  → 正当なユーザーが必要なときにシステムを使えること
  → 実装: 冗長構成、DDoS対策、バックアップ、SLO管理

SRE は特に A(可用性)に責任を持つが、セキュリティインシデントは
C と I の侵害から A の喪失に連鎖することを理解する必要がある。
多層防御(Defense in Depth):

「単一の防御策が突破されても、次の層が守る」という設計原則。

┌────────────────────────────────────────────────────┐
│  Layer 7: 人(教育・プロセス)                      │
│  ├── セキュリティ意識向上トレーニング               │
│  └── セキュリティチャンピオン制度                   │
├────────────────────────────────────────────────────┤
│  Layer 6: 物理(データセンター・デバイス)          │
├────────────────────────────────────────────────────┤
│  Layer 5: ネットワーク(境界防御)                  │
│  ├── ファイアウォール、WAF                          │
│  └── DDoS 保護(CloudFlare / AWS Shield)          │
├────────────────────────────────────────────────────┤
│  Layer 4: アプリケーション                          │
│  ├── 入力バリデーション、認証・認可                 │
│  └── SAST/DAST(静的・動的解析)                   │
├────────────────────────────────────────────────────┤
│  Layer 3: エンドポイント                            │
│  ├── EDR(Endpoint Detection & Response)           │
│  └── コンテナイメージスキャン(Trivy/Snyk)        │
├────────────────────────────────────────────────────┤
│  Layer 2: データ                                    │
│  ├── 保存時暗号化(at-rest encryption)             │
│  └── 転送中暗号化(TLS 1.3)                       │
├────────────────────────────────────────────────────┤
│  Layer 1: 監視・検知(SOC / SIEM)                 │
│  ├── Falco(ランタイムセキュリティ)                │
│  └── AWS CloudTrail / GCP Cloud Audit Logs         │
└────────────────────────────────────────────────────┘

STRIDEによる脅威モデリング

脅威モデリングは設計段階でセキュリティ問題を特定するプロセス。STRIDEは最も広く使われるフレームワーク。

STRIDE カテゴリ:

S - Spoofing(なりすまし)
  → 攻撃者が別のユーザー/サービスになりすます
  → 対策: 強固な認証(MFA、mTLS)

T - Tampering(改ざん)
  → データ・コード・設定の不正変更
  → 対策: デジタル署名、チェックサム、監査ログ、RBAC

R - Repudiation(否認)
  → 「自分はやっていない」と行為を否定できてしまう
  → 対策: 改ざん不可能な監査ログ、デジタル署名

I - Information Disclosure(情報漏洩)
  → 機密情報が権限なき者に開示される
  → 対策: 暗号化、アクセス制御、シークレット管理

D - Denial of Service(サービス拒否)
  → システムを利用不能にする
  → 対策: レート制限、DDoS対策、スケーリング、SRE の守備範囲

E - Elevation of Privilege(権限昇格)
  → 低権限のユーザーが高権限を取得
  → 対策: 最小権限の原則、セキュリティパッチ管理

STRIDE実践例:ログインAPIへの脅威分析

分析対象: POST /api/v1/login (ID + パスワードで認証)

┌──────────┬─────────────────────────────────┬────────────────────────────────┐
│ STRIDE   │ 脅威シナリオ                    │ 対策                           │
├──────────┼─────────────────────────────────┼────────────────────────────────┤
│Spoofing  │ 漏洩したパスワードでログイン     │ MFA の強制、Passkey 対応       │
│          │ フィッシングで認証情報を窃取     │ FIDO2、パスワードレス認証      │
├──────────┼─────────────────────────────────┼────────────────────────────────┤
│Tampering │ 通信経路でリクエストを改ざん     │ TLS 1.3 強制、HSTS             │
│          │ ログイン応答を中間者が改ざん     │ 証明書ピンニング               │
├──────────┼─────────────────────────────────┼────────────────────────────────┤
│Repudiati.│ 「ログインしていない」と主張     │ 不変な監査ログ(IP、UA、時刻) │
│          │                                 │ CloudTrail / ログアーカイブ    │
├──────────┼─────────────────────────────────┼────────────────────────────────┤
│Info Disc.│ エラーメッセージでユーザーの存在 │ 汎用エラー("認証失敗")で統一   │
│          │ を確認できる                     │ パスワードと同一レスポンス時間 │
├──────────┼─────────────────────────────────┼────────────────────────────────┤
│DoS       │ ブルートフォース攻撃            │ レート制限(5回失敗でロック)  │
│          │ パスワードスプレー攻撃          │ CAPTCHA、IP ブロック           │
├──────────┼─────────────────────────────────┼────────────────────────────────┤
│Elevation │ セッショントークンの固定化攻撃  │ ログイン後にセッションID再生成 │
│          │ JWT アルゴリズム混同攻撃        │ alg フィールドの検証を強制     │
└──────────┴─────────────────────────────────┴────────────────────────────────┘

サプライチェーンセキュリティ(SLSA / Cosign)

現代のセキュリティはコード自体だけでなく、ソフトウェアの「製造過程」の保護が不可欠だ。

SLSA(Supply chain Levels for Software Artifacts):
ソフトウェアサプライチェーンの完全性を保証するフレームワーク

Level 0: 保証なし
  → ビルドプロセス・来歴の記録なし

Level 1: 基本的な来歴(Provenance)
  → ビルドシステムがどのソースから何を生成したか記録
  → GitHub Actions の SLSA Generator で自動生成可能

Level 2: 改ざん防止
  → 来歴がサービス(e.g. GitHub) によって署名されている
  → CI/CD システムがホスティングされていること

Level 3: 強い改ざん防止
  → ビルド環境が分離・監査可能
  → 来歴の偽造が困難
  → Google、Sigstore が Level 3 対応

Level 4(廃止→ Level 3 に統合): 最高レベルの保証
# ==== Cosign による コンテナイメージ署名(Sigstore)====

# Cosign のインストール
brew install cosign

# コンテナイメージをビルド
docker build -t myapp:v1.0.0 .
docker push ghcr.io/myorg/myapp:v1.0.0

# キーレス署名(Sigstore/Fulcio を使用)
# OIDC トークンで本人確認 → Fulcio が短命な証明書を発行
# → Rekor (透明性ログ) に記録される
cosign sign ghcr.io/myorg/myapp:v1.0.0

# 署名の確認(デプロイ前に必ず検証)
cosign verify \
  --certificate-identity "https://github.com/myorg/myapp/.github/workflows/release.yml@refs/tags/v1.0.0" \
  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
  ghcr.io/myorg/myapp:v1.0.0

# SBOM(Software Bill of Materials)の生成と添付
syft ghcr.io/myorg/myapp:v1.0.0 -o spdx-json > sbom.spdx.json
cosign attach sbom --sbom sbom.spdx.json ghcr.io/myorg/myapp:v1.0.0
cosign sign --attachment sbom ghcr.io/myorg/myapp:v1.0.0
# ==== Kubernetes Admission Controller での署名検証 ====
# Policy Controller(Sigstore)を使って、署名済みイメージのみ許可

apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
  name: require-signed-images
spec:
  images:
    - glob: "ghcr.io/myorg/**"
  authorities:
    - keyless:
        url: "https://fulcio.sigstore.dev"
        identities:
          - issuer: "https://token.actions.githubusercontent.com"
            subject: "https://github.com/myorg/myapp/.github/workflows/release.yml@refs/heads/main"

Zero Trustアーキテクチャ

旧来のペリメーターセキュリティ:
  [外部] ─── ファイアウォール ─── [内部ネットワーク(信頼ゾーン)]

  問題: 一度内部に入ると自由に動き回れる(横移動が容易)

Zero Trust モデル:
  "Never trust, always verify"

  原則:
  1. すべてのアクセスを認証・認可する(場所に関係なく)
  2. 最小権限の原則を徹底
  3. 全アクセスを記録・監視
  4. すべての通信を暗号化

  実装:
  ├── ID ベースのアクセス制御(SPIFFE/SPIRE でWorkload Identity)
  ├── mTLS による通信の暗号化と認証
  ├── 継続的な認証(セッション中も定期的に検証)
  └── マイクロセグメンテーション(NetworkPolicy)

HashiCorp Vault詳細

Vault のアーキテクチャ

Vault アーキテクチャ:

[クライアント] ─→ [Vault API(HTTPS:8200)]
                          │
                    ┌─────▼─────┐
                    │  Vault    │ ← Shamir の秘密分散でシール
                    │  Core     │   (アンシール前はデータにアクセス不可)
                    └─────┬─────┘
                          │
              ┌───────────┼───────────┐
              ▼           ▼           ▼
         [Storage]   [Auth]      [Secrets]
         Backend     Methods     Engines
         (etcd/      (OIDC,      (KV, PKI,
          Raft/       Kubernetes, Database,
          DynamoDB)   AWS)        AWS)

Vault の実践設定

# ==== Vault の初期化と設定 ====

# 1. Vault の初期化(本番: Shamir の秘密分散 5/3)
vault operator init \
  -key-shares=5 \
  -key-threshold=3

# → 5つのアンシールキーと1つのルートトークンが生成される
# → アンシールキーは別々の担当者が保管する(信頼の分散)

# 2. アンシール(起動時に3つのキーが必要)
vault operator unseal <key-1>
vault operator unseal <key-2>
vault operator unseal <key-3>

# 3. Kubernetes 認証の設定
vault auth enable kubernetes

vault write auth/kubernetes/config \
  kubernetes_host="https://kubernetes.default.svc" \
  kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
  token_reviewer_jwt=@/var/run/secrets/kubernetes.io/serviceaccount/token

# 4. ポリシーの作成
vault policy write payment-api - <<EOF
# payment-api サービスが使えるシークレットのパス
path "secret/data/payment-api/*" {
  capabilities = ["read"]
}

path "database/creds/payment-api-role" {
  capabilities = ["read"]
}
EOF

# 5. Kubernetes の ServiceAccount にロールを紐付け
vault write auth/kubernetes/role/payment-api \
  bound_service_account_names=payment-api \
  bound_service_account_namespaces=production \
  policies=payment-api \
  ttl=1h

# 6. KV シークレットエンジンの設定
vault secrets enable -path=secret kv-v2

# シークレットの書き込み
vault kv put secret/payment-api/config \
  stripe_api_key="sk_live_xxxxx" \
  database_password="super-secret-password"

# 7. データベースシークレットエンジン(動的シークレット)
vault secrets enable database

vault write database/config/postgresql \
  plugin_name=postgresql-database-plugin \
  allowed_roles="payment-api-role" \
  connection_url="postgresql://vault:vault@postgres:5432/myapp" \
  username="vault" \
  password="vault"

# ロールの設定(動的認証情報を生成するロール)
vault write database/roles/payment-api-role \
  db_name=postgresql \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
  default_ttl="1h" \
  max_ttl="24h"

# 動的認証情報を取得(Vault が一時的なユーザーを生成)
vault read database/creds/payment-api-role
# → username: v-kubern-payment-XXXXX
# → password: A1b2C3d4E5f6...
# → 1時間後に自動で削除される

Kubernetes での Vault Secrets Operator

# Vault Secrets Operator を使ってシークレットを自動同期
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
  name: payment-api-secrets
  namespace: production
spec:
  vaultAuthRef: default
  mount: secret
  type: kv-v2
  path: payment-api/config

  refreshAfter: 30s    # 30秒ごとに最新値を同期

  destination:
    name: payment-api-secrets    # 生成する Kubernetes Secret の名前
    create: true
    overwrite: true

mTLS(相互TLS認証)

通常の TLS:
  クライアント → [TLS HandShake] → サーバー
  サーバーの証明書をクライアントが検証する(一方向)

mTLS:
  クライアント ← → [TLS HandShake] → サーバー
  サーバーの証明書をクライアントが検証 AND
  クライアントの証明書をサーバーが検証(双方向)

  → 「あなたが本当に payment-api サービスであること」を証明できる
  → ネットワークポリシーより強力な「誰が通信しているか」の検証

SPIFFE/SPIRE による Workload Identity

SPIFFE (Secure Production Identity Framework For Everyone):
  マイクロサービスに ID(SVID: SPIFFE Verifiable Identity Document)を付与する

  SVID の形式:
  spiffe://example.com/ns/production/sa/payment-api

  ↑ この URI が証明書の SAN(Subject Alternative Name)に埋め込まれる
  → サービス間で「どのサービスが通信してきたか」を証明できる

セキュリティインシデント対応

セキュリティインシデントの分類:

P1(Critical):
  - 本番データへの不正アクセス
  - 顧客データの漏洩
  - ランサムウェア感染
  - 重要システムの不正改ざん

P2(High):
  - 認証情報の漏洩(APIキー, パスワード)
  - DDoS 攻撃(サービスへの影響あり)
  - 不審な特権アクセス

P3(Medium):
  - 脆弱性の発見(悪用前)
  - 不審なログインの試み
  - 非本番環境への侵害

セキュリティインシデント対応のフロー:
1. 検知・トリアージ(15分以内)
   → SOC(Security Operations Center)またはオンコールに通知

2. 封じ込め(1時間以内)
   → 影響を受けたシステムのネットワーク分離
   → 侵害された認証情報の無効化

3. 調査(インシデントが続く間)
   → 攻撃ベクター・影響範囲の特定
   → フォレンジック(証跡の保全)

4. 根絶(調査完了後)
   → 攻撃者のアクセス経路を閉鎖
   → マルウェアの除去・システムの再構築

5. 復旧
   → クリーンな状態からの再構築
   → バックアップからの復元

6. 事後対応
   → セキュリティインシデントのポストモーテム
   → 再発防止策の実装
   → 必要に応じて規制当局への報告
# ==== セキュリティ調査コマンド集 ====

# 不審なプロセスを探す
ps auxf | grep -v "^\[" | sort -k3 -rn | head -20  # CPU 使用率上位
lsof -i :4444  # バックドアポートの確認

# ネットワーク接続を確認
ss -tupn | grep ESTABLISHED
netstat -an | grep -v "127.0.0.1" | grep ESTABLISHED

# 最近変更されたファイル
find /etc /bin /sbin /usr/bin /usr/sbin -newer /etc/passwd -type f 2>/dev/null
find /tmp /var/tmp -newer /etc/passwd -type f 2>/dev/null

# 認証ログの確認
grep "Failed password\|Invalid user\|authentication failure" /var/log/auth.log | \
  awk '{print $11}' | sort | uniq -c | sort -rn | head -20

# sudo 実行履歴
grep "sudo" /var/log/auth.log | tail -100

# 最近追加されたユーザー
awk -F: '$3 >= 1000 {print $1, $3, $7}' /etc/passwd

# crontab の確認(バックドアが仕掛けられることがある)
for user in $(cut -f1 -d: /etc/passwd); do
  crontab -l -u "$user" 2>/dev/null | grep -v "^#" | grep -v "^{{CONTENT}}quot;
done
cat /etc/cron* /var/spool/cron/* 2>/dev/null

# Kubernetes での不審な活動を探す
kubectl get events -A --sort-by='.lastTimestamp' | tail -50
kubectl get pods -A | grep -v "Running\|Completed\|Pending"
# 不審なイメージが使われていないか確認
kubectl get pods -A -o jsonpath='{range .items[*]}{.spec.containers[*].image}{"\n"}{end}' | \
  sort | uniq | grep -v "mycompany.com\|gcr.io/google\|k8s.gcr.io"

19. SREの組織設計

SREチームモデル

1. Centralized(集中型)

構造:
  ┌──────────────────────────────┐
  │      Centralized SRE Team   │
  │  (独立した専門チーム)         │
  └──────────────────────────────┘
    ↓ サービスを横断的にサポート
  [Product A] [Product B] [Product C]

メリット:
  ✓ SRE のベストプラクティスを組織全体に統一できる
  ✓ 専門知識を集中させてスケールできる
  ✓ 標準化されたツール・プラットフォームを提供
  ✓ 採用・評価が一元化されて質を保てる

デメリット:
  ✗ プロダクトの文脈理解が浅くなりやすい
  ✗ 各プロダクトチームと距離が生まれやすい
  ✗ 要望が多いとボトルネックになる

向いている組織:
  - SRE 文化を統一的に根付かせたい
  - 小〜中規模のエンジニア組織(SRE 5〜20名)

2. Embedded(組み込み型)

構造:
  [Product Team A] ← SRE-1 組み込み
  [Product Team B] ← SRE-2 組み込み
  [Product Team C] ← SRE-3 組み込み

メリット:
  ✓ プロダクトの文脈を深く理解できる
  ✓ 開発チームとの連携が密接
  ✓ サービス固有の信頼性改善が速い

デメリット:
  ✗ 組織横断のベストプラクティス共有が困難
  ✗ 孤立してSREとしての成長が制限される
  ✗ 開発タスクに引っ張られてSRE本来の仕事が减る

向いている組織:
  - プロダクトの複雑さが高い
  - 各プロダクトの信頼性要件が大きく異なる

3. Consulting(コンサルティング型)

構造:
  [SRE Chapter / Center of Excellence]
      ↓ 相談・支援・教育
  [Product Team A] [Product Team B] [Product Team C]
  (SRE自身はサービスを直接所有しない)

メリット:
  ✓ 少人数で多くのチームをサポートできる
  ✓ プロダクトチームの SRE 能力を高める(自立を促す)
  ✓ ベストプラクティスの普及に特化できる

デメリット:
  ✗ プロダクトチームが自律的に動く必要がある
  ✗ 重大障害時に権限がない
  ✗ 採用が難しい(高い説明能力が必要)

向いている組織:
  - エンジニア組織が成熟している
  - 大企業で SRE 人材が少ない
  - DevOps 文化が浸透している

4. Hybrid(ハイブリッド型)

最も多くの組織が採用する現実的なモデル。

構造:
  [Centralized Platform SRE]  ← 共通基盤・ツール・プラクティス
       ↓ 支援・ガバナンス
  [Product Team A + SRE-1]
  [Product Team B + SRE-2]  ← 組み込み SRE がプロダクトを担当
  [Product Team C] ← SRE なし → Platform SRE がコンサルティング

SRE導入における変革管理:Gleicherの公式

SRE の導入は技術的な問題だけでなく、組織の変革管理の問題でもある。

Gleicher の変革の公式:

  D × V × F > R

  D = Dissatisfaction(現状への不満)
      「今の運用に問題がある」という共通認識があるか?

  V = Vision(変革後のビジョン)
      「SRE を導入したらどうなるか」が具体的に描けているか?

  F = First Steps(最初の一歩)
      「明日から何を変えるか」が明確で実行可能か?

  R = Resistance(抵抗)
      変革のコスト(学習コスト・プロセス変更・文化摩擦)

  この掛け算の結果が R(抵抗)を上回って初めて変革は成功する。
  D, V, F のいずれかが 0 でも右辺は 0 になる。

SRE導入への抵抗と対処

よくある抵抗の形と対処:

抵抗①: 「SRE は大企業のもの。我々には関係ない」
  対処: 小さく始める(まず1サービスに SLO を定義するだけ)
       成功事例を早く作って社内に見せる

抵抗②: 「今でもちゃんと運用できている」(D が低い)
  対処: 障害の影響とコストを数値化する
       「先月の障害で失った売上は ○○ 万円」と可視化する
       DORA メトリクスで現状を客観的に示す

抵抗③: 「SRE エンジニアを雇う予算がない」
  対処: Consulting SRE モデルで既存エンジニアを教育する
       自動化で削減できるコストを ROI として提示する

抵抗④: 「開発が止まる」(F のコストが高く見える)
  対処: SLO がある間はリリースできることを強調する
       エラーバジェットが「開発チームのための余裕」であることを示す

変革の戦略:
1. アーリーアダプターを見つける(熱心な1チームから始める)
2. 早期に可視化する(Grafana ダッシュボードで成果を見せる)
3. 成功体験を組織に広める(社内勉強会・ブログ)
4. 政治的スポンサーを確保する(Engineering Manager 以上の支持を得る)

SRE チームの規模と構成

SRE チームのサイジング目安:

【エンジニア: SRE 比率】
  Google の推奨: 開発エンジニア 8人に SRE 1人
  現実的な範囲: 5〜15人に SRE 1人

【チーム規模別の構成例】

スタートアップ(〜50名エンジニア):
  SRE: 2〜5名
  役割分担は流動的。全員がオンコールに入る。
  Platform Engineering と SRE を兼務することが多い。

中規模(50〜200名エンジニア):
  SRE: 5〜15名
  チームリーダー 1名 + シニア 2〜3名 + ミドル 5〜8名
  オンコールローテーション: 7〜10名で回す

大企業(200名以上エンジニア):
  SRE: 20名以上
  Centralized + Embedded ハイブリッド構成
  専門チーム(Security SRE, Data SRE 等)に分化

SRE の KPI・OKR 設計

SRE OKR の例

期: 2026 Q2(4月〜6月)

Objective 1: 本番環境の信頼性を業界トップ水準に引き上げる

  KR1: SLO 達成率を 85% → 95% に向上させる
       (全サービスの SLO を 95% 以上達成できている割合)

  KR2: MTTD(平均検知時間)を 15分 → 5分に短縮

  KR3: MTTR(平均復旧時間)を 60分 → 30分に短縮

  KR4: P1 インシデント件数を 前四半期比 30% 削減

Objective 2: エンジニアのオンコール負担を持続可能な水準にする

  KR1: 1人あたり深夜アラート発火数を 月20件 → 5件以下に削減

  KR2: アクショナブルでないアラートを 全体の 40% → 10% 以下に削減

  KR3: オンコールエンジニアの主観的疲労スコア(1〜5)を 3.2 → 4.0 以上

Objective 3: SRE の自動化比率を高めてトイルを削減する

  KR1: SRE の作業時間のうちトイル比率を 55% → 40% 以下に削減

  KR2: 手動対応が必要なインシデントの 30% を自動対応に置き換える

  KR3: 新しい自動化ツール・スクリプトを 10件以上リリース

Error Budget Policyの策定

エラーバジェットポリシーは組織の合意文書として整備する。

# Error Budget Policy

## 適用範囲: 全本番サービス

## 承認者: CTO, VPoE, 各サービスオーナー

### 1. エラーバジェットの計算

エラーバジェット = 1 - SLO目標値
計算ウィンドウ: 過去28日間のローリングウィンドウ
計算頻度: 毎時更新

### 2. ステータス定義

| バジェット残量 | ステータス | 色表示 |
|-------------|----------|------|
| > 50%       | 健全      | 🟢 Green |
| 25〜50%     | 注意      | 🟡 Yellow |
| 10〜25%     | 警告      | 🟠 Orange |
| 0〜10%      | 危機      | 🔴 Red |
| 0%(枯渇)  | ロック    | ⛔ Locked |

### 3. ステータス別の行動指針

🟢 Green(> 50%):
  - 通常のリリースサイクルを維持
  - 実験的な変更も許可

🟡 Yellow(25〜50%):
  - 週次の信頼性レビューを開始
  - リリース前に SRE レビューを必須化

🟠 Orange(10〜25%):
  - リリース頻度を半減(週1回まで)
  - インシデント防止を最優先タスクに
  - SRE と PO の週次ミーティング開始

🔴 Red(0〜10%):
  - リリースフリーズ(セキュリティパッチを除く)
  - SRE チームが全リソースを信頼性改善に投入
  - インシデント対応を全員参加体制に

⛔ Locked(0%):
  - 機能リリース停止
  - SRE と開発チームが緊急の信頼性スプリントを実施
  - 経営層への週次報告を開始

### 4. 例外事項

以下の場合はポリシーを一時的に緩和できる:
a. 重大なセキュリティ脆弱性の修正
b. 法的・コンプライアンス上の必須対応
c. CTO と SRE Lead の双方が承認した事業上の理由

### 5. レビュー

本ポリシーは四半期ごとに見直す。
次回レビュー: 2026-06-30

Toil Budget の組織的管理

#!/usr/bin/env python3
"""
トイル測定ツール
SRE エンジニアの作業時間をカテゴリ別に記録・分析する
"""

from dataclasses import dataclass, field
from datetime import datetime, timedelta
from enum import Enum
from typing import Optional
import json

class WorkCategory(Enum):
    TOIL_ONCALL = "オンコール対応"           # トイル
    TOIL_TICKET = "チケット処理"              # トイル
    TOIL_MANUAL_DEPLOY = "手動デプロイ"      # トイル
    TOIL_MANUAL_RESTART = "手動再起動"       # トイル
    ENGINEERING_AUTOMATION = "自動化開発"    # エンジニアリング
    ENGINEERING_ARCHITECTURE = "アーキテクチャ改善"  # エンジニアリング
    ENGINEERING_POSTMORTEM = "ポストモーテム"  # エンジニアリング(価値あり)
    MEETING = "ミーティング"                  # 中立
    OTHER = "その他"                          # 中立

TOIL_CATEGORIES = {
    WorkCategory.TOIL_ONCALL,
    WorkCategory.TOIL_TICKET,
    WorkCategory.TOIL_MANUAL_DEPLOY,
    WorkCategory.TOIL_MANUAL_RESTART,
}

@dataclass
class WorkEntry:
    date: str
    engineer: str
    category: WorkCategory
    duration_minutes: float
    description: str
    service: Optional[str] = None

@dataclass
class ToilMetrics:
    entries: list[WorkEntry] = field(default_factory=list)

    def add_entry(self, engineer: str, category: WorkCategory,
                  duration_minutes: float, description: str, service: str = None):
        self.entries.append(WorkEntry(
            date=datetime.now().strftime("%Y-%m-%d"),
            engineer=engineer,
            category=category,
            duration_minutes=duration_minutes,
            description=description,
            service=service,
        ))

    def calculate_toil_ratio(self, engineer: str = None,
                              days: int = 30) -> dict:
        """トイル比率を計算"""
        cutoff = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")

        filtered = [
            e for e in self.entries
            if e.date >= cutoff and (engineer is None or e.engineer == engineer)
        ]

        total_minutes = sum(e.duration_minutes for e in filtered)
        toil_minutes = sum(
            e.duration_minutes for e in filtered
            if e.category in TOIL_CATEGORIES
        )

        if total_minutes == 0:
            return {"error": "データなし"}

        toil_ratio = toil_minutes / total_minutes * 100

        # カテゴリ別集計
        by_category = {}
        for entry in filtered:
            cat = entry.category.value
            by_category[cat] = by_category.get(cat, 0) + entry.duration_minutes

        return {
            "engineer": engineer or "全員",
            "period_days": days,
            "total_hours": round(total_minutes / 60, 1),
            "toil_hours": round(toil_minutes / 60, 1),
            "toil_ratio_percent": round(toil_ratio, 1),
            "status": "✅ 正常" if toil_ratio <= 50 else "⚠️ 要改善",
            "by_category": {k: round(v/60, 1) for k, v in sorted(
                by_category.items(), key=lambda x: -x[1]
            )},
        }

# 使用例
metrics = ToilMetrics()

# エントリの追加
metrics.add_entry("yamada", WorkCategory.TOIL_ONCALL, 120, "決済APIの障害対応", "payment-api")
metrics.add_entry("yamada", WorkCategory.ENGINEERING_AUTOMATION, 240, "アラート自動対応スクリプト開発")
metrics.add_entry("yamada", WorkCategory.TOIL_TICKET, 60, "ユーザーアカウントの手動復旧対応")
metrics.add_entry("yamada", WorkCategory.ENGINEERING_POSTMORTEM, 90, "INC-001 ポストモーテム実施")

# 結果表示
result = metrics.calculate_toil_ratio("yamada", 30)
print(json.dumps(result, indent=2, ensure_ascii=False))

20. コスト最適化

インフラコストとSREの関係

SRE はシステムの信頼性に責任を持つが、コスト効率も重要な指標だ。信頼性のためだけにコストを無限に使うことはできない。

コスト vs 信頼性のトレードオフ:

信頼性 ↑
  |        ∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙(コスト∞でも100%にはならない)
  |    ∙∙∙∙
  |   ∙∙
  |  ∙
  | ∙
  |∙
  +──────────────────────────→ コスト →

99.9% → 99.99% は「最後の一個の 9」で数倍のコストがかかる

原則:
  - 必要以上の信頼性はコストの無駄(ユーザーが気づかない余剰冗長性)
  - SLO で信頼性の要件を定義して、それを達成するための最小コストを探す

AWS/GCP のコスト最適化テクニック

リソースの右サイジング(Right-sizing)

# ==== AWS Cost Explorer での未活用リソース確認 ====

# AWS CLI でインスタンスの CPU 使用率を確認
aws cloudwatch get-metric-statistics \
  --namespace AWS/EC2 \
  --metric-name CPUUtilization \
  --dimensions Name=InstanceId,Value=i-1234567890abcdef0 \
  --start-time $(date -d "30 days ago" +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date +%Y-%m-%dT%H:%M:%S) \
  --period 86400 \
  --statistics Average,Maximum \
  --query 'Datapoints[*].[Timestamp,Average,Maximum]' \
  --output table

# p95 CPU 使用率が 20% 以下 → ダウンサイジング候補
# メモリ使用率が常時 30% 以下 → インスタンスタイプを変更

# ==== コスト削減の基本 ====
# Compute Savings Plans(AWS): 1年〜3年コミットで最大72%割引
# Reserved Instances(AWS): 特定インスタンスタイプを1〜3年コミット
# Committed Use Discounts(GCP): 1〜3年コミットで最大57%割引

# Kubernetes でのリソース最適化
# VPA(Vertical Pod Autoscaler)で適切なリクエスト値を自動設定
kubectl describe vpa payment-api -n production

Spot/Preemptible インスタンスの活用

Spot インスタンス(AWS)/ Spot VM(GCP)/ Spot VM(Azure):
  - 余剰キャパシティを低価格で提供
  - AWS: 最大90%割引(動的価格)
  - GCP Spot VM: 最大91%割引(固定割引)
  - Azure Spot: 最大90%割引(動的価格)

  デメリット: いつでも2分前通知で強制終了される

SRE 的な設計原則:
  1. 中断されても問題ないワークロードのみ使用
  2. 中断時の graceful shutdown を実装
  3. 複数の Availability Zone / InstanceType を使う
  4. Spot と On-Demand を混在させてベースラインを保証

Kubernetes での Spot インスタンス混在設計

# Node Group の設定(Spot + On-Demand 混在)
# AWS EKS Node Group の設定例

# On-Demand ベースライン(最低限の安定性確保)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-api
  namespace: production
spec:
  replicas: 6
  template:
    spec:
      # Spot と On-Demand を使い分けるアフィニティ
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            # 優先: On-Demand(重要サービスの一部は Spot を避ける)
            - weight: 80
              preference:
                matchExpressions:
                  - key: eks.amazonaws.com/capacityType
                    operator: In
                    values: ["ON_DEMAND"]
            # 許容: Spot(コスト削減)
            - weight: 20
              preference:
                matchExpressions:
                  - key: eks.amazonaws.com/capacityType
                    operator: In
                    values: ["SPOT"]

      # Spot ノード強制終了時の graceful shutdown
      terminationGracePeriodSeconds: 60
      containers:
        - name: payment-api
          lifecycle:
            preStop:
              exec:
                command: ["sh", "-c", "sleep 30"]  # ドレイン時間

---

# バッチ処理は Spot のみ(コスト最優先)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: report-generator
spec:
  template:
    spec:
      tolerations:
        - key: "spot-instance"
          operator: "Equal"
          value: "true"
          effect: "NoSchedule"
      nodeSelector:
        eks.amazonaws.com/capacityType: SPOT
# Spot インスタンス中断シグナルのハンドリング(Python)
import signal
import time
import threading
import requests

class SpotInterruptionHandler:
    """AWS Spot インスタンスの中断シグナルを検知して graceful shutdown"""

    def __init__(self, drain_callback, shutdown_callback):
        self.drain_callback = drain_callback     # 新規リクエストを止める
        self.shutdown_callback = shutdown_callback  # クリーンアップ
        self.interrupted = False
        self._start_monitoring()

    def _check_interruption(self):
        """EC2 インスタンスメタデータで中断シグナルを確認"""
        try:
            # IMDSv2 でトークンを取得
            token_resp = requests.put(
                "http://169.254.169.254/latest/api/token",
                headers={"X-aws-ec2-metadata-token-ttl-seconds": "21600"},
                timeout=1,
            )
            token = token_resp.text

            # Spot 中断通知を確認
            resp = requests.get(
                "http://169.254.169.254/latest/meta-data/spot/interruption-notice",
                headers={"X-aws-ec2-metadata-token": token},
                timeout=1,
            )

            if resp.status_code == 200:
                print(f"SPOT INTERRUPTION NOTICE: {resp.json()}")
                return True
        except Exception:
            pass
        return False

    def _start_monitoring(self):
        """バックグラウンドで5秒ごとに確認"""
        def monitor():
            while not self.interrupted:
                if self._check_interruption():
                    self.interrupted = True
                    print("Spot interruption detected. Starting graceful shutdown...")
                    self.drain_callback()   # 新規リクエストを止める
                    time.sleep(25)          # 最大25秒(2分の余裕の中で処理完了を待つ)
                    self.shutdown_callback()  # 終了処理
                    break
                time.sleep(5)

        thread = threading.Thread(target=monitor, daemon=True)
        thread.start()

クラウドコストの見える化

#!/usr/bin/env python3
"""
AWS Cost Explorer API でサービス別コストを取得してSlackに通知
"""

import boto3
import requests
from datetime import datetime, timedelta

def get_service_costs(days: int = 7) -> list[dict]:
    """過去N日間のサービス別コストを取得"""
    client = boto3.client('ce', region_name='us-east-1')

    end_date = datetime.now().strftime('%Y-%m-%d')
    start_date = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')

    response = client.get_cost_and_usage(
        TimePeriod={
            'Start': start_date,
            'End': end_date,
        },
        Granularity='MONTHLY',
        Metrics=['UnblendedCost'],
        GroupBy=[
            {'Type': 'DIMENSION', 'Key': 'SERVICE'},
        ],
    )

    costs = []
    for group in response['ResultsByTime'][0]['Groups']:
        service = group['Keys'][0]
        amount = float(group['Metrics']['UnblendedCost']['Amount'])
        if amount > 0:
            costs.append({
                'service': service,
                'cost_usd': round(amount, 2),
            })

    return sorted(costs, key=lambda x: -x['cost_usd'])

def send_weekly_cost_report(webhook_url: str):
    """週次コストレポートをSlackに送信"""
    costs = get_service_costs(7)
    total = sum(c['cost_usd'] for c in costs)

    top_services = costs[:5]

    blocks = [
        {
            "type": "header",
            "text": {"type": "plain_text", "text": "💰 週次クラウドコストレポート"}
        },
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": f"*今週の総コスト: ${total:,.2f} USD*\n期間: 過去7日間"
            }
        },
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": "*コスト上位5サービス:*\n" + "\n".join([
                    f"{i+1}. {c['service']}: ${c['cost_usd']:,.2f}"
                    for i, c in enumerate(top_services)
                ])
            }
        }
    ]

    requests.post(webhook_url, json={"blocks": blocks}, timeout=10)

if __name__ == "__main__":
    import os
    send_weekly_cost_report(os.environ["SLACK_WEBHOOK_URL"])

21. マルチリージョン・グローバル信頼性

Active-Active vs Active-Passive 設計

【Active-Passive】

  [クライアント]
      │
      ├─→ [リージョンA: ACTIVE] ← 通常時はここだけに流す
      │
      └─→ [リージョンB: PASSIVE/STANDBY] ← 障害時に切替

  特徴:
  ✓ シンプルな設計
  ✓ データの整合性管理が容易(マスターは1つ)
  ✗ スタンバイのリソースが遊んでいる(コスト無駄)
  ✗ フェイルオーバー時にダウンタイムが発生(数十秒〜数分)
  ✗ スタンバイが長期間使われないと本番切替時に問題が発覚しやすい

  向いているケース:
  - 厳密なデータ整合性が必要(金融取引等)
  - DR(Disaster Recovery)用途
  - コストを最小化したい

【Active-Active】

  [クライアント]
      │
  [グローバルLB / DNS フェイルオーバー]
      │
      ├─→ [リージョンA: ACTIVE] ← 通常時は両方に流す(例: 50/50 or 70/30)
      │
      └─→ [リージョンB: ACTIVE] ← 通常時から稼働している

  特徴:
  ✓ 一方のリージョンが落ちても即座にもう一方で対応
  ✓ 両リージョンのリソースを常時活用(コスト効率)
  ✓ ユーザーに地理的に近いリージョンで応答可能(レイテンシ改善)
  ✗ データの整合性管理が複雑(両リージョンへの書き込み同期)
  ✗ 設計・運用コストが高い

  向いているケース:
  - 高可用性が最重要(99.99%以上)
  - グローバルにユーザーが分散している
  - 読み取りが多いワークロード

グローバルロードバランシング

DNS ベースのフェイルオーバー

DNS フェイルオーバーの仕組み:

1. ヘルスチェック: Route 53/Cloud DNS が定期的にエンドポイントを確認
2. 障害検知: ヘルスチェック失敗を複数回確認
3. DNS レコード更新: 障害エンドポイントを DNS レスポンスから除外
4. TTL: クライアントのキャッシュが切れたら新しい IP に接続

フェイルオーバー時間:
= DNS TTL + ヘルスチェック間隔 × 失敗閾値
= 60秒 + 10秒 × 3 = 90秒 → 最大約2分のダウンタイム

TTL のトレードオフ:
短い TTL(60秒): フェイルオーバーが速い → DNS サーバーへの負荷が高い
長い TTL(300秒): フェイルオーバーが遅い → DNS サーバーの負荷が低い
推奨: 通常時 300秒、障害事前対応時に 60秒に変更
# ==== AWS Route 53 フェイルオーバー設定 ====

# 1. ヘルスチェックの作成
aws route53 create-health-check \
  --caller-reference "primary-$(date +%s)" \
  --health-check-config '{
    "IPAddress": "1.2.3.4",
    "Port": 443,
    "Type": "HTTPS",
    "ResourcePath": "/health",
    "FullyQualifiedDomainName": "api-ap-northeast-1.example.com",
    "RequestInterval": 10,
    "FailureThreshold": 3,
    "EnableSNI": true,
    "Regions": ["ap-northeast-1", "us-east-1", "eu-west-1"]
  }'

# 2. フェイルオーバーレコードの設定
# Primary(ap-northeast-1)
aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890 \
  --change-batch '{
    "Changes": [{
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "api.example.com",
        "Type": "A",
        "SetIdentifier": "primary-ap-northeast-1",
        "Failover": "PRIMARY",
        "TTL": 60,
        "ResourceRecords": [{"Value": "1.2.3.4"}],
        "HealthCheckId": "HEALTH_CHECK_ID_HERE"
      }
    }]
  }'

# Secondary(us-west-2)
aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890 \
  --change-batch '{
    "Changes": [{
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "api.example.com",
        "Type": "A",
        "SetIdentifier": "secondary-us-west-2",
        "Failover": "SECONDARY",
        "TTL": 60,
        "ResourceRecords": [{"Value": "5.6.7.8"}]
      }
    }]
  }'

# 3. フェイルオーバーのテスト
# Primary を強制的にヘルスチェック失敗にさせて確認
dig api.example.com
# → プライマリ(1.2.3.4)が返ってくること確認

# Primary のヘルスチェックを一時的に失敗させる(サービスを止める等)
# 数分後に再確認
dig api.example.com
# → セカンダリ(5.6.7.8)が返ってくることを確認

データレプリケーションと一貫性

分散システムにおける一貫性モデル:

【CAP 定理】
  分散システムは以下の3つのうち2つしか同時に保証できない:
  C: Consistency(一貫性)
  A: Availability(可用性)
  P: Partition Tolerance(ネットワーク分断への耐性)

  現実: P は必須(ネットワーク障害は必ず起きる)
  選択: CP(一貫性優先)vs AP(可用性優先)

【一貫性レベルの選択】

  Strong Consistency(強い一貫性):
    → 全ノードで同じデータが読める
    → 書き込みは全ノードに確認後に完了
    → レイテンシが高い・可用性が下がる
    → 使いどき: 金融取引・在庫管理

  Eventual Consistency(結果整合性):
    → いずれは一致するが、一時的にずれる可能性がある
    → 書き込みは一部ノードで完了したら応答
    → 低レイテンシ・高可用性
    → 使いどき: ソーシャルメディアのフォロー数、閲覧数

  Read-your-writes(自分が書いたものは読める):
    → 自分が書いたデータは必ず自分が読める
    → ユーザーにとって自然な体験を保証
    → 実装: セッションを同じレプリカに固定

【マルチリージョンでの DB レプリケーション】

  パターン1: Single Master(マルチリージョン読み取り)
    リージョンA [Master] ──→ リージョンB [Replica] ──→ リージョンC [Replica]
    書き込みはすべてマスターへ(1リージョンに集中)
    読み取りは地理的に近いレプリカから
    RPO: 数秒(レプリケーションラグ)

  パターン2: Multi-Master(双方向レプリケーション)
    リージョンA [Master] ←→ リージョンB [Master]
    書き込みを両リージョンで受け付ける
    コンフリクト解決が必要(Last-Write-Wins等)
    RPO: ほぼ0(ただしコンフリクト発生リスク)

リージョン障害時のフェイルオーバー手順

# ==== リージョン障害時のフェイルオーバー ランブック ====

# このランブックは ap-northeast-1(東京)が全断した場合の
# us-west-2(オレゴン)へのフェイルオーバー手順

# ====== 確認フェーズ(5分以内) ======

# 1. AWS サービスステータスを確認
open https://health.aws.amazon.com/health/status

# 2. 東京リージョンの ALB ヘルスを確認
aws elbv2 describe-target-health \
  --target-group-arn $TOKYO_TARGET_GROUP_ARN \
  --region ap-northeast-1

# 3. RDS の状態確認
aws rds describe-db-instances \
  --region ap-northeast-1 \
  --query 'DBInstances[*].[DBInstanceIdentifier,DBInstanceStatus]'

# 4. 影響範囲の確認
# Grafana で Tokyo リージョンのメトリクスを確認
# → エラー率、レイテンシ、リクエスト数

# ====== 意思決定(10分以内) ======

# フェイルオーバーを実施する基準:
# - リージョン全体の障害で回復の見込みが30分以上
# - SLO が15分間継続して大幅に違反している
# - AWS から公式に長期障害の発表がある

# フェイルオーバー前にチェック:
# [ ] Incident Commander の承認を得た
# [ ] ステータスページを更新した("調査中")
# [ ] US-West-2 のリソースが正常であることを確認した
# [ ] DB フェイルオーバー計画を確認した

# ====== フェイルオーバー実施(30分以内) ======

# Step 1: Aurora Global Database のフェイルオーバー
aws rds failover-global-cluster \
  --global-cluster-identifier my-global-cluster \
  --target-db-cluster-identifier arn:aws:rds:us-west-2:123456789:cluster:my-db-us-west-2 \
  --region ap-northeast-1

# フェイルオーバー完了を待機(通常1〜2分)
while true; do
  STATUS=$(aws rds describe-global-clusters \
    --global-cluster-identifier my-global-cluster \
    --query 'GlobalClusters[0].Status' \
    --output text 2>/dev/null)
  echo "Global Cluster Status: $STATUS"
  if [[ "$STATUS" == "available" ]]; then
    echo "Failover completed!"
    break
  fi
  sleep 10
done

# Step 2: DNS の切り替え(Route 53)
# フェイルオーバーは自動(ヘルスチェックに基づく)
# 手動で切り替える場合:
aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890 \
  --change-batch '{
    "Changes": [{
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "api.example.com",
        "Type": "A",
        "TTL": 60,
        "ResourceRecords": [{"Value": "US_WEST_2_IP"}]
      }
    }]
  }'

# Step 3: 疎通確認
curl -v https://api.example.com/health
dig api.example.com
echo "Response region: $(curl -s https://api.example.com/region)"

# Step 4: ステータスページを更新("フェイルオーバー実施中")

# Step 5: モニタリングで正常化を確認
# エラー率が通常レベルに戻っているか確認

# Step 6: ステータスページを更新("復旧")

# ====== 事後対応 ======

# - 東京リージョン復旧後のフェイルバック計画を立てる
# - ポストモーテムを実施する
# - フェイルオーバーの実測時間を記録する(MTTR の改善に使う)

22. 実践的なSREツール集

モニタリング・オブザーバビリティツール比較

┌──────────────────┬────────────────────────────────┬─────────────────────┬──────────────┐
│ ツール            │ 特徴                           │ 推奨ユースケース     │ コスト        │
├──────────────────┼────────────────────────────────┼─────────────────────┼──────────────┤
│ Prometheus       │ Pull型メトリクス収集。強力な    │ Kubernetes監視      │ OSS          │
│                  │ PromQL。エコシステムが豊富      │ オンプレミス        │              │
├──────────────────┼────────────────────────────────┼─────────────────────┼──────────────┤
│ Grafana          │ 最高のダッシュボード。多データ  │ 全データの可視化    │ OSS/SaaS     │
│                  │ ソース対応。アラート機能あり    │                     │              │
├──────────────────┼────────────────────────────────┼─────────────────────┼──────────────┤
│ Datadog          │ フルスタックAPM。設定が簡単。  │ スタートアップ〜    │ 高め         │
│                  │ 機械学習異常検知あり            │ 中規模企業          │              │
├──────────────────┼────────────────────────────────┼─────────────────────┼──────────────┤
│ New Relic        │ APM に強い。フルスタック監視。 │ アプリパフォーマンス│ 中〜高       │
│                  │ 無料枠が充実                   │ 重視の企業          │              │
├──────────────────┼────────────────────────────────┼─────────────────────┼──────────────┤
│ CloudWatch       │ AWS と深く統合。Lambda 等も    │ AWS中心の環境       │ AWS料金体系  │
│ (AWS)            │ 自動で監視。設定が少ない        │                     │              │
├──────────────────┼────────────────────────────────┼─────────────────────┼──────────────┤
│ Victoria Metrics │ Prometheusより高いパフォーマンス│ 大規模Prometheus    │ OSS          │
│                  │ 長期保存に適している           │ の代替              │              │
├──────────────────┼────────────────────────────────┼─────────────────────┼──────────────┤
│ Grafana Loki     │ Prometheusライクなログ集約。   │ Grafanaスタックと   │ OSS          │
│                  │ ラベルベース。低コスト         │ 組み合わせ          │              │
├──────────────────┼────────────────────────────────┼─────────────────────┼──────────────┤
│ Elastic Stack    │ 強力なログ検索。Kibana の      │ 大量ログの全文検索  │ OSS/SaaS     │
│ (ELK)            │ 豊富な可視化                  │                     │              │
└──────────────────┴────────────────────────────────┴─────────────────────┴──────────────┘

インシデント管理ツール比較

┌──────────────────┬────────────────────────────────┬──────────────┐
│ ツール            │ 特徴                           │ コスト        │
├──────────────────┼────────────────────────────────┼──────────────┤
│ PagerDuty        │ 業界標準。豊富なインテグレーション│ 高め        │
│                  │ オンコールスケジュール管理が強力 │              │
├──────────────────┼────────────────────────────────┼──────────────┤
│ OpsGenie         │ PagerDutyより安価。機能は同等。 │ 中程度       │
│                  │ Atlassian 製品と親和性高い      │              │
├──────────────────┼────────────────────────────────┼──────────────┤
│ VictorOps        │ Splunk 傘下。インシデント        │ 中程度       │
│(Splunk On-Call)│ タイムライン機能が強力          │              │
├──────────────────┼────────────────────────────────┼──────────────┤
│ Alertmanager     │ Prometheus 付属。シンプル。      │ OSS          │
│                  │ ルーティング・グルーピング機能  │              │
├──────────────────┼────────────────────────────────┼──────────────┤
│ Incident.io      │ Slack ネイティブ。ポストモーテム│ 中程度       │
│                  │ 管理機能が優れている            │              │
└──────────────────┴────────────────────────────────┴──────────────┘

CI/CD ツール比較

┌──────────────────┬────────────────────────────────┬──────────────┐
│ ツール            │ 特徴                           │ コスト        │
├──────────────────┼────────────────────────────────┼──────────────┤
│ GitHub Actions   │ GitHub と密統合。エコシステム豊富│ GitHub料金  │
│                  │ YAML で書きやすい               │              │
├──────────────────┼────────────────────────────────┼──────────────┤
│ GitLab CI/CD     │ GitLab と一体。Auto DevOps 機能│ OSS/SaaS    │
│                  │ Kubernetes 連携が強力           │              │
├──────────────────┼────────────────────────────────┼──────────────┤
│ Jenkins          │ 最も歴史が長い。プラグイン豊富  │ OSS          │
│                  │ 設定・運用コストが高い          │              │
├──────────────────┼────────────────────────────────┼──────────────┤
│ ArgoCD           │ GitOps 標準ツール。Kubernetes   │ OSS          │
│                  │ への継続的デプロイに特化        │              │
├──────────────────┼────────────────────────────────┼──────────────┤
│ Flux             │ ArgoCD の代替。CNCF Graduated。 │ OSS          │
│                  │ より軽量・シンプル              │              │
├──────────────────┼────────────────────────────────┼──────────────┤
│ Tekton           │ Kubernetes ネイティブ CI/CD。  │ OSS          │
│                  │ 柔軟だが設定が複雑             │              │
└──────────────────┴────────────────────────────────┴──────────────┘

IaC ツール比較

┌──────────────────┬────────────────────────────────┬──────────────┐
│ ツール            │ 特徴                           │ コスト        │
├──────────────────┼────────────────────────────────┼──────────────┤
│ Terraform        │ 最も普及。マルチクラウド対応。  │ OSS/TF Cloud │
│                  │ HCL 構文。State 管理。          │              │
├──────────────────┼────────────────────────────────┼──────────────┤
│ OpenTofu         │ Terraform のOSSフォーク。        │ OSS          │
│                  │ ライセンス変更に対応            │              │
├──────────────────┼────────────────────────────────┼──────────────┤
│ Pulumi           │ Python/TypeScript 等で書ける。  │ OSS/SaaS     │
│                  │ テストが書きやすい              │              │
├──────────────────┼────────────────────────────────┼──────────────┤
│ Ansible          │ エージェントレス設定管理。      │ OSS          │
│                  │ YAML。冪等性。AD Hoc も使える  │              │
├──────────────────┼────────────────────────────────┼──────────────┤
│ Crossplane       │ Kubernetes CRD でインフラ管理。 │ OSS          │
│                  │ GitOps と相性が良い             │              │
└──────────────────┴────────────────────────────────┴──────────────┘

カオスエンジニアリングツール比較

┌──────────────────┬────────────────────────────────┬──────────────┐
│ ツール            │ 特徴                           │ コスト        │
├──────────────────┼────────────────────────────────┼──────────────┤
│ LitmusChaos      │ Kubernetes ネイティブ。CNCF    │ OSS          │
│                  │ Graduated。豊富なシナリオ       │              │
├──────────────────┼────────────────────────────────┼──────────────┤
│ Chaos Monkey     │ Netflix 発祥。EC2 ランダム停止  │ OSS          │
│                  │ シンプルで実績あり             │              │
├──────────────────┼────────────────────────────────┼──────────────┤
│ Gremlin          │ エンタープライズ向け。GUI 操作  │ 高め         │
│                  │ 豊富な障害パターン             │              │
├──────────────────┼────────────────────────────────┼──────────────┤
│ AWS FIS          │ AWS マネージド。EC2/ECS/EKS 対応│ AWS料金     │
│(Fault Injection)│ 安全な制御が可能              │              │
├──────────────────┼────────────────────────────────┼──────────────┤
│ Chaos Toolkit    │ Python ベース。プログラマブル   │ OSS          │
│                  │ 拡張しやすい                   │              │
└──────────────────┴────────────────────────────────┴──────────────┘

ツール選定の基準

SRE ツール選定チェックリスト:

1. 運用コスト
   □ セルフホストの場合: 運用・アップグレードのコストは?
   □ マネージドの場合: スケール時のコストは許容範囲か?

2. スケーラビリティ
   □ 現在のデータ量・クエリ数に耐えられるか?
   □ 5倍になっても動くか?

3. 既存スタックとの統合
   □ 既存の監視・アラートツールと連携できるか?
   □ Kubernetes / クラウド環境と親和性があるか?

4. チームのスキルセット
   □ チームが学習・習得できる難易度か?
   □ ドキュメント・コミュニティが充実しているか?

5. MTTR への貢献
   □ 障害時に役立つ情報を素早く提供できるか?
   □ アラート → 根本原因特定 → 修正のフローを支援できるか?

6. ベンダーロックイン
   □ 将来のマイグレーションは現実的か?
   □ OpenTelemetry等の標準に対応しているか?

OSS vs マネージドの選択基準:

OSS を選ぶべき場合:
  - コストが最重要
  - チームに運用スキルがある
  - カスタマイズが必要
  - データを自分のインフラに置きたい(セキュリティ・法規制)

マネージドを選ぶべき場合:
  - 運用に割くエンジニアリング時間がない
  - スタートアップで速く構築したい
  - サポートが必要
  - 運用コストよりサービスコストの方が安い(TCO で比較)

23. Pythonによる自動化実践:スクリプト集

SLOレポート自動生成とSlack通知

#!/usr/bin/env python3
"""
週次 SLO レポート自動生成スクリプト
毎週月曜日に先週のSLO達成状況をSlackに通知する
"""

import os
import json
from datetime import datetime, timedelta
from typing import Optional
import requests

PROMETHEUS_URL = os.environ.get("PROMETHEUS_URL", "http://prometheus:9090")
SLACK_WEBHOOK_URL = os.environ["SLACK_WEBHOOK_URL"]

# SLO 設定
SLO_CONFIGS = [
    {
        "name": "API Gateway - 可用性",
        "service": "api-gateway",
        "slo_target": 0.999,
        "query": 'sum(rate(http_requests_total{job="api-gateway",status!~"5.."}[7d])) / sum(rate(http_requests_total{job="api-gateway"}[7d]))',
    },
    {
        "name": "決済API - 可用性",
        "service": "payment-api",
        "slo_target": 0.9995,
        "query": 'sum(rate(http_requests_total{job="payment-api",status!~"5.."}[7d])) / sum(rate(http_requests_total{job="payment-api"}[7d]))',
    },
    {
        "name": "API Gateway - レイテンシ p99",
        "service": "api-gateway",
        "slo_target": 0.95,  # 95% が 500ms 以内
        "query": 'sum(rate(http_request_duration_seconds_bucket{job="api-gateway",le="0.5"}[7d])) / sum(rate(http_request_duration_seconds_count{job="api-gateway"}[7d]))',
    },
]

def query_prometheus(query: str) -> Optional[float]:
    """Prometheus にクエリを投げて単一の値を返す"""
    try:
        resp = requests.get(
            f"{PROMETHEUS_URL}/api/v1/query",
            params={"query": query},
            timeout=30,
        )
        resp.raise_for_status()
        data = resp.json()

        if data["status"] != "success":
            return None

        results = data["data"]["result"]
        if not results:
            return None

        return float(results[0]["value"][1])
    except Exception as e:
        print(f"Prometheus query error: {e}")
        return None

def calculate_error_budget_status(sli: float, slo: float) -> dict:
    """エラーバジェットの状況を計算"""
    error_budget_total = 1.0 - slo
    error_budget_consumed = max(0.0, slo - sli)
    error_budget_remaining = error_budget_total - error_budget_consumed
    error_budget_pct = error_budget_remaining / error_budget_total * 100 if error_budget_total > 0 else 0

    if error_budget_pct > 50:
        status_emoji = "🟢"
        status = "良好"
    elif error_budget_pct > 25:
        status_emoji = "🟡"
        status = "注意"
    elif error_budget_pct > 0:
        status_emoji = "🔴"
        status = "危険"
    else:
        status_emoji = "⛔"
        status = "枯渇"

    return {
        "remaining_pct": round(error_budget_pct, 1),
        "status_emoji": status_emoji,
        "status": status,
        "met_slo": sli >= slo,
    }

def generate_weekly_report() -> list[dict]:
    """週次 SLO レポートを生成"""
    reports = []

    for config in SLO_CONFIGS:
        sli = query_prometheus(config["query"])

        if sli is None:
            reports.append({
                "name": config["name"],
                "error": "データ取得失敗",
            })
            continue

        budget_status = calculate_error_budget_status(sli, config["slo_target"])

        reports.append({
            "name": config["name"],
            "slo_target": f"{config['slo_target'] * 100:.3f}%",
            "actual_sli": f"{sli * 100:.4f}%",
            "met_slo": budget_status["met_slo"],
            "error_budget_remaining": f"{budget_status['remaining_pct']}%",
            "status_emoji": budget_status["status_emoji"],
            "status": budget_status["status"],
        })

    return reports

def send_to_slack(reports: list[dict]) -> None:
    """Slack に週次レポートを送信"""
    now = datetime.now()
    week_start = (now - timedelta(days=7)).strftime("%m/%d")
    week_end = now.strftime("%m/%d")

    met_count = sum(1 for r in reports if r.get("met_slo", False))
    total_count = sum(1 for r in reports if "error" not in r)

    overall_emoji = "🟢" if met_count == total_count else "🔴"

    blocks = [
        {
            "type": "header",
            "text": {
                "type": "plain_text",
                "text": f"{overall_emoji} 週次 SLO レポート ({week_start}{week_end})"
            }
        },
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": f"SLO 達成: *{met_count}/{total_count}*"
            }
        },
        {"type": "divider"},
    ]

    for report in reports:
        if "error" in report:
            blocks.append({
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": f"❓ *{report['name']}*\n{report['error']}"
                }
            })
        else:
            slo_emoji = "✅" if report["met_slo"] else "❌"
            blocks.append({
                "type": "section",
                "text": {
                    "type": "mrkdwn",
                    "text": (
                        f"{slo_emoji} *{report['name']}*\n"
                        f"目標: {report['slo_target']} | "
                        f"実績: {report['actual_sli']} | "
                        f"バジェット残量: {report['status_emoji']} {report['error_budget_remaining']} ({report['status']})"
                    )
                }
            })

    blocks.append({
        "type": "section",
        "text": {
            "type": "mrkdwn",
            "text": f"📊 <https://grafana.example.com/d/slo-overview|詳細ダッシュボード>"
        }
    })

    resp = requests.post(
        SLACK_WEBHOOK_URL,
        json={"blocks": blocks},
        timeout=10,
    )
    resp.raise_for_status()
    print(f"Report sent to Slack: {met_count}/{total_count} SLOs met")

def main():
    print(f"Generating weekly SLO report at {datetime.now().isoformat()}")
    reports = generate_weekly_report()

    print("\n=== SLO Report ===")
    for report in reports:
        print(json.dumps(report, ensure_ascii=False, indent=2))

    send_to_slack(reports)

if __name__ == "__main__":
    main()

Kubernetesリソース自動クリーンアップ

#!/usr/bin/env python3
"""
Kubernetes の古いリソースを自動クリーンアップするスクリプト
- 完了した Job を削除
- Evicted された Pod を削除
- 古い ReplicaSet を削除
"""

import subprocess
import json
import sys
from datetime import datetime, timezone
from typing import Optional

DRY_RUN = "--dry-run" in sys.argv
RETENTION_DAYS = 7      # この日数より古い完了リソースを削除
NAMESPACES_TO_CLEAN = ["production", "staging", "default"]

def kubectl(args: list[str], namespace: Optional[str] = None) -> tuple[bool, str]:
    """kubectl コマンドを実行"""
    cmd = ["kubectl"]
    if namespace:
        cmd.extend(["-n", namespace])
    cmd.extend(args)

    if DRY_RUN and any(a in ["delete"] for a in args):
        print(f"[DRY RUN] Would run: {' '.join(cmd)}")
        return True, ""

    result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
    return result.returncode == 0, result.stdout

def get_resources(resource_type: str, namespace: str) -> list[dict]:
    """指定タイプのリソース一覧を取得"""
    ok, output = kubectl(["get", resource_type, "-o", "json"], namespace)
    if not ok or not output.strip():
        return []

    try:
        data = json.loads(output)
        return data.get("items", [])
    except json.JSONDecodeError:
        return []

def age_days(creation_timestamp: str) -> float:
    """リソースの経過日数を計算"""
    created = datetime.fromisoformat(creation_timestamp.replace("Z", "+00:00"))
    now = datetime.now(timezone.utc)
    return (now - created).total_seconds() / 86400

def cleanup_completed_jobs(namespace: str) -> int:
    """完了した Job を削除"""
    jobs = get_resources("jobs", namespace)
    deleted = 0

    for job in jobs:
        name = job["metadata"]["name"]
        conditions = job.get("status", {}).get("conditions", [])

        # 完了した Job かチェック
        is_complete = any(
            c.get("type") == "Complete" and c.get("status") == "True"
            for c in conditions
        )

        if not is_complete:
            continue

        # 経過日数チェック
        created = job["metadata"]["creationTimestamp"]
        if age_days(created) < RETENTION_DAYS:
            continue

        ok, _ = kubectl(["delete", "job", name], namespace)
        if ok:
            print(f"Deleted completed job: {namespace}/{name}")
            deleted += 1

    return deleted

def cleanup_evicted_pods(namespace: str) -> int:
    """Evicted された Pod を削除"""
    pods = get_resources("pods", namespace)
    deleted = 0

    for pod in pods:
        name = pod["metadata"]["name"]
        phase = pod.get("status", {}).get("phase", "")
        reason = pod.get("status", {}).get("reason", "")

        if phase == "Failed" and reason == "Evicted":
            ok, _ = kubectl(["delete", "pod", name], namespace)
            if ok:
                print(f"Deleted evicted pod: {namespace}/{name}")
                deleted += 1

    return deleted

def cleanup_old_replicasets(namespace: str) -> int:
    """古い ReplicaSet(レプリカ数0)を削除"""
    replicasets = get_resources("replicasets", namespace)
    deleted = 0

    for rs in replicasets:
        name = rs["metadata"]["name"]
        replicas = rs.get("spec", {}).get("replicas", 0)
        desired = rs.get("status", {}).get("replicas", 0)

        # レプリカ数が0のものだけ対象
        if replicas != 0 or desired != 0:
            continue

        created = rs["metadata"]["creationTimestamp"]
        if age_days(created) < RETENTION_DAYS:
            continue

        ok, _ = kubectl(["delete", "replicaset", name], namespace)
        if ok:
            print(f"Deleted old replicaset: {namespace}/{name}")
            deleted += 1

    return deleted

def main():
    print(f"Starting Kubernetes cleanup at {datetime.now().isoformat()}")
    print(f"Mode: {'DRY RUN' if DRY_RUN else 'ACTUAL'}")
    print(f"Retention days: {RETENTION_DAYS}")
    print(f"Namespaces: {', '.join(NAMESPACES_TO_CLEAN)}")
    print("=" * 50)

    total_deleted = 0

    for namespace in NAMESPACES_TO_CLEAN:
        print(f"\nCleaning namespace: {namespace}")

        jobs_deleted = cleanup_completed_jobs(namespace)
        pods_deleted = cleanup_evicted_pods(namespace)
        rs_deleted = cleanup_old_replicasets(namespace)

        ns_total = jobs_deleted + pods_deleted + rs_deleted
        total_deleted += ns_total

        print(f"  Jobs deleted: {jobs_deleted}")
        print(f"  Evicted pods deleted: {pods_deleted}")
        print(f"  Old ReplicaSets deleted: {rs_deleted}")
        print(f"  Namespace total: {ns_total}")

    print(f"\n{'='*50}")
    print(f"Total resources deleted: {total_deleted}")
    print("Cleanup completed!")

if __name__ == "__main__":
    main()

自動エスカレーションスクリプト

#!/usr/bin/env python3
"""
SLO バーンレートを監視して自動エスカレーションするスクリプト
Prometheus でバーンレートを確認し、一定以上ならPagerDutyでインシデントを作成
"""

import os
import time
import requests
from datetime import datetime
from typing import Optional

PROMETHEUS_URL = os.environ.get("PROMETHEUS_URL", "http://prometheus:9090")
PAGERDUTY_INTEGRATION_KEY = os.environ["PAGERDUTY_INTEGRATION_KEY"]
CHECK_INTERVAL = 60  # 60秒ごとにチェック

SLO_BURN_RATE_CHECKS = [
    {
        "name": "payment-api-critical-burn",
        "service": "payment-api",
        "severity": "critical",
        "query": """
            (
              1 - job:sli_availability:ratio_rate1h{service="payment-api"} > 14.4 * 0.001
            ) and (
              1 - job:sli_availability:ratio_rate5m{service="payment-api"} > 14.4 * 0.001
            )
        """,
        "description": "P1: 1時間でバジェットの2%消費(バーンレート14.4x超)",
    },
    {
        "name": "payment-api-warning-burn",
        "service": "payment-api",
        "severity": "warning",
        "query": """
            (
              1 - job:sli_availability:ratio_rate6h{service="payment-api"} > 6 * 0.001
            ) and (
              1 - job:sli_availability:ratio_rate30m{service="payment-api"} > 6 * 0.001
            )
        """,
        "description": "P2: 6時間でバジェットの5%消費(バーンレート6x超)",
    },
]

def query_prometheus(query: str) -> bool:
    """クエリを実行して値が1件以上あれば True を返す"""
    try:
        resp = requests.get(
            f"{PROMETHEUS_URL}/api/v1/query",
            params={"query": query.strip()},
            timeout=10,
        )
        resp.raise_for_status()
        data = resp.json()
        return bool(data["data"]["result"])
    except Exception as e:
        print(f"Prometheus query error: {e}")
        return False

def create_pagerduty_incident(
    summary: str,
    severity: str,
    component: str,
    details: dict,
) -> Optional[str]:
    """PagerDuty インシデントを作成"""
    payload = {
        "routing_key": PAGERDUTY_INTEGRATION_KEY,
        "event_action": "trigger",
        "payload": {
            "summary": summary,
            "severity": severity,  # critical, error, warning, info
            "source": "slo-burn-rate-monitor",
            "component": component,
            "timestamp": datetime.utcnow().isoformat() + "Z",
            "custom_details": details,
        },
        "links": [
            {
                "href": f"https://grafana.example.com/d/slo-dashboard",
                "text": "SLO ダッシュボード",
            }
        ],
    }

    try:
        resp = requests.post(
            "https://events.pagerduty.com/v2/enqueue",
            json=payload,
            timeout=10,
        )
        resp.raise_for_status()
        return resp.json().get("dedup_key")
    except Exception as e:
        print(f"PagerDuty API error: {e}")
        return None

# アクティブなインシデントを追跡(重複作成を防ぐ)
active_incidents: dict[str, str] = {}  # name → dedup_key

def main():
    print(f"SLO Burn Rate Monitor started at {datetime.now().isoformat()}")

    while True:
        for check in SLO_BURN_RATE_CHECKS:
            name = check["name"]
            is_burning = query_prometheus(check["query"])

            if is_burning and name not in active_incidents:
                # 新しいバーンレート検知 → PagerDuty にインシデント作成
                print(f"BURN RATE DETECTED: {name}")

                dedup_key = create_pagerduty_incident(
                    summary=f"SLO Error Budget Burning: {check['service']}",
                    severity=check["severity"],
                    component=check["service"],
                    details={
                        "check_name": name,
                        "description": check["description"],
                        "detected_at": datetime.utcnow().isoformat(),
                    },
                )

                if dedup_key:
                    active_incidents[name] = dedup_key
                    print(f"PagerDuty incident created: {dedup_key}")

            elif not is_burning and name in active_incidents:
                # バーンレートが解消された → アクティブリストから削除
                print(f"BURN RATE RESOLVED: {name}")
                del active_incidents[name]

        time.sleep(CHECK_INTERVAL)

if __name__ == "__main__":
    main()

24. SRE実践チェックリスト

サービスリリース前チェックリスト(Production Readiness Review)

新しいサービスを本番リリースする前に確認すべき項目:

Production Readiness Review (PRR) チェックリスト

【可観測性】
□ SLI が定義されている(可用性・レイテンシ・エラー率)
□ SLO が設定されてステークホルダーが合意している
□ Prometheus メトリクスが expose されている
□ 構造化ログ(JSON)で出力されている
□ 分散トレーシングが実装されている(trace_id がログに含まれる)
□ Grafana ダッシュボードが作成されている
□ SLO ダッシュボードが作成されている

【アラート】
□ SLO バーンレートアラートが設定されている
□ アラートにランブック URL が記載されている
□ P1/P2 は PagerDuty に連携されている
□ アラート疲労のリスクを評価した(誤検知率を確認)
□ 深夜アラートの影響を評価した

【インシデント対応】
□ ランブック(Runbook)が作成されている
□ オンコールローテーションに追加されている
□ エスカレーション先が明確になっている
□ サービスのステータスページへの記載が準備されている

【デプロイ・ロールバック】
□ CI/CD パイプラインが設定されている
□ Canary リリースまたは Blue/Green デプロイが設定されている
□ ロールバック手順が文書化されている
□ ロールバックに要する時間を確認した(目標: 5分以内)
□ フィーチャーフラグで緊急無効化できる

【信頼性設計】
□ 単一障害点(SPOF)の特定と対策
□ 水平スケーリング(HPA)の設定
□ ヘルスチェック(liveness / readiness probe)の設定
□ Graceful shutdown の実装(terminationGracePeriodSeconds)
□ サーキットブレーカー・リトライの設定
□ タイムアウトの設定(すべての外部接続)

【キャパシティ】
□ 負荷テストを実施した
□ リソース requests/limits が適切に設定されている
□ スケーリングの動作確認をした
□ ピーク時(例: セール期間)の対応計画がある

【データ】
□ バックアップが設定されている
□ バックアップのリストアテストを実施した
□ データ保持ポリシーが定義されている
□ PITR(任意時点復元)が可能

【セキュリティ】
□ 最小権限の RBAC・IAM が設定されている
□ シークレットが適切に管理されている(コードに直接書いていない)
□ 通信が TLS で暗号化されている
□ 依存ライブラリの脆弱性スキャンを実施した
□ ペネトレーションテストまたはセキュリティレビューを実施した

週次SREオペレーションチェックリスト

毎週 月曜日 に実施:

【先週のレビュー】
□ 先週の SLO 達成状況を確認(全サービス)
□ 先週のインシデントをレビュー(件数・MTTR・根本原因)
□ アクションアイテムの進捗確認(ポストモーテムから)
□ エラーバジェットの消費状況を確認

【今週の準備】
□ 予定されているリリースの SRE リスク評価
□ 負荷が増える予定のイベント確認(キャンペーン・プロモーション等)
□ キャパシティの確認(増設が必要か)
□ オンコールシフトの確認・引き継ぎ

【継続的な改善】
□ アラートの精度確認(先週の誤検知・見逃しを確認)
□ ランブックの更新が必要か確認
□ 自動化の機会を1つ特定する

毎月 1日 に実施:
□ DR(災害復旧)訓練の計画・実施
□ バックアップリストアテストの確認
□ キャパシティプランのレビュー(翌月の計画)
□ SLO の見直し(目標値は適切か?)
□ エラーバジェットポリシーの確認
□ チームのトイル比率確認(50% 以下を維持)

Game Day(ゲームデー)実施テンプレート

Game Day は本物のインシデントが起きる前に、チームの対応能力を訓練するシミュレーションだ。カオスエンジニアリングの「Wheel of Misfortune」よりも大規模で、ビジネス影響も含めたシナリオを扱う。

# Game Day 計画書テンプレート

## 基本情報
- **実施日**: YYYY-MM-DD
- **時間**: 14:00〜17:00(3時間)
- **ファシリテーター**: [名前]
- **参加者**: [SREチーム + サービスオーナー + オプションで経営層]
- **シナリオ**: [例: 東京リージョン全断]

---

## 目標

1. フェイルオーバー手順の実際の所要時間を計測する
2. ランブックの正確性を検証する(ギャップを見つける)
3. チームの意思決定プロセスを観察・改善する
4. 参加者のスキルアップ

---

## シナリオ定義

**タイトル**: 本番データベースの突然のリージョン障害

**前提条件(既知のシステム状態)**:
- ap-northeast-1 のプライマリ RDS がダウン
- us-west-2 にレプリカあり(Aurora Global Database)
- DNS フェイルオーバーは自動ではなく手動設定

**ビジネス影響(シミュレーション)**:
- 決済機能が全停止
- 影響ユーザー: 全ユーザー
- 推定損失: ○○万円/時間

---

## タイムライン

| 時刻 | ファシリテーターのアクション | 参加者の期待行動 |
|------|---------------------------|----------------|
| 14:00 | シナリオ開始(アラートを手動発火) | アラートに応答・IC を宣言 |
| 14:05 | — | War Room 開設・役割分担 |
| 14:15 | ヒント: 「AWS Health Dashboard を確認」 | フェイルオーバー判断 |
| 14:30 | — | DB フェイルオーバー実施 |
| 14:45 | — | DNS 切り替え |
| 15:00 | ファシリテーターが「復旧確認」を宣言 | ステータスページ更新 |
| 15:00〜16:00 | デブリーフィング | — |

---

## 評価基準

| 指標 | 目標 | 実測 |
|------|------|------|
| IC 宣言までの時間 | 5分以内 | |
| 障害原因特定までの時間 | 15分以内 | |
| フェイルオーバー完了までの時間 | 30分以内 | |
| ステータスページ更新 | 発生から10分以内 | |
| コミュニケーション頻度 | 15分ごとに更新 | |

---

## デブリーフィング(振り返り)

実施後に必ず以下を議論する:

**うまくいったこと**:
- (記入)

**改善が必要なこと**:
- (記入)

**ランブックのギャップ**:
- (記入)

**次回 Game Day への改善アクション**:
| # | 内容 | 担当 | 期限 |
|---|------|------|------|
| 1 | | | |

## 重要ルール

- 実際の本番データは削除・変更しない
- 「アボート条件」を事前に設定する(SLO がX%を下回ったら即座に中断)
- 参加者はシミュレーションだと知っているが、リアルに対応する
- 事後の「批判なし」ルール: 失敗は学習機会

SLI (Service Level Indicator) と具体的なメトリクス定義

SLISLO を測定するための具体的な指標です。一般的な SLI は以下の4つのカテゴリに分かれます(Google の"Four Golden Signals")。

4つの Golden Signals:

シグナル 定義 測定方法 目標例
Latency(レイテンシ) リクエスト処理時間 p99, p95, p50 p99 < 500ms
Traffic(トラフィック) システムの負荷度 RPS(Requests Per Second) 10,000 RPS 処理可能
Errors(エラー率) 失敗リクエストの割合 5xx エラー率 99.9% 成功
Saturation(飽和度) リソース使用率 CPU, メモリ, ディスク CPU < 80%

具体的な SLI 設定例(Webサービス):

- Availability SLI: (成功したリクエスト数) / (全リクエスト数) >= 99.95%
- Latency SLI: p99 レスポンスタイム <= 200ms
- Completeness SLI: ユーザーが取得したデータの完全性 >= 99.9%
- Durability SLI: データ損失なし(RPO: Recovery Point Objective <= 1時間)

測定装置:

  • APM(Application Performance Monitoring): DataDog, New Relic, Prometheus
  • ブラウザ Real User Monitoring (RUM): Synthetic + Real を組み合わせ
  • アラート: SLO 違反を即座に検知する閾値設定

参考: Google “Site Reliability Workbook” Chapter 1、O’Reilly “The Art of Monitoring”


オンコール体制とポストモーテム文化

SRE チームの実務では、障害時の対応体制と事後分析が極めて重要です。

オンコール(On-Call)の職責:

  1. アラート対応 — 自動監視が検知した異常に即座に対応
  2. インシデント宣言 — 深刻度を判定し、エスカレーション判断
  3. 通信 — チャット・会議で進行状況を共有
  4. ページング — さらに人員が必要なら追加メンバーを呼び出し
  5. 復旧 — 速度より「安全な復旧」を優先

オンコール時間の効果的な管理:

方法 詳細
ローテーション チーム全体で負担を分散(例:月1回1週間)
ハンドオフ タイムゾーンをまたぐ場合、引き継ぎドキュメント作成
オンコール負荷上限 月1回以上のページングが生じたら、アラート改善が必須
報酬 オンコール手当てまたは代休を用意

ポストモーテム(事後検証)の流れ:

1. タイムライン記録
   - 異常検知時刻
   - 対応開始時刻
   - サービス復旧時刻
   - 完全復旧時刻

2. 根本原因分析(5 Why)
   「なぜが起きたのか」を5段階追跡

3. 行動項目(Action Items)
   - 即座(1週間): 再発防止の緊急対策
   - 短期(1ヶ月): モニタリング、アラート改善
   - 長期(四半期): アーキテクチャ改善

4. ブレームレス文化
   - 「誰が悪い」ではなく「システムをどう改善するか」に焦点
   - 未経験者のミスは学習機会と位置づけ

参考: Google “Site Reliability Workbook” Chapter 2(Postmortem Culture)


キャパシティプランニングとスケーリング戦略

システムが成長するなか、キャパシティ不足は SLO 違反につながります。

キャパシティプランニングのステップ

  1. 予測

    • 過去 6-12 ヶ月のトラフィック成長率を分析
    • 予定されたマーケティング活動・セール時期を考慮
    • 経営目標(新規顧客拡大など)を入力
  2. 設計容量

    • ピーク時トラフィックに対して 30-50% の余裕を持たせる
    • インフラ障害時のフェイルオーバーを想定
  3. 段階的導入

    • 新しいサーバー/リージョンをカナリアデプロイで検証
    • トラフィックを段階的に移行

スケーリングの2つのアプローチ:

アプローチ 特徴 利用シーン
垂直(Vertical) 既存マシンのスペック向上 即座の対応、単一障害点をつぶす
水平(Horizontal) マシン台数を増加 長期的スケール、冗長性確保

クラウドネイティブなスケーリング例(Kubernetes):

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-app-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-app
  minReplicas: 3
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 70
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80

参考: Kubernetes “Horizontal Pod Autoscaler” 公式ドキュメント、Google Cloud “Compute Engine: Scaling Groups”


まとめ

本ガイドは SRE の基礎概念から高度な実践技術まで、広範囲にわたってカバーした。

SRE の核心を3つの言葉でまとめると:

1. 測定する
   SLI/SLO でサービス品質を数値化する。
   測定できないものは改善できない。

2. 自動化する
   人間の判断が必要なことはなくならないが、
   繰り返しの作業はすべて機械に委ねる。
   50% ルールを守ることでエンジニアリング時間を確保する。

3. 学習する
   ブレームレスポストモーテムで障害から学び、
   カオスエンジニアリングで弱点を事前に発見する。
   完璧なシステムは存在しない。
   失敗から学び続けるシステムこそが真に信頼できる。

SRE は技術だけではない。組織・文化・コミュニケーションの融合だ。

良い SRE エンジニアは:

  • コードを書けるオペレーターであり
  • システムを理解するアーキテクトであり
  • 障害から学ぶ研究者であり
  • 組織の信頼性文化を育てるリーダーでもある

このガイドが、SRE としての成長の一助となれば幸いだ。

“The goal of SRE is not to eliminate failure — it’s to manage the consequences of failure.” — Google SRE Team


参考文献

公式・標準

書籍

解説・補助