決済インシデントのチャンネル — 誰が指揮し、何が抜けていたのか
目標
インシデントチャンネルの記録(チャット・呼び出し・注文)から、役割の流れ、宣言までにかかった時間、オーナーのいない決定をデータで見つけ出し、計算した数字で 決められた形式の状況告知を書きます。最後に、呼び出しが人に届かなかったインシデントを設定から直します。
なぜ重要か
インシデントコマンドは「うまくやろう」という心構えではなく、記録で確認できる習慣です。宣言まで何分かかったか、ICが手を動かしたか、 オーナーのいない決定がいくつあったかはチャンネルの記録にそのまま残り、それを数えてみるチームだけが次に改善します。状況告知の数字は 顧客と経営陣がそのまま信じる値なので、当て推量で書いてはいけません。そしてこれらすべては、呼び出しが人に届いて初めて始まります — 呼び出し設定は、インシデントが起きる前に直しておけるほぼ唯一の指揮の仕組みです。
ステップ
python3 /opt/fixtures/ic/ickit.py open rolesで決済インシデントの素材を展開し、/root/ic/roles/README.txtを読んでください。インシデントチャンネル(chat.jsonl)に書き込んだ人が何人いるか(ボットicbotは除き、同じ人は1回)を数え、/root/ic/roles/answers/participants.txtに数字1つで書きます。- このインシデントの最初のアラート(
pages.csvで名前がpay-で始まるアラートが最初にtriggeredになった時刻)から、(1) 最初の応答(pay-アラートの最初のacked)まで、(2) チャンネルの/declareまで、それぞれ何秒かかったかを/root/ic/roles/answers/declare.txtにack_delay_sec=<seconds>、declare_delay_sec=<seconds>の2行で書きます。 - インシデントが終わった時点(
/resolve)でIC・Comms・Opsをそれぞれ誰が担当していたかを、/root/ic/roles/answers/roles.txtにic: <name>、comms: <name>、ops: <name>の3行で書きます(名前は@なし)。 - ICが指揮を担当していたその時点で、自ら
/doingを打った決定を見つけ、/root/ic/roles/answers/ic-hands.txtに決定番号(D1の形)を1行に1つずつ書きます(該当する決定がなければnone)。ICを渡した後の元ICが行ったことはここに含めません。 /decideで記録された決定のうち、(1)/ownでオーナーが決まっていないものと、(2) 前の人が/doneする前に別の人が同じ決定を/doingしたもの(同時に手を付けたもの)を見つけ、/root/ic/roles/answers/work.txtにunowned: <decisions>、concurrent: <decisions>の2行で書きます(なければコロンの後を空にします)。- Commsが
/statusを打った時刻を基準に、最初の状況告知を/root/ic/roles/answers/status.txtに5行で書きます。1行目は[SEV<n>] <title> — status update(重大度と/declareのタイトル)、続いてtime: <YYYY-MM-DD HH:MM> KST(告知時刻、分まで)、impact: failed_orders=<N> affected_customers=<M> since=<HH:MM> KST(失敗した注文数・その顧客数・最初のアラート時刻)、command: ic=@<name> comms=@<name>(告知時刻のIC・Comms)、next_update: <YYYY-MM-DD HH:MM> KST(READMEの重大度別の間隔内)です。影響の数字は、最初のアラート時刻から告知時刻までのorders.csvから数えます。キーを韓国語で書いた以前の形式も受け付けます。 incident ic-slow-pageを実行するとインシデントが発生します。昨夜、payments・search・identityの3チームのアラートが1回ずつ鳴りましたが、3つとも最初の20分間、誰にも呼び出しが届きませんでした(呼び出し記録/var/log/pager/pages.log、設定/srv/pager/、シミュレーターpython3 /opt/fixtures/ic/pagersim.py <alert> <time>)。チームごとに原因を見つけ、いつ鳴っても0分でそのチームの当番に届くように直してください。ポリシーの最初の段階はそのチームの当番(oncall)を0分で呼び出すものとし、1人に1週間の当番の60%超を集中させず、people.csvの人は削除しません。当番は、インシデント発生前のpeople.csvにそのチームとして記載されていた人の中から割り当てます(新しい人を作ったり退職者を復活させたりしません)。連絡先は呼び出しサービスが使えるsms:<number>・voice:<number>・push:<id>・email:<address>の形でなければならず、ルーティングルールは直すときに変更せず追加します(元の名前のアラートも引き続きそのチームに届く必要があります)。/root/incident/ic-slow-page/report.txtにpayments: <code>、search: <code>、identity: <code>の3行とevidence: <pages.log-line>を書きます。コードはチームごとに次のいずれかです。ack-timeout(呼び出しは届いたが確認が遅れた)、contact-missing(当番の連絡手段が空だった)、override-departed(当番の交代が退職者を指していた)、route-mismatch(アラート名がルーティングルールと合わず、デフォルトポリシーに落ちた)、schedule-gap(当番表がその時刻をカバーしていない)。
参考
- 素材ツール:
python3 /opt/fixtures/ic/ickit.py open <name>が素材を/root/ic/<name>/に展開します。素材は人ごとに異なるランダムなシードで作られ、採点ツールは同じシードで正解を再計算します — 他人の数字は合いません。もう一度展開しても同じ素材で、--freshを付けると新しい素材で最初からやり直しになります。 - 答えは
/root/ic/<name>/answers/の下に書きます。セッションが終わると消えるので、必要なら別に保管してください。 - インシデントツール:
incident listで発生させられるインシデントと1行の症状を、incident show <name>でチケットを再表示します。同じインシデントを再び発生させると、新しいランダムな素材で最初の故障状態になります(先に書いた答えは合わなくなります)。 - 報告書は
/root/incident/<name>/report.txtです。対象ごとに<target>: <cause-code>を1行ずつ、最後にevidence: <line>をそのまま付けます。証拠は素材や観察記録にそのまま存在する1行(20文字以上)でなければなりません。採点ツールは修正後の実際の状態を先に確認し、報告書は判断を確かめるために使います。 - 素材のタイムゾーン:
pages.csvはUTC、chat.jsonlはKST(+09:00)、orders.csvはKSTですが表記がありません。 jq -r 'select(.user != "icbot") | .text' chat.jsonlのように人のメッセージだけを絞り込んで読むと、流れがよく見えます。- よくある間違い2つ — ボットの繰り返し(icbot)を人のコマンドとして数えること、そして文中にバッククォートで書かれたコマンドを本物のコマンドとして読むこと。
素材を展開する — チャンネルに誰がいたか
python3 /opt/fixtures/ic/ickit.py open rolesで決済インシデントの素材を展開し、/root/ic/roles/README.txtを読んでください。インシデントチャンネル(chat.jsonl)に書き込んだ人が何人いるか(ボットicbotは除き、同じ人は1回)を数え、/root/ic/roles/answers/participants.txtに数字1つで書きます。
1行がJSON 1つです。jqでuserの欄だけを抜き出し、重複をなくして数えます。ボットは人ではありません。
最初のアラートから応答・宣言まで
このインシデントの最初のアラート(pages.csvで名前がpay-で始まるアラートが最初にtriggeredになった時刻)から、(1) 最初の応答(pay-アラートの最初のacked)まで、(2) チャンネルの/declareまで、それぞれ何秒かかったかを/root/ic/roles/answers/declare.txtにack_delay_sec=<seconds>、declare_delay_sec=<seconds>の2行で書きます。
2つのファイルはタイムゾーンが異なります(pages.csvはUTC、チャットはKST)。同じ瞬間の単位に変換してから引いてください。pages.csvには、このインシデントと無関係なアラートも先に記録されています。date -dは両方の形式を読めます。
インシデント終了時の役割
インシデントが終わった時点(/resolve)でIC・Comms・Opsをそれぞれ誰が担当していたかを、/root/ic/roles/answers/roles.txtにic: <name>、comms: <name>、ops: <name>の3行で書きます(名前は@なし)。
READMEのコマンド規則どおりに数えます — メッセージが/で始まるときだけがコマンドで、同じ役割をもう一度割り当てたら、それは渡したということです。ボットの繰り返し、文中に書かれたコマンド、ボットが拒否した役割は役割の変更ではありません。
ICが自ら手を付けた決定
ICが指揮を担当していたその時点で、自ら/doingを打った決定を見つけ、/root/ic/roles/answers/ic-hands.txtに決定番号(D1の形)を1行に1つずつ書きます(該当する決定がなければnone)。ICを渡した後の元ICが行ったことはここに含めません。
チャンネルは時系列順です。上から読みながら「今のICは誰か」を変数1つに保持しておき、/doingを打った人と比べてください。
オーナーのいない決定と重複した作業
/decideで記録された決定のうち、(1) /ownでオーナーが決まっていないものと、(2) 前の人が/doneする前に別の人が同じ決定を/doingしたもの(同時に手を付けたもの)を見つけ、/root/ic/roles/answers/work.txtにunowned: <decisions>、concurrent: <decisions>の2行で書きます(なければコロンの後を空にします)。
決定ごとに「今誰がやっているか」を保持しておく必要があります。前の人が終えた後に確認のためにもう一度行ったものは重複ではありません。awkの連想配列やpythonのdictが便利です。
最初の状況告知を書く
Commsが/statusを打った時刻を基準に、最初の状況告知を/root/ic/roles/answers/status.txtに5行で書きます。1行目は[SEV<n>] <title> — status update(重大度と/declareのタイトル)、続いてtime: <YYYY-MM-DD HH:MM> KST(告知時刻、分まで)、impact: failed_orders=<N> affected_customers=<M> since=<HH:MM> KST(失敗した注文数・その顧客数・最初のアラート時刻)、command: ic=@<name> comms=@<name>(告知時刻のIC・Comms)、next_update: <YYYY-MM-DD HH:MM> KST(READMEの重大度別の間隔内)です。影響の数字は、最初のアラート時刻から告知時刻までのorders.csvから数えます。キーを韓国語で書いた以前の形式も受け付けます。
orders.csvの時刻はKSTですが、タイムゾーンの表記がありません — 最初のアラート(UTC)と比べるには同じタイムゾーンにそろえます。顧客数は重複なしで数えます。役割は告知時刻までの/roleだけを反映します。
インシデント — 3つのアラートが20分間誰も起こせなかった
incident ic-slow-pageを実行するとインシデントが発生します。昨夜、payments・search・identityの3チームのアラートが1回ずつ鳴りましたが、3つとも最初の20分間、誰にも呼び出しが届きませんでした(呼び出し記録/var/log/pager/pages.log、設定/srv/pager/、シミュレーターpython3 /opt/fixtures/ic/pagersim.py <alert> <time>)。チームごとに原因を見つけ、いつ鳴っても0分でそのチームの当番に届くように直してください。ポリシーの最初の段階はそのチームの当番(oncall)を0分で呼び出すものとし、1人に1週間の当番の60%超を集中させず、people.csvの人は削除しません。当番は、インシデント発生前のpeople.csvにそのチームとして記載されていた人の中から割り当てます(新しい人を作ったり退職者を復活させたりしません)。連絡先は呼び出しサービスが使えるsms:<number>・voice:<number>・push:<id>・email:<address>の形でなければならず、ルーティングルールは直すときに変更せず追加します(元の名前のアラートも引き続きそのチームに届く必要があります)。/root/incident/ic-slow-page/report.txtにpayments: <code>、search: <code>、identity: <code>の3行とevidence: <pages.log-line>を書きます。コードはチームごとに次のいずれかです。ack-timeout(呼び出しは届いたが確認が遅れた)、contact-missing(当番の連絡手段が空だった)、override-departed(当番の交代が退職者を指していた)、route-mismatch(アラート名がルーティングルールと合わず、デフォルトポリシーに落ちた)、schedule-gap(当番表がその時刻をカバーしていない)。
方向: 呼び出しが届くまでは、ルーティング → チームポリシーの段階 → その時刻の当番(交代表が当番表より優先) → その人の存在と連絡手段、の順に進みます。チームごとに、どの欄で途切れたかが異なります。 ツール: pages.logのFAILED・route行、シミュレーター(任意の時刻を入れて事前に回してみる)、grep、awk -F,。 具体: チームごとにインシデント時刻のルーティング行とlevel=1行を読み、その行が指すファイル(routes・people・overrides・schedule)からその時刻・その人の行を探してください。直した後は、その時刻だけでなく1週間のほかの時刻でもシミュレーターを回してみます。