Article

深夜3時の障害対応を100回経験して得た、インシデント対応の極意

高田晃太郎
深夜3時の障害対応を100回経験して得た、インシデント対応の極意

公開されている業界レポートや一般的な運用事例を見ても、深夜帯(とくに3時前後)の初動インシデントは一定割合を占め、復旧時間(MTTR: Mean Time To Recovery)が日中より延びやすい傾向があります¹²³。高パフォーマーチームは相対的に復旧が速いことが報告されており、SLO(Service Level Objective: サービス品質の目標)と運用プロセスの整合が成果を左右します¹²。CTOとして現場を見ていても、対応の良し悪しは手順の分厚さより、初動の設計と自動化の徹底、そして役割分担の明確さに集約されます。深夜帯は人的パフォーマンスが落ち、判断が鈍りやすい時間帯です³。だからこそ「人に頼らず、プロセスに頼る」準備が生死を分けます。

この記事では、深夜3時でも機能するインシデント対応の原則と、Web開発組織が明日から導入できる実装をコードで示します。アラート疲れ(alert fatigue)を減らし、誤検知を抑え、復旧までの経路を短縮するために、SLOベースの検知、Escalation(段階的な呼び出し)、ChatOps(チャットベースの運用)、ロールバック、自動化Runbook(実行可能な手順書)を一気通貫で繋ぎます²。体験談は最小限に留め、実装と指標にフォーカスします。

深夜に強いチームの原則は「人を守りつつ、顧客影響を最小化し、学びを残す」

インシデント対応の最優先は安全と影響の封じ込めです。現場では、責任者(インシデントコマンダー)、修復担当、コミュニケーション担当という三つの役割が必要ですが、その割り当てを当夜の人に毎回委ねると混乱が生じます。深夜帯でも機能したチームに共通して見られるのは、役割の自動割当と定型の宣言手順です。SlackやTeamsのテンプレートで、誰がインシデントコマンダーか、修復の技術責任者か、対外・対内の連絡担当かを起票直後すぐ(目安1分以内)に明示すると、以降の判断速度が大きく変わります⁴。

また、復旧の目標は必ずしも完全な修理ではなく、まずはサービスの安定化と顧客影響の遮断です。たとえばトラフィックを既知の安定版へ戻す、負荷を下げる、問題機能をフラグで無効化する、といった可逆的でリスクの小さい手段から着手し、恒久対応は落ち着いて日中に設計します。役割を先に固定化し、可逆な第一手を定型化しておくことが、結果として復旧時間の短縮につながると広く指摘されています⁶。

誤検知を削って初動の集中力を守る

深夜の誤検知は心身の負債を増やし、次の本番障害に響きます。SLOに紐づかないメトリクス単体の閾値アラートは、環境変化やスパイクで容易に鳴ります。SLOの短窓×長窓でのバーンレート監視(バーンレート=一定期間でSLOのエラーバジェットをどれだけ消費しているか)に切り替えると、顧客影響が実際に発生している可能性の高い時だけページングされます²。これによりアラートの実効性が上がり、初動の意思決定に集中できます。

「3時に動く」プロセス設計とコミュニケーション

プロセスは、検知、評価、隔離、復旧、周知、記録が途切れなく流れることが重要です。検知後は、影響範囲を短い文で定義します。例えば「JPリージョンのチェックアウトがタイムアウト、エラーレート3%増加、開始02:57、影響率最大8%」といった一文です。この短い定義が、暫定施策の安全性評価に役立ちます。以降は、サービスを守る仮説に基づき、影響を遮断できるスイッチ(カナリアの切り戻し=一部トラフィックのみの安全弁、Feature FlagのOFF、レート制限の強化)を順に試します。ここで重要なのは、ログとメトリクスの計測点が、施策前後で並べて比較できる構造になっていることです。

コミュニケーションはノイズを減らす設計にします。外部向けのステータスページは、約束した更新間隔で短く事実のみ。内部は専用チャンネルに集約し、要約の固定メッセージを更新します。役員報告と一次対応のチャンネルを分けると、現場の集中を守れます。夜間は人的リソースを増やせません。だからこそ、情報の粒度と頻度をパターン化し、決められたタイムボックスで更新することで意思決定コストを下げます⁵。

ポストモーテムで「学びの移転」を仕組みにする

深夜の成功・失敗を翌日に移転できないと、次も人に依存します。事実の時系列、影響指標、意思決定の根拠、機会損失の推定額、再発防止の優先度を一つのフォーマットに収め、レビューのタイムボックスを設定します。非難を避け、システムとプロセスの欠陥として扱う原則を守ると、レポートは改善の土台になります⁷。ひとつの運用例として、レビュー45分、翌営業日中に暫定施策、翌週内に恒久案レビューというペースを事前に決めておくと、次回の初動が明確になります。

実装編: アラート、Escalation、ChatOps、ロールバックをコードで繋ぐ

言葉の約束は眠気に負けます。動く仕組みはコードで用意し、テストできる形にします。以下は、現場でよく用いられる構成要素とその実装例です。すべて独立に導入でき、組み合わせるほど強くなります。

SLOバーンレートでページングするPrometheusルール

顧客影響のある時だけ鳴るよう、短窓と長窓の二段階でアラートを定義します²。

groups:
  - name: slo-burn
    rules:
      - alert: CheckoutErrorBudgetBurnFast
        expr: |
          (
            sum(rate(http_requests_total{app="checkout",status=~"5.."}[5m]))
            /
            sum(rate(http_requests_total{app="checkout"}[5m]))
          ) > 0.02
          and
          (
            sum(rate(http_requests_total{app="checkout",status=~"5.."}[1h]))
            /
            sum(rate(http_requests_total{app="checkout"}[1h]))
          ) > 0.01
        for: 10m
        labels:
          severity: page
        annotations:
          summary: "Checkout SLO burning (fast/slow)"
          runbook: "https://runbook.example.com/checkout-slo"

短窓で急激な悪化、長窓で持続的な悪化を同時に満たした時だけページします。RunbookのURLを必ず添えて、起票直後の行動を固定化します。

TerraformでPagerDutyのサービスとエスカレーションをコード化

深夜の連絡網を人事異動に合わせて手で直すのは危険です。IaC(Infrastructure as Code)で定義し、レビューとテスト可能にします⁴。

provider "pagerduty" {
  token = var.pd_token
}

resource "pagerduty_escalation_policy" "checkout" {
  name = "Checkout Escalation"
  num_loops = 2
  rule {
    escalation_delay_in_minutes = 10
    target {
      type = "user_reference"
      id   = var.primary_oncall_user_id
    }
  }
  rule {
    escalation_delay_in_minutes = 10
    target {
      type = "schedule_reference"
      id   = var.team_schedule_id
    }
  }
}

resource "pagerduty_service" "checkout" {
  name                    = "Checkout"
  auto_resolve_timeout    = 14400
  acknowledgement_timeout = 900
  escalation_policy       = pagerduty_escalation_policy.checkout.id
}

承認タイムアウトやループ回数を明示すると、夜間の取りこぼしが減ります。変更はPull Requestで可視化し、監査しやすくなります。

ChatOpsでインシデント起票と役割の自動割り当て

Slackのスラッシュコマンドから起票し、役割を即時に宣言します。人的な呼びかけをやめ、ボットがテンプレートを展開します⁵。

import os
import slack_bolt
from slack_bolt.adapter.socket_mode import SocketModeHandler

app = slack_bolt.App(token=os.environ["SLACK_BOT_TOKEN"]) 

@app.command("/incident")
def create_incident(ack, body, client):
    ack()
    title = body.get("text", "") or "Untitled Incident"
    channel = client.conversations_create(name=f"inc-{title[:20].lower().replace(' ', '-')}")
    ch_id = channel["channel"]["id"]
    client.chat_postMessage(
        channel=ch_id,
        text=(
            f"🔥 Incident: *{title}*\n"
            "IC: <@PRIMARY>  Ops: <@SECONDARY>  Comms: <@COMMS>\n"
            "Status: Investigating | Next update: 10 min\n"
            "Runbook: https://runbook.example.com/incident"
        ),
    )

if __name__ == "__main__":
    SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()

役割のプレースホルダーはオンコールAPIやシフト表から置換します。起票と同時に次回更新時刻を宣言し、以降はボットが更新リマインドを送ります。

可逆なロールバックとメンテナンス入口の用意

失敗の少ない復旧は、既知の安定版へ戻すことです。KubernetesのロールバックをRunbookから一撃で実行できるようにします。

#!/usr/bin/env bash
set -euo pipefail
app=${1:?"app name required"}
ns=${2:-"checkout"}

kubectl rollout undo deploy/${app} -n ${ns}

kubectl rollout status deploy/${app} -n ${ns} --timeout=120s || {
  echo "rollback status check failed" >&2
  exit 1
}

echo "rolled back ${app} in ${ns}"

アプリ全体を落とさず顧客影響を抑えるため、読み込み専用のバナーや一時的なレート制限も入口で用意します。

server {
  listen 443 ssl;
  location /checkout {
    if ($request_method = POST) { return 503; }
    add_header Retry-After 120 always;
    try_files $uri @app;
  }
  location @app { proxy_pass http://app; }
}

Feature Flagで問題機能を即時無効化

デプロイ不要で機能を止められると、復旧の第一手が増えます。永続化は単純なテーブルでも充分です。

CREATE TABLE feature_flags (
  key TEXT PRIMARY KEY,
  enabled BOOLEAN NOT NULL,
  updated_at TIMESTAMP NOT NULL DEFAULT now()
);

-- Disable risky coupon module
UPDATE feature_flags SET enabled = false WHERE key = 'coupon_v2';

アプリ側は読み取り失敗時に安全側に倒す実装にしておきます。ネットワーク分断時でも危険な機能が動かないようにします。

import { getFlag } from "./flags";

export async function applyCoupon(req, res) {
  const flag = await getFlag("coupon_v2").catch(() => ({ enabled: false }));
  if (!flag.enabled) {
    return res.status(503).json({ message: "Temporarily unavailable" });
  }
  // ... normal path
}

指標とROI: MTTR短縮を事業価値に接続する

技術だけでなく、意思決定を後押しする数字が必要です。SLOは顧客体験に紐づく指標で定義し、たとえば「チェックアウト成功率99.9%(30日移動)」のように記述します。違反時に消費されるエラーバジェットは、新機能投入の判断と連動させます。違反が続いたらリリースを減速し、信頼性の投資を優先するという運用ルールです²。

ROIの見積もりは、ダウンタイム1分あたりの損失を、直接売上の逸失、将来の離脱(コンバージョン低下の回帰係数から推定)、SLA違反ペナルティ、対応人件費の合算で置きます。例えばピークトラフィック時にチェックアウトが3%失敗し、20分継続した場合、売上レートが分あたり200万円なら単純損失は1200万円になります。ここにペナルティや将来の再訪率低下を仮の前提で素朴に加えていくと、インシデント自動化への投資判断が定量的に説明できます⁸。これらの施策を組み合わせることで復旧経路が可視化され、夜間のページング回数の削減や復旧時間の短縮につながることは、各社の公開事例やガイドでも示されています¹⁵⁶。

テストと演習で「眠ったままでも動く」状態へ

計画は演習で磨かれます。予告ありのゲームデイ(障害対応の模擬訓練)から始め、段階的に難易度を上げていきます。初回は読み合わせでも構いません。重要なのは、Runbookに曖昧さが残っていないか、権限と秘密情報が夜間でも取得できるか、計測点が施策前後を明確に分けられているか、を確認することです。演習ログはポストモーテムと同じフォーマットで残し、学びが散逸しないようにします。

まとめ

深夜3時の現場で頼れるのは、個人の気合いではなく、起票から復旧までを繋ぐ仕組みです。SLOに基づく検知でノイズを減らし、役割の自動宣言で初動を揃え、可逆なロールバックとFeature Flagで被害を食い止め、ChatOpsでコミュニケーションを定型化する。この連鎖ができると、眠い夜でも判断は軽くなります。今日紹介した実装は、小さく始めて積み上げられるものばかりです。

インシデント対応は訓練可能なチームスポーツです。あなたの組織でまず一つ、SLOアラートかChatOps起票のどちらかを選び、来週のゲームデイで動かしてみてください。数週間後、深夜の通知音に感じる重さが少し変わるはずです。そして、次の改善をまたコードで積み上げましょう。

参考文献

  1. Google Cloud. Announcing DORA 2021 Accelerate State of DevOps Report. https://cloud.google.com/blog/products/devops-sre/announcing-dora-2021-accelerate-state-of-devops-report
  2. Google SRE Book (O’Reilly日本語版). サービスレベル指標とエラーバジェット(アラート設計を含む). https://www.oreilly.com/library/view/sre/9784873119137/ch05.xhtml
  3. Rupp TL, et al. Effects of simulated night shifts on performance and alertness (PVT). https://pmc.ncbi.nlm.nih.gov/articles/PMC7241942/
  4. PagerDuty. What Is an Incident Commander? https://www.pagerduty.co.jp/blog/what-is-incident-commander/
  5. Atlassian. ChatOps によるインシデント管理. https://www.atlassian.com/ja/incident-management/devops/chatops
  6. Atlassian. インシデント管理のKPI(MTTR など). https://www.atlassian.com/ja/incident-management/kpis
  7. Google SRE Book. Postmortem Culture(Blameless Postmortems). https://sre.google/sre-book/postmortem-culture/
  8. TechTarget. The cost of downtime and how businesses can avoid it. https://www.techtarget.com/searchdatabackup/feature/The-cost-of-downtime-and-how-businesses-can-avoid-it