AWS SSM Automation IR Runbook Aurora EKS S3 事後分析 本番

目次

1. この記事について — 復旧・運用編シリーズ Vol3

fig01: IR全体アーキテクチャ + SSM Automation連携

【復旧・運用編シリーズ Vol1-4 ラインナップ】

【この記事で学ぶこと 3 つ】

  • SSM Automation Document × IR Runbook を Terraform で完全 IaC 化し、検知から復旧まで全フェーズを自動化する
  • Aurora・仮想マシンとコンテナ Pod・S3 脅威検知 の 3 大障害シナリオを自動復旧する Runbook を実装する
  • Amazon Bedrock を活用した 事後分析 自動生成と Slack・Email・GitHub PR への配信を完全自動化する

1-1. IR Runbook 自動化の北極星

深夜 3 時に Aurora フェイルオーバーが発生したとき、人が手順書を開いて作業を始めた段階で、すでに 15 分が経過している。SLO が 99.9% (月間ダウンタイム許容 43 分) の本番サービスであれば、手動対応 1 件で月次バジェットの 3 分の 1 を消費する計算だ。Vol1 (Multi-Region Backup/DR) で設計した Cross-Region Backup/DR 構成は「どこへ切り戻すか」を明確にしたが、「誰が・いつ・どの手順で」実行するかは依然として人に委ねられていた。Vol2 (Chaos Engineering with AWS FIS) はその障害シナリオを機械で再現し、DR 構成が正しく動作することを証明した。

本 Vol3 はそのクローズドループを完結させる。Vol2 の FIS Experiment が起こした障害を、SSM Automation Document が検知・診断・復旧まで自動で完結させる。人が画面を開く前にシステムが自分自身を修復する状態を Terraform で完全 IaC 化することが本記事の北極星だ。

AWS SSM Automation は Lambda と異なり、実行ステップのビジュアル確認・承認ステップの挿入・Stop Condition による緊急停止を Document として宣言的に定義できる。Lambda は柔軟だが「なぜこのタイミングで起動し、どの順序で何を実行したか」をコードを読まずに把握することが難しい。SSM Automation Document は YAML で可読なフローを定義し、Systems Manager コンソールで実行履歴と各ステップの入出力を確認できる。本番障害対応において「機械が何をしたか」を人が追跡できることは、手動対応と同等かそれ以上に重要だ。

SSM Automation の特に強力な点は、aws:executeAutomation を使ったサブドキュメント呼び出しによる再利用性だ。Aurora 復旧・EKS Pod 再スケジュール・S3 隔離の 3 シナリオを独立した Document として定義し、メイン Document からシナリオに応じて呼び分けることで、コードの重複を排除しつつ運用シナリオを安全に拡張できる。

自動化の三本柱は以下の通りだ。

MTTR 短縮: 検知 (CloudWatch Alarm / 脅威検知 Finding / FIS 完了イベント) から EventBridge を経由して SSM Automation が起動し、診断→復旧→通知まで 1 つの Document で完結させる。人の判断を介さないため、MTTR を従来の 30 分超から 5 分以下へ短縮できる。

RACI Matrix 制度化: Incident Commander・Tech Lead・Communications・Scribe の 4 役割を SSM Document の Parameter と IAM Role で IaC 化する。Severity 1 (全断) から Severity 4 (軽微) まで Stop Condition と Cooldown を自動適用することで、過剰な自動化がさらなる障害を引き起こすリスクを排除する。

事後分析 自動生成: Amazon Bedrock (Claude) が SSM Automation の実行ログと CloudWatch Logs Insights の分析結果を入力に受け取り、構造化された 事後分析 を Markdown で生成する。生成されたドキュメントは Slack・Email・GitHub PR への自動配信まで含めて完全自動化する。

3 大障害シナリオ (Aurora / 仮想マシンとコンテナ Pod / S3 脅威検知) を単一の Terraform コードベースで実装し、本番環境への直接適用を可能にすることが本記事の核心だ。本 Vol3 を読み終えたとき、手順書を開く必要のない IR 基盤が手元に揃っている状態を目指してほしい。

なお本 Vol3 と既存の AWS Systems Manager Incident Manager は共存する。Incident Manager は「インシデント管理プロセス (タイムライン記録・担当者割当・エスカレーション)」を担い、本記事の SSM Automation Document は「技術的な自動復旧操作 (API 呼び出し・スクリプト実行・リソース操作)」を担う。2 つは補完関係にあり、EventBridge で連結することで検知・復旧・記録の全フェーズを自動化できる。

1-2. 読者像と前提条件

対象読者: AWS Systems Manager の基本操作 (Run Command・Session Manager) を使った経験があり、Terraform で AWS リソースを管理した経験を持つエンジニアを対象とする。インフラエンジニア・SRE・Platform Engineer として本番環境の運用責任を持ち、障害対応の自動化に取り組んでいる方に特に有用な内容だ。

前提知識:

  • AWS Systems Manager: Document の概念 (Run Command との違い、Automation の起動方法) を理解していること
  • Terraform: aws_iam_roleaws_cloudwatch_metric_alarm 程度のリソース定義を書いた経験があること
  • Amazon CloudWatch: メトリクスアラームと EventBridge ルールの基本連携を理解していること
  • シリーズ前巻: Vol1 (Multi-Region Backup/DR) と Vol2 (Chaos Engineering with AWS FIS) を読んでいると §4 の Aurora 連携シナリオの理解が深まるが、本 Vol3 は単独でも完結する構成にしている

使用する主要 AWS サービスと Terraform リソース:

AWS サービス主要 Terraform リソース本記事での用途
AWS Systems Manageraws_ssm_documentIR Runbook 定義の中核
Amazon EventBridgeaws_cloudwatch_event_rule, aws_cloudwatch_event_targetアラームから自動化起動へのトリガー
Amazon CloudWatchaws_cloudwatch_metric_alarm, aws_cloudwatch_composite_alarm3 大シナリオの障害検知
脅威検知サービス (脅威検知サービス (Amazon 脅威検知))(既存検知を活用)S3 アクセス異常の検知
Amazon Bedrockaws_lambda_function (InvokeModel 呼び出し経由)事後分析 自動生成エンジン
Amazon EKS(既存クラスターを活用)コンテナ Pod 障害の復旧対象
Amazon Aurora(既存グローバルクラスターを活用)DB フェイルオーバー自動復旧対象
Amazon SNSaws_sns_topic, aws_sns_topic_subscription通知ハブ

AWS リージョン: ap-northeast-1 (東京) を主リージョンとし、Aurora Global Database の場合は us-east-1 をセカンダリとして使用する例で解説する。Amazon Bedrock の Model access は us-east-1 または us-west-2 での事前承認が必要なため、デプロイ前に AWS コンソールの Bedrock セクションでモデルアクセスを有効化しておくこと。

Terraform・ツールバージョン:

ツール動作確認バージョン備考
Terraformv1.7 以上aws_cloudwatch_composite_alarm は v4.x プロバイダーから安定
AWS Providerv5.40 以上aws_ssm_documentdocument_type = "Automation" 必須
Python3.11SSM Automation の aws:executeScript ランタイム
AWS CLIv2.15 以上aws ssm start-automation-execution--parameters 形式

IAM 前提条件: 本記事のサンプルコードを実行するには、AmazonSSMFullAccessAmazonEventBridgeFullAccessAmazonBedrockFullAccessAmazon脅威検知ReadOnlyAccess を持つ IAM ユーザーまたはロールが必要だ。本番環境では最小権限の IAM ポリシーを使うこと。§3 で最小権限 IAM Role の Terraform 実装を詳解する。

1-3. 12 巻完結シリーズと復旧・運用編 4 巻の連結

本記事は全 14 リソースで構成するシリーズの 13 番目にあたる。各シリーズは独立して読めるが、クロスリンクを設けることで実装の文脈を相互補完している。

シリーズ主な参照§連携内容の概要
EKS 本番運用 3 部作 (Vol2・Vol3)§5IRSA 設計と ArgoCD 自動 Rollback 実装を参照
Lambda 本番運用 3 部作 (Vol2・Vol3)§7事後分析 生成 Lambda の共通ライブラリと Powertools 活用を参照
観測性 3 部作 (Vol1・Vol2・Vol3)§3・§4・§5SSM 実行ログ分析・Cross-Region 分散トレーシング・SLO Burn Rate Alarm 連携を参照
セキュリティ 3 部作 (Vol1・Vol2・Vol3)§3・§6Permission Sets 最小権限・脅威検知 Finding 連携・Conformance Pack 準拠を参照
復旧・運用編 4 部作 (Vol1・Vol2・本 Vol3・Vol4 予告)§1・§4Aurora DR 構成 (Vol1) を FIS で証明 (Vol2) し SSM Automation で自動復旧 (本 Vol3) するクローズドループ

Vol3 単独でも「IR Runbook × SSM Automation の Terraform 完全実装」として完結するように設計しているが、シリーズを通して読むことで CloudWatch Alarm → 脅威検知 → FIS → SSM Automation の全体像が立体的に把握できる。§8 に全 14 リソースの双方向クロスリンク表を掲載しているため、関連記事への導線としても活用してほしい。

本記事で扱う「3 大障害シナリオ」はシリーズ全体の集大成でもある。Aurora シナリオは観測性 Vol1 (CloudWatch Logs Insights) と観測性 Vol2 (X-Ray 分散トレーシング) で培った分析基盤の上に成り立ち、EKS Pod シナリオは EKS Vol2 (IRSA 設計) と EKS Vol3 (ArgoCD 自動 Rollback) の実装と連携する。S3 脅威検知 シナリオはセキュリティ Vol1 (脅威検知) と セキュリティ Vol3 (Conformance Pack) の検知ルールを SSM Automation のトリガーとして再活用する。個々の記事で実装した AWS リソースが本 Vol3 で有機的に連結し、「検知→自動復旧→事後分析」までの一気通貫フローを完成させる。

次巻 Vol4 (Multi-Region Active-Active vs Active-Passive 設計実装) では、本 Vol3 で確立した SSM Automation による自動復旧基盤を Active-Active アーキテクチャへ拡張する。§8 の Vol4 予告で詳細を先行公開している。

1-4. 読み進め方ガイド

以下の順で読み進めることを推奨する。

§Nタイトル所要時間の目安読み飛ばし可否
§1この記事について (本節)5 分必読
§2前提・IR 設計原則・RACI Matrix・Severity 階層10 分推奨
§3SSM Automation 仕組み + Document YAML + IAM・EventBridge 連携20 分各シナリオ実装前に必読
§4シナリオ1 Aurora 障害自動復旧 Runbook20 分Aurora 未使用なら読み飛ばし可
§5シナリオ2 仮想マシンとコンテナ Pod 障害自動診断と復旧20 分EKS 未使用なら読み飛ばし可
§6シナリオ3 S3 アクセス異常→自動隔離と再開15 分脅威検知 未使用なら読み飛ばし可
§7事後分析 自動生成 (Bedrock 連携)20 分Bedrock 未承認なら後回し可
§8まとめ + Vol4 予告 + 落とし穴 10 選10 分必読

まず全体を把握したい場合: §1 → §2-1 (IR 設計原則) → §8 (まとめとチートシート) の順に読む。各章の Quality Gate (ep-box) が実装の要点を整理しているため、コード量の多い §3〜§7 は QG を先に読むと文脈が掴みやすい。

Aurora 障害自動復旧だけを実装したい場合: §3 (SSM Automation 仕組み) → §4 (Aurora シナリオ) の順に進む。Vol1 (Multi-Region Backup/DR) の Aurora Global Database 構成が前提になるため、未導入の場合は先に Vol1 を参照すること。

脅威検知 と S3 自動隔離を先に試したい場合: §3 → §6 へ進む。脅威検知 を有効化済みの環境であれば §6 単体で動作確認できる。

事後分析 自動生成だけを導入したい場合: §7 は他シナリオとほぼ独立している。Bedrock の Model access を事前取得済みであれば §7 単独で実装を試せる。

§2 では IR 設計原則と RACI Matrix を、§3 では SSM Automation の仕組みを基礎から解説する。ここを押さえると §4〜§7 の各シナリオが一層理解しやすくなる。§8 にはチートシートと「落とし穴 10 選」を掲載しているため、実装後のレビューや本番投入前の確認リストとして活用してほしい。


2. 前提・環境・準備

2-1. IR 設計原則

本記事が対象とする本番環境向け IR (Incident Response) の設計は、SRE 分野で確立された三つの柱で構成されます。

MTTR 最小化 は最優先指標です。障害を検知してから復旧完了までの時間を短縮するため、手動オペレーションの介入を段階的に排除し、検知→診断→復旧の各フェーズを自動化チェインで接続します。Aurora フェイルオーバーを例にとると、CloudWatch Alarm 発火から Route 53 切り替え完了まで人手を挟まず 5 分以内に収める設計を §4 で実装します。

自動化優先 は、Runbook を SSM Automation Document として IaC 化する基本方針です。Runbook がコードとして Git 管理されることで、変更履歴・レビュー・テスト・本番適用の CI/CD パイプラインと統合できます。繰り返し発生するインシデントを最初の 1 回で完全に自動化し、2 回目以降はゼロタッチで復旧できる状態を目指します。

学習サイクル は、発生したインシデントをシステムの改善に転換する仕組みです。SSM Automation の実行ログを S3 に集約し、Amazon Bedrock が自動生成した 事後分析 をチームで共有することで、根本原因分析 (RCA) を習慣化します。§7 ではこの 事後分析 自動生成パイプラインを Terraform で完全実装します。

前提ツールバージョン:

  • AWS CLI v2.15 以上
  • Terraform v1.7 以上 / AWS Provider v5.40 以上
  • Python 3.11 以上 (Lambda 事後分析 生成関数)
  • Terraform で管理する AWS リソースの作成・変更・削除権限を持つ IAM ユーザーまたはロール
  • Amazon Bedrock のモデルアクセス承認 (Claude / Amazon Nova のいずれか、us-east-1 または ap-northeast-1)

本記事の Terraform コードは ap-northeast-1 リージョンを前提とし、マルチリージョン構成が必要な場合は provider ブロックにエイリアスを追加して対応します。

EKS シナリオを実装するには、EKS クラスターで Container Insights を事前に有効化しておく必要があります。eksctl enable cloudwatch-observability コマンドまたは EKS アドオン amazon-cloudwatch-observability を Terraform の aws_eks_addon リソースで追加してください。Container Insights が有効でない場合、§5 の node_cpu_utilization メトリクスが CloudWatch に送信されず Composite Alarm が機能しません。


2-2. RACI Matrix の四つの役割

インシデント対応における役割分担を RACI Matrix で定義します。R=Responsible (実行責任)、A=Accountable (説明責任)、C=Consulted (相談対象)、I=Informed (情報共有先) を各アクティビティに明示することで、Sev1 重大インシデント時の意思決定速度を上げます。

【RACI Matrix — Sev1 重大インシデント】

アクティビティIncident CommanderTech LeadCommunications LeadScribe
インシデント宣言・Sev 判定A/RCII
SSM Automation 起動承認ARII
技術診断・復旧手順選定CA/RII
ステークホルダー通知AIRI
タイムライン記録IIIA/R
Stop Condition 判断A/RCII
事後分析 ドラフト作成ARCR
事後分析 レビュー承認A/RCCI

Sev1 インシデントでは Incident Commander の承認ステップを SSM Automation Document 内に自動挿入します。aws:approve アクション を使うと、指定した IAM ユーザー・ロール・SNS トピックへ承認リクエストを送付し、承認されるまで後続ステップを待機させられます。承認タイムアウト (デフォルト 3600 秒) を超えた場合は自動的に Abort に切り替わり、Incident Commander へエスカレーション通知が飛ぶ設計を §3-3 で実装します。


2-3. Severity 階層と SLO・MTTR 目標

Severity 階層は S1〜S4 の 4 段階で定義します。各レベルに対応する RTO 目標、通知先、SSM Automation の Stop Condition を設定することで、インシデントの深刻度に比例した対応速度と自動化範囲を実現します。

Sev定義RTO 目標通知先Stop Condition
S1売上直結サービス完全停止 / データ消失リスク15 分以内Incident Commander + 経営層 + PagerDuty Critical3 回失敗で自動停止 + 人手エスカレーション + Incident Commander 承認必須
S2主要機能の部分停止 / SLO 違反1 時間以内Tech Lead + On-call Engineer + PagerDuty Warning5 回失敗で自動停止 + Tech Lead 通知
S3非主要機能の劣化 / 警告アラート4 時間以内On-call Engineer (Slack)自動リトライ 3 回後にチケット起票
S4軽微な異常 / 情報通知翌営業日Slack チャンネル通知のみ自動修復のみ・エスカレーションなし

S1/S2 の判定は EventBridge Rule の detail.severity フィールドを aws:branchStringEquals で評価し、通知先と Stop Condition を動的に切り替えます。S3/S4 は EventBridge の detail.severity でフィルタリングして別の SSM Document を起動するか、CloudWatch Alarm の TreatMissingData 設定で静的対応を選択します。

SLO 違反の検知には Application Signals の SLO Burn Rate Alarm を活用します。Error Budget が 5% 以下になった段階で S2 宣言し、自動復旧 Runbook を起動する設計により SLO 違反を未然に防ぎます。詳細は 観測性 Vol3 Application Signals SLO 本番運用 を参照してください。


2-4. 安全策

自動化 Runbook を本番環境で安全に運用するため、三層の安全策を設けます。

Stop Condition は SSM Automation 実行中に異常を検知した場合に自動停止するルールです。各 Step に timeoutSecondsonFailure: Abort を設定し、連続失敗回数が閾値を超えた場合は aws:executeScript で PagerDuty への通知とロールバック Step を呼び出します。フロントエンドの変更を自動ロールバックする際は Deployment 操作前に必ず CloudWatch Alarm を参照し、回復傾向が見えない場合のみ実行する二段階確認を組み込みます。

Rollback 設計 は、Aurora フェイルオーバーや S3 Bucket Policy 変更などの操作を元に戻せる手順を Document 内に事前実装する考え方です。mainSteps の末尾に RollbackStep を定義し、正常終了時はスキップし失敗時のみ自動実行する分岐を aws:branch で制御します。特に §4 の Aurora フェイルオーバーでは Switchover 後のプライマリ昇格確認をロールバック条件に組み込み、不完全な状態でオートメーションが完了しないよう設計します。

Dry-run 実行 は、本番適用前に実際のリソースを変更せず処理フローのみ検証するモードです。SSM Automation Document の Parameter に DryRun (Boolean) を追加し、aws:branchChoose 条件で分岐させます。CI パイプラインでは常に DryRun: true でテストし、本番 EventBridge からは DryRun: false で起動します。

# Dry-run モードで実行確認
aws ssm start-automation-execution \
  --document-name "IRRunbook-Aurora" \
  --parameters '{"DryRun":["true"],"Severity":["S2"]}'

# Dry-run 結果確認
aws ssm get-automation-execution \
  --automation-execution-id <execution-id> \
  --query 'AutomationExecution.{Status:AutomationExecutionStatus,Steps:StepExecutions[*].{Name:StepName,Status:StepStatus}}'

Dry-run Step では aws:executeScript でシミュレーション結果をログ出力するだけに留め、実際の API コール (aws:executeAwsApi) はすべてスキップします。本番で Dry-run を省略して即実行が必要な緊急事態では Incident Commander が RACI Matrix に基づいて承認し、実行後に 事後分析 で振り返りを実施します。

Cooldown Period は、自動復旧 Runbook が短時間に繰り返し起動するループ状態を防ぐための待機時間です。EventBridge Rule の event_patternpreviousState: OK フィルタを追加すると、INSUFFICIENT_DATA → ALARM への遷移では起動せず OK → ALARM の遷移のみ対象にできます。さらに EventBridge の「Cool-down period」設定で同一 Rule の最小発火間隔を 5〜15 分に設定することを推奨します。Aurora フェイルオーバーが完了した直後に別のアラームが発火して二重起動するケースは、この Cooldown 設定で防止できます。

非本番環境でのテスト戦略 として、Staging 環境で実際に SSM Automation を週次で Dry-run 実行し、Step の正常完了を CI パイプラインで検証します。aws ssm start-automation-execution の終了コードと get-automation-executionAutomationExecutionStatusSuccess であることをアサートする形でテストを自動化できます。


3. SSM Automation の仕組み

3-1. SSM Automation アーキテクチャ全体像

AWS Systems Manager Automation は、AWS リソースへの操作手順を Document (YAML または JSON) として定義し、EventBridge や Lambda・ AWS CLI から起動できるマネージドオーケストレーションサービスです。EC2 の定期パッチ適用から本番障害の自動復旧まで、幅広いユースケースに対応します。

本記事で実装する IR Runbook の起動チェインは次のとおりです。

  1. トリガー検知: CloudWatch Alarm (EC2/EKS) / 脅威検知 Finding (S3) / AWS FIS Experiment 完了通知 (Aurora) が EventBridge に転送される
  2. EventBridge Rule: detail-typedetail.severity でフィルタリングし、対応する SSM Automation Document を起動する
  3. SSM Automation 実行: assumeRole で指定した IAM ロールを引き受け、mainSteps に定義した Step を順次実行する
  4. Step 実行: aws:executeAwsApi / aws:executeScript / aws:branch を組み合わせて診断・復旧・通知する
  5. 結果記録: 実行ログを CloudWatch Logs の /aws/ssm/automation/{document-name} に書き込み、S3 にも同期して 事後分析 の材料とする

Automation Document は schemaVersion: "0.3" が現在の最新バージョンです。バージョン "0.3" では aws:executeScript で Python 3.11 / PowerShell 7.x が使用でき、外部パッケージを含む zip アップロードにも対応しています。旧バージョン "0.2" との最大の差異は assumeRole のサポートと aws:branch の条件評価機能の拡充です。

Automation Document は AWS マネジメントコンソールの Systems Manager > Automation > Documents に保存され、バージョン管理が可能です。Terraform で管理する場合は aws_ssm_document リソースを使い、content に YAML/JSON を直接インライン定義するか file() 関数でファイルを参照します。ドキュメントの更新は自動的に新バージョンとして記録され、EventBridge ターゲットには $DEFAULT バージョンが参照されます。


3-2. Document Schema 5 要素

【QG-1: SSM Automation 5 要素マトリクス】

要素Document 内のキー必須属性Terraform リソース役割
DocumentschemaVersion / description / parameters / mainStepsschemaVersion: "0.3" / document_type: "Automation"aws_ssm_documentRunbook 全体の定義。Step と Parameter を束ねる
StepmainSteps[].name / action / inputsaction (aws:executeAwsApi 等) / timeoutSeconds / onFailureDocument 内 YAML/JSON で定義個々の操作単位。順次・分岐・並列を制御
Parameterparameters.{name}.type / default / allowedValuestype (String / StringList / Boolean / Integer / MapList)Document 内 YAML/JSON で定義実行時に外部から注入する動的な入力値
IAM RoleassumeRole: "{{ AutomationAssumeRole }}"Principal: ssm.amazonaws.com / 最小権限ポリシー添付aws_iam_role / aws_iam_role_policyAutomation が AWS API を呼び出す際に引き受けるロール
EventBridgeEventBridge Rule の Targets[].Arnarn:aws:ssm:{region}:{account}:automation-definition/{document-name}aws_cloudwatch_event_rule / aws_cloudwatch_event_targetトリガーソース (Alarm / 脅威検知 / FIS) から Automation を自動起動

IAM Role の最小権限設計については セキュリティ Vol2 IAM Identity Center Permission Sets と ABAC 実践 で解説している Permission Sets パターンを SSM Automation にも適用します。


3-3. Step Type 詳説

SSM Automation Document が利用できる主要な Step Type (action) とその使い分けを解説します。

aws:executeAwsApi は AWS SDK の API を直接呼び出す最も基本的な Step です。ServiceApi を指定し、レスポンスを outputs で後続 Step に渡せます。Aurora フェイルオーバーの rds:FailoverDBCluster や S3 の s3:PutBucketPolicy など、単一 API 呼び出しで完結する操作に最適です。

- name: failover_aurora
  action: aws:executeAwsApi
  timeoutSeconds: 300
  onFailure: step:rollback_aurora
  inputs:
 Service: rds
 Api: FailoverDBCluster
 DBClusterIdentifier: "{{ DBClusterIdentifier }}"
  outputs:
 - Name: cluster_status
Selector: $.DBCluster.Status
Type: String

aws:executeScript は Python 3.11 または PowerShell 7.x のスクリプトを Lambda なしで実行できる Step です。複数 API の呼び出し・条件判定・ループ処理など、aws:executeAwsApi 1 回では表現できないロジックに使います。スクリプトは Script にインライン記述するか Attachment で zip をアップロードします。

- name: check_rto
  action: aws:executeScript
  inputs:
 Runtime: python3.11
 Handler: check_rto_handler
 Script: |
import boto3, time
def check_rto_handler(events, context):
 start = float(events["StartEpoch"])
 elapsed = time.time() - start
 target = int(events["RTOSeconds"])
 return {
  "elapsed_seconds": int(elapsed),
  "rto_met": elapsed <= target,
  "message": f"RTO {'達成' if elapsed <= target else '超過'}: {int(elapsed)}s / {target}s"
 }
 InputPayload:
StartEpoch: "{{ StartEpoch }}"
RTOSeconds: "{{ RTOSeconds }}"
  outputs:
 - Name: rto_met
Selector: $.Payload.rto_met
Type: Boolean
 - Name: message
Selector: $.Payload.message
Type: String

aws:branch は前の Step の出力値を評価して後続 Step を分岐させます。Choices リストに条件と遷移先を列挙し、いずれにも該当しない場合は Default に進みます。Sev 判定による通知先切り替えや Dry-run モードのスキップなど、フロー制御全般に使います。

aws:approve は指定した IAM エンティティまたは SNS トピックへ承認依頼を送り、承認されるまで後続 Step を待機させます。Sev1 インシデントで Incident Commander の確認を必須化したい場面で使います。NotificationArn を SNS トピックにすることで、メール・Slack の両方に承認リンクを配信できます。

- name: require_approval
  action: aws:approve
  timeoutSeconds: 1800
  onFailure: Abort
  inputs:
 NotificationArn: "{{ ApprovalSNSTopicArn }}"
 Message: |
Sev1 IR Runbook の実行を承認してください。
対象リソース: {{ TargetResource }}
Execution ID: {{ automation:EXECUTION_ID }}
 MinRequiredApprovals: 1
 Approvers:
- "{{ IncidentCommanderRoleArn }}"

aws:sleep は指定秒数だけ実行を一時停止します。Aurora フェイルオーバー後に Writer インスタンスが起動するまでの待機や、S3 隔離後に 脅威検知 の Threat Intel 更新を待つ用途に使います。Duration は ISO 8601 形式 (例: PT30S) で指定します。


3-4. IAM Role 設計

SSM Automation 実行ロールは最小権限の原則で設計します。ssm.amazonaws.com を Principal とした信頼ポリシーを設定し、各シナリオ固有のアクションだけを許可します。

resource "aws_iam_role" "ssm_ir_automation" {
  name = "SSMIRAutomationRole"

  assume_role_policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Effect = "Allow"
Principal = { Service = "ssm.amazonaws.com" }
Action = "sts:AssumeRole"
Condition = {
  StringEquals = {
 "aws:SourceAccount" = var.account_id
  }
}
 }]
  })

  tags = {
 Environment = var.environment
 ManagedBy= "Terraform"
  }
}

resource "aws_iam_role_policy" "ssm_ir_automation" {
  name = "SSMIRAutomationPolicy"
  role = aws_iam_role.ssm_ir_automation.id

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Sid = "RDSFailover"
  Effect = "Allow"
  Action = [
 "rds:FailoverDBCluster",
 "rds:DescribeDBClusters",
 "rds:DescribeGlobalClusters",
 "rds:SwitchoverGlobalCluster"
  ]
  Resource = "arn:aws:rds:*:${var.account_id}:cluster:*"
},
{
  Sid = "Route53Failover"
  Effect = "Allow"
  Action = [
 "route53:ChangeResourceRecordSets",
 "route53:GetHostedZone",
 "route53:ListResourceRecordSets"
  ]
  Resource = "arn:aws:route53:::hostedzone/${var.hosted_zone_id}"
},
{
  Sid = "S3IsolationAudit"
  Effect = "Allow"
  Action = [
 "s3:PutBucketPolicy",
 "s3:GetBucketPolicy",
 "s3:PutBucketVersioning",
 "s3:GetBucketVersioning"
  ]
  Resource = "arn:aws:s3:::${var.target_bucket_name}"
},
{
  Sid = "SSMExecutionLogs"
  Effect = "Allow"
  Action = [
 "logs:CreateLogGroup",
 "logs:CreateLogStream",
 "logs:PutLogEvents",
 "logs:DescribeLogGroups"
  ]
  Resource = "arn:aws:logs:${var.region}:${var.account_id}:log-group:/aws/ssm/automation/*"
},
{
  Sid = "SNSNotify"
  Effect = "Allow"
  Action = ["sns:Publish"]
  Resource = aws_sns_topic.ir_notification.arn
}
 ]
  })
}

Condition ブロックに aws:SourceAccount を追加することで、クロスアカウントからの不正な AssumeRole を防止します。さらに aws:SourceArn で特定の SSM Automation Document から呼び出された場合のみロールを引き受けられるよう制限すると、最小権限をより厳密に適用できます。


3-5. EventBridge 連携

EventBridge Rule は SSM Automation Document の自動起動トリガーとして機能します。各シナリオのトリガーソースに応じて event_pattern を設計します。

# EventBridge Rule: CloudWatch Alarm → SSM Automation (Aurora)
resource "aws_cloudwatch_event_rule" "aurora_ir_trigger" {
  name  = "aurora-ir-runbook-trigger"
  description = "Aurora クラスターの障害アラームで IR Runbook を自動起動"

  event_pattern = jsonencode({
 source= ["aws.cloudwatch"]
 detail-type = ["CloudWatch Alarm State Change"]
 detail = {
alarmName = [{ prefix = "aurora-cluster-" }]
state  = { value = ["ALARM"] }
previousState = { value = ["OK"] }
 }
  })
}

# EventBridge Rule: GuardDuty Finding → SSM Automation (S3 隔離)
resource "aws_cloudwatch_event_rule" "guardduty_s3_ir_trigger" {
  name  = "guardduty-s3-ir-runbook-trigger"
  description = "GuardDuty S3 Finding で S3 隔離 Runbook を自動起動"

  event_pattern = jsonencode({
 source= ["aws.guardduty"]
 detail-type = ["GuardDuty Finding"]
 detail = {
type= [{ prefix = "Stealth:S3/" }, { prefix = "Policy:S3/" }]
severity = [{ numeric = [">=", 4.0] }]
 }
  })
}

# EventBridge ターゲット: SSM Automation Document を起動
resource "aws_cloudwatch_event_target" "aurora_ir_ssm" {
  rule  = aws_cloudwatch_event_rule.aurora_ir_trigger.name
  arn= "arn:aws:ssm:${var.region}:${var.account_id}:automation-definition/IRRunbook-Aurora:$DEFAULT"
  role_arn = aws_iam_role.events_ssm_invoke.arn

  input_transformer {
 input_paths = {
alarm_name = "$.detail.alarmName"
account_id = "$.account"
 }
 input_template = jsonencode({
DBClusterIdentifier= [var.aurora_cluster_id]
AutomationAssumeRole  = [aws_iam_role.ssm_ir_automation.arn]
ApprovalSNSTopicArn= [aws_sns_topic.ir_notification.arn]
Severity  = ["S1"]
DryRun = ["false"]
 })
  }
}

# EventBridge から SSM を起動するための IAM ロール
resource "aws_iam_role" "events_ssm_invoke" {
  name = "EventBridgeSSMInvokeRole"

  assume_role_policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Effect = "Allow"
Principal = { Service = "events.amazonaws.com" }
Action = "sts:AssumeRole"
 }]
  })
}

resource "aws_iam_role_policy" "events_ssm_invoke" {
  role = aws_iam_role.events_ssm_invoke.id

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Effect= "Allow"
Action= ["ssm:StartAutomationExecution"]
Resource = [
  "arn:aws:ssm:${var.region}:${var.account_id}:automation-definition/IRRunbook-Aurora:*",
  "arn:aws:ssm:${var.region}:${var.account_id}:automation-definition/IRRunbook-S3Isolation:*"
]
 }]
  })
}

EventBridge Rule の previousState フィルタを ["OK"] に設定することで、INSUFFICIENT_DATA から ALARM に遷移した際の誤発火を防止します。脅威検知 Finding のフィルタでは severity >= 4.0 を閾値とし、Low (1.0〜3.9) レベルの Finding でランブックが不必要に起動しないよう制御します。


3-6. 3 点セット

Terraform — SSM Document 作成

resource "aws_ssm_document" "ir_runbook_aurora" {
  name= "IRRunbook-Aurora"
  document_type= "Automation"
  document_format = "YAML"

  content = file("${path.module}/ssm_docs/ir_runbook_aurora.yaml")

  tags = {
 Environment = var.environment
 Scenario = "Aurora-Failover"
 ManagedBy= "Terraform"
  }
}

resource "aws_ssm_document" "ir_runbook_s3_isolation" {
  name= "IRRunbook-S3Isolation"
  document_type= "Automation"
  document_format = "YAML"

  content = file("${path.module}/ssm_docs/ir_runbook_s3_isolation.yaml")

  tags = {
 Environment = var.environment
 Scenario = "S3-GuardDuty-Isolation"
 ManagedBy= "Terraform"
  }
}

AWS CLI — Document 作成・実行・確認

# SSM Document 作成
aws ssm create-document \
  --name "IRRunbook-Aurora" \
  --document-type "Automation" \
  --document-format YAML \
  --content file://ssm_docs/ir_runbook_aurora.yaml \
  --tags "Key=Environment,Value=production"

# SSM Automation 手動起動 (Sev1 / DryRun なし)
aws ssm start-automation-execution \
  --document-name "IRRunbook-Aurora" \
  --parameters '{
 "DBClusterIdentifier":["aurora-prod-cluster"],
 "AutomationAssumeRole":["arn:aws:iam::123456789012:role/SSMIRAutomationRole"],
 "ApprovalSNSTopicArn":["arn:aws:sns:ap-northeast-1:123456789012:ir-notification"],
 "Severity":["S1"],
 "DryRun":["false"]
  }'

# 実行状態確認
aws ssm get-automation-execution \
  --automation-execution-id <execution-id> \
  --query 'AutomationExecution.{Status:AutomationExecutionStatus,Steps:StepExecutions[*].{Name:StepName,Status:StepStatus,Output:Outputs}}'

# Step 詳細確認
aws ssm describe-automation-step-executions \
  --automation-execution-id <execution-id>

# Document バージョン一覧
aws ssm list-document-versions \
  --name "IRRunbook-Aurora"

コンソール操作

SSM Automation を手動起動するには、AWS コンソールで Systems Manager > Automation を開き、右上の「Execute automation」をクリックします。「Owned by me」タブに作成した Document が表示されるので選択し、「Simple execution」を選んで Parameter を入力して「Execute」をクリックします。

実行中の Automation は「Executions」タブでリアルタイムに進捗を確認できます。各 Step が緑色 (Success) に変わっていくことを確認し、失敗した Step をクリックすると OutputError の詳細が確認できます。実行ログの全量は CloudWatch Logs の /aws/ssm/automation/IRRunbook-Aurora ロググループで検索できます。


4. シナリオ1 Aurora 障害自動復旧 Runbook

fig02: シナリオ1 Aurora 障害自動復旧フロー

4-1. Vol1 と Vol2 連携クローズドループ全体図

本シナリオは、Vol1 (Multi-Region Backup と DR 本番実践) で構築した Aurora Global Database のクロスリージョン DR 構成と、Vol2 (Chaos Engineering with AWS FIS 本番運用) で機械検証した障害注入実験を組み合わせた「クローズドループ」を完成させる。

クローズドループの全体フローは以下の通りです。

  1. 障害注入 (Vol2): FIS ExperimentTemplate の aws:rds:failover-db-cluster アクションが Aurora Global Database のプライマリリージョン (ap-northeast-1) クラスターへ意図的な Failover を実行する。
  2. イベント検知: FIS Experiment が completed 状態に遷移すると、EventBridge Rule がソース aws.fis・詳細タイプ AWS FIS Experiment State Changestate.status: completed のイベントを捕捉する。
  3. 自動復旧起動 (本 Vol3 §4): EventBridge が SSM Automation Document AuroraAutoRecovery を起動し、自動復旧シーケンスが開始される。
  4. Aurora Global Database Switchover: SSM Automation が DR リージョン (us-east-1) のセカンダリクラスターをプライマリに昇格させる。rds:SwitchoverGlobalCluster API により RPO ゼロの計画的切替を実現する。
  5. Route53 切替: 復旧後の新プライマリエンドポイントに Route53 CNAME レコードを UPSERT し、アプリケーションの接続先を自動的に切り替える。
  6. RTO 実測と CloudWatch 投入: FIS 実験開始から Aurora Writer 昇格完了までの差分秒数を算出し、カスタムメトリクス IR/AuroraRunbook/RTO に記録する。
  7. 復旧ログ S3 保存: RTO・復旧タイムスタンプ・Global Cluster ID を含む JSON を S3 に保存し、§7 事後分析 自動生成フローへ引き渡す。

Vol1 では Aurora Global Database のリージョン間切替が手動手順書で管理されていた。Vol2 でその障害シナリオが機械的に再現可能になった。本 Vol3 でその復旧が EventBridge + SSM Automation により完全自動化され、MTTR の継続的な計測・改善サイクルが実現する。


4-2. FIS Action 検知から SSM Automation chain 実装

FIS Experiment が完了すると EventBridge はソース aws.fis・詳細タイプ AWS FIS Experiment State Change のイベントを発行する。EventBridge Rule で FIS テンプレート ID を絞り込むことで、Aurora 以外の FIS 実験が誤って Runbook を起動しないよう制御する。

{
  "source": ["aws.fis"],
  "detail-type": ["AWS FIS Experiment State Change"],
  "detail": {
 "state": { "status": ["completed"] },
 "experimentTemplateId": ["EXTabc123def"]
  }
}

EventBridge ターゲットには arn:aws:ssm:<region>:<account>:automation-definition/AuroraAutoRecovery:$DEFAULT を指定し、input_transformer で Aurora Global Cluster ID・DR リージョン・Route53 ゾーン情報をパラメータとして渡す。EventBridge から SSM Automation を起動する IAM ロールには ssm:StartAutomationExecutioniam:PassRole の両方が必要な点に注意する。

【QG-2: Aurora 障害自動復旧 Runbook チェックリスト (6 段階)】

  1. FIS アクション定義確認: FIS ExperimentTemplate に aws:rds:failover-db-cluster アクションが設定され、ターゲット Aurora クラスター (ap-northeast-1 プライマリ) が正しく指定されているか。
  2. EventBridge Rule フィルタ確認: aws.fis ソース・AWS FIS Experiment State Change 詳細タイプで構成され、FIS テンプレート ID で絞り込まれているか (別障害シナリオの誤起動防止)。
  3. SSM Automation IAM ロール権限確認: rds:SwitchoverGlobalClusterrds:DescribeGlobalClustersroute53:ChangeResourceRecordSetss3:PutObjectcloudwatch:PutMetricData が IAM ポリシーに含まれているか。
  4. Switchover 完了確認: Global Database Switchover 後、DR リージョンのクラスターが IsWriter=true として GlobalClusterMembers に反映されているか (rds:DescribeGlobalClusters で確認)。
  5. Route53 TTL 設定確認: Aurora エンドポイントを指す DNS レコードの TTL が 60 秒以下に設定されており、Switchover 後のキャッシュ影響が最小化されているか。
  6. 復旧ログ S3 保存確認: SSM Automation の最終ステップで RTO 計測値・復旧タイムスタンプ・Global Cluster ID を含む JSON が S3 バケットに PutObject されているか (§7 事後分析 フロー前提)。

4-3. SSM Automation Document 実装

以下の Terraform で aws_ssm_document リソース AuroraAutoRecovery を schemaVersion 0.3 で定義する。5 段階のステップで Aurora の完全自動復旧を実現する。

resource "aws_ssm_document" "aurora_auto_recovery" {
  name= "AuroraAutoRecovery"
  document_type= "Automation"
  document_format = "YAML"

  content = <<-YAML
 schemaVersion: "0.3"
 description: "Aurora Global Database automatic recovery after FIS experiment"
 assumeRole: "{{ AutomationAssumeRole }}"
 parameters:
AutomationAssumeRole:
  type: String
GlobalClusterId:
  type: String
  default: "aurora-global-production"
DrRegion:
  type: String
  default: "us-east-1"
Route53ZoneId:
  type: String
Route53RecordName:
  type: String
RecoveryLogBucket:
  type: String
 mainSteps:
- name: RecordStartTime
  action: aws:executeScript
  inputs:
 Runtime: python3.8
 Handler: handler
 Script: |
import datetime
def handler(e, c):
 return {"StartTime": datetime.datetime.utcnow().isoformat() + "Z"}
  outputs:
 - Name: StartTime
Selector: "$.Payload.StartTime"
Type: String
- name: SwitchoverAuroraGlobalDB
  action: aws:executeAwsApi
  inputs:
 Service: rds
 Api: SwitchoverGlobalCluster
 GlobalClusterIdentifier: "{{ GlobalClusterId }}"
 TargetDbClusterIdentifier: "arn:aws:rds:{{ DrRegion }}:{{ global:ACCOUNT_ID }}:cluster:{{ GlobalClusterId }}-{{ DrRegion }}"
  onFailure: Continue
- name: WaitForNewPrimary
  action: aws:waitForAwsResourceProperty
  inputs:
 Service: rds
 Api: DescribeGlobalClusters
 GlobalClusterIdentifier: "{{ GlobalClusterId }}"
 PropertySelector: "$.GlobalClusters[0].Status"
 DesiredValues:
- "available"
  timeoutSeconds: 300
  onFailure: Abort
- name: UpdateRoute53Record
  action: aws:executeAwsApi
  inputs:
 Service: route53
 Api: ChangeResourceRecordSets
 HostedZoneId: "{{ Route53ZoneId }}"
 ChangeBatch:
Changes:
  - Action: UPSERT
 ResourceRecordSet:
Name: "{{ Route53RecordName }}"
Type: CNAME
TTL: 60
ResourceRecords:
  - Value: "{{ GlobalClusterId }}-{{ DrRegion }}.cluster-xxxx.{{ DrRegion }}.rds.amazonaws.com"
- name: MeasureAndSaveRTO
  action: aws:executeScript
  inputs:
 Runtime: python3.8
 Handler: handler
 InputPayload:
StartTime: "{{ RecordStartTime.StartTime }}"
GlobalClusterId: "{{ GlobalClusterId }}"
RecoveryLogBucket: "{{ RecoveryLogBucket }}"
 Script: |
import boto3, datetime, json
def handler(events, context):
 start= datetime.datetime.fromisoformat(events["StartTime"].replace("Z", ""))
 now  = datetime.datetime.utcnow()
 rto_sec = (now - start).total_seconds()
 boto3.client("cloudwatch").put_metric_data(
  Namespace="IR/AuroraRunbook",
  MetricData=[{"MetricName": "RTO", "Value": rto_sec, "Unit": "Seconds"}]
 )
 log = {"rto_seconds": rto_sec, "recovered_at": now.isoformat() + "Z",
  "global_cluster": events["GlobalClusterId"]}
 boto3.client("s3").put_object(
  Bucket=events["RecoveryLogBucket"],
  Key=f"aurora-recovery/{now.strftime('%Y-%m-%d')}.json",
  Body=json.dumps(log)
 )
 return {"RTO": rto_sec}
  YAML
}

続けて IAM ロールと EventBridge Rule を定義する。

resource "aws_iam_role" "ssm_aurora_recovery" {
  name = "ssm-aurora-recovery-role"
  assume_role_policy = jsonencode({
 Version= "2012-10-17"
 Statement = [{ Effect = "Allow", Principal = { Service = "ssm.amazonaws.com" }, Action = "sts:AssumeRole" }]
  })
}

resource "aws_iam_role_policy" "ssm_aurora_recovery" {
  name = "ssm-aurora-recovery-policy"
  role = aws_iam_role.ssm_aurora_recovery.id
  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{ Effect = "Allow", Action = ["rds:SwitchoverGlobalCluster", "rds:DescribeGlobalClusters"], Resource = "*" },
{ Effect = "Allow", Action = ["route53:ChangeResourceRecordSets"], Resource = "arn:aws:route53:::hostedzone/*" },
{ Effect = "Allow", Action = ["s3:PutObject"], Resource = "arn:aws:s3:::${var.recovery_log_bucket}/*" },
{ Effect = "Allow", Action = ["cloudwatch:PutMetricData"], Resource = "*" }
 ]
  })
}

resource "aws_cloudwatch_event_rule" "fis_aurora_completed" {
  name = "fis-aurora-experiment-completed"
  event_pattern = jsonencode({
 source  = ["aws.fis"]
 "detail-type" = ["AWS FIS Experiment State Change"]
 detail  = { state = { status = ["completed"] }, experimentTemplateId = [var.fis_template_id] }
  })
}

resource "aws_cloudwatch_event_target" "ssm_aurora_recovery" {
  rule= aws_cloudwatch_event_rule.fis_aurora_completed.name
  target_id = "AuroraRecoveryAutomation"
  arn = "arn:aws:ssm:${var.region}:${data.aws_caller_identity.current.account_id}:automation-definition/AuroraAutoRecovery:$DEFAULT"
  role_arn  = aws_iam_role.eventbridge_ssm.arn

  input_transformer {
 input_paths = { experimentId = "$.detail.id" }
 input_template = jsonencode({
GlobalClusterId= var.aurora_global_cluster_id
DrRegion = var.dr_region
Route53ZoneId  = var.route53_zone_id
Route53RecordName = var.aurora_dns_record
RecoveryLogBucket = var.recovery_log_bucket
AutomationAssumeRole = aws_iam_role.ssm_aurora_recovery.arn
 })
  }
}

4-4. Aurora Global Database Switchover 自動化

Aurora Global Database の切替操作には SwitchoverFailover の 2 種類がある。Switchover は計画的な切替で、プライマリがすべてのデータをセカンダリへレプリケートしてから役割を交換するため理論上データロスが発生しません。Failover は予期しない障害発生時の緊急切替で、未レプリケート分のデータロスを生じる可能性があります (通常 1 秒未満)。

FIS aws:rds:failover-db-cluster は同一リージョン内のインスタンスレベルのフェイルオーバーを発生させる。Global Database のリージョン間 Failover ではない。本 Runbook では FIS 実験完了後に意図的に Global Database Switchover を実行して DR リージョンへ完全切替し、MTTR を実測する設計としている。

SwitchoverAuroraGlobalDB ステップでは rds:SwitchoverGlobalCluster API を呼び出す。TargetDbClusterIdentifier に昇格させたいセカンダリクラスターの ARN を指定する。Switchover には通常 1〜2 分かかるため、後続の WaitForNewPrimary ステップで aws:waitForAwsResourceProperty を用いて GlobalClusters[0].Statusavailable に変わるまでポーリングする。タイムアウトは 300 秒に設定し、超過した場合は onFailure: Abort で実行を停止してアラームを発火させる。

Switchover 完了後、旧プライマリは自動的に Global Cluster のセカンダリとして再参加します。ただしネットワーク分断を伴う FIS 障害が発生したときは再参加へ失敗するケースがあり、その際は rds:remove-from-global-cluster で一旦切り離してから手動で再追加する手順が必要です (詳細は 4-6 落とし穴参照)。


4-5. RTO 自動実測スクリプト

RTO を数値で管理するには実測が不可欠です。SSM Document の MeasureAndSaveRTO ステップでは Python スクリプトを用いて次の 3 点を自動化する。

計測ロジック: RecordStartTime ステップで FIS 実験開始直後のタイムスタンプを記録し、WaitForNewPrimary 完了後の現在時刻との差分秒数を RTO として定義する。「FIS 障害注入 → Aurora 新プライマリ昇格完了」のエンドツーエンド所要時間が計測される。

CloudWatch Metrics 投入: 算出した RTO を名前空間 IR/AuroraRunbook・メトリクス名 RTO・単位 Seconds で投入する。CloudWatch アラームを設定すれば RTO が SLO 目標 (例: 180 秒) を超過した際に SNS → PagerDuty 通知が発火する。

S3 復旧ログ保存: RTO・復旧タイムスタンプ・Aurora Global Cluster ID を含む JSON を S3 に保存する。このファイルが §7 事後分析 自動生成の入力データとなる。

手動実行が必要な場合は以下の AWS CLI コマンドを使用する。

aws ssm start-automation-execution \
  --document-name "AuroraAutoRecovery" \
  --parameters \
 "GlobalClusterId=my-aurora-global,\
DrRegion=ap-northeast-1,\
Route53ZoneId=Z1234567890ABC,\
Route53RecordName=aurora.example.internal,\
RecoveryLogBucket=my-recovery-logs,\
AutomationAssumeRole=arn:aws:iam::123456789012:role/ssm-aurora-recovery-role" \
  --region ap-northeast-1

実行ステータスを確認するコマンドは以下の通りです。

aws ssm get-automation-execution \
  --automation-execution-id <ExecutionId> \
  --query "AutomationExecution.{Status:AutomationExecutionStatus,Steps:StepExecutions[*].{Step:StepName,Status:StepStatus}}" \
  --output table

4-6. 落とし穴

Switchover 後の旧プライマリ再参加失敗: Global Database Switchover が完了すると旧プライマリはセカンダリとして自動再参加するものの、FIS 実験でネットワーク分断を伴う障害を注入したケースでは再参加へ失敗する事象が起こります。この場合 rds:remove-from-global-cluster で一旦切り離してから手動で再追加する。SSM Document の後処理ステップに DescribeGlobalClusters で再参加状態を診断するロジックを組み込むと Runbook の自己完結性が高まる。

Route53 TTL の罠: Aurora エンドポイントを指す DNS レコードの TTL が長い (例: 300 秒) と、Switchover 後も古いエンドポイントがキャッシュされ続けてアプリケーションの接続エラーが継続する。本番では TTL を 60 秒以下に設定し、RDS Proxy 経由のフェイルオーバーと組み合わせると切替影響を最小化できる。

EventBridge → SSM 起動レイテンシ: FIS Experiment の completed 状態への遷移から EventBridge 発火を経て SSM Automation 起動までは、最大 60 秒の遅延を見込んでください。この遅延は RTO 計測値に含まれるため、SLO 目標を設定する際は EventBridge レイテンシ分のバッファ (60 秒程度) を加算すること。

IAM PassRole の設定漏れ: EventBridge が SSM Automation を起動するとき、EventBridge の IAM ロールでは ssm:StartAutomationExecution に加え iam:PassRole も必要です。iam:PassRole が不足していると EventBridge がロールを SSM へ渡せず AccessDenied エラーでサイレントに失敗します。EventBridge ターゲット設定後に EventBridge コンソールの「Monitoring」タブでエラー数を確認する習慣をつける。

SwitchoverGlobalCluster vs FailoverGlobalCluster の使い分け: 本 Runbook では FIS 実験完了後の切り戻しに SwitchoverGlobalCluster (計画的切替・RPO ゼロ) を使用する。緊急時の FailoverGlobalCluster とは API シグネチャが異なるため混同しないよう注意する。本番 Runbook にはコメントで使い分け理由を記載しておく。


4-7. 3 点セット

Terraform 主要リソース

リソース目的
aws_ssm_document.aurora_auto_recoveryAurora 自動復旧 SSM Automation Document
aws_iam_role.ssm_aurora_recoverySSM Automation 実行 IAM ロール
aws_iam_role_policy.ssm_aurora_recoveryRDS Switchover / Route53 / S3 / CloudWatch 権限
aws_cloudwatch_event_rule.fis_aurora_completedFIS Experiment 完了イベント検知 Rule
aws_cloudwatch_event_target.ssm_aurora_recoverySSM Automation をターゲットに設定

AWS CLI

# 実行履歴一覧
aws ssm describe-automation-executions \
  --filters "Key=DocumentNamePrefix,Values=AuroraAutoRecovery" \
  --query "AutomationExecutionMetadataList[*].{Id:AutomationExecutionId,Status:AutomationExecutionStatus,Start:ExecutionStartTime}" \
  --output table

# ステップ詳細確認
aws ssm describe-automation-step-executions \
  --automation-execution-id <ExecutionId> \
  --query "StepExecutions[*].{Step:StepName,Status:StepExecutionStatus}" \
  --output table

コンソール確認手順

  1. AWS Management Console で Systems Manager を開き、左ペインの AutomationExecutions を選択する。
  2. 実行中または完了した AuroraAutoRecovery の行をクリックし、各ステップの STATUS・出力・実行時間を確認する。
  3. 実行が FAILED になった場合、該当ステップの Output タブでエラーメッセージを確認し IAM 権限やパラメータ値を修正する。
  4. CloudWatch コンソール → Metrics → カスタム名前空間 IR/AuroraRunbookRTO メトリクスの時系列グラフを確認し、SLO 目標値に対するアラーム状態を監視する。

5. シナリオ2 仮想マシンとコンテナ Pod 障害の自動診断と復旧

fig03: シナリオ2 仮想マシンとコンテナ Pod 障害診断と復旧フロー

5-1. CloudWatch Composite Alarm 設計

CloudWatch Composite Alarm は、EC2 の StatusCheckFailed_Instance と EKS ノードの CPU 使用率アラームを OR 条件で結合し、単一アラームで複数リソースの障害を検知する。これにより EventBridge ルールを 1 本に集約しつつ、2 種の障害シナリオを一元管理できる。

まず EC2 用と EKS ノード用の 2 本のメトリクスアラームを個別に作成し、それを Composite Alarm の alarm_rule で組み合わせる。EKS ノードの CPU メトリクスには、Container Insights を有効化して node_cpu_utilization を CloudWatch に転送しておく必要がある。

# EC2 StatusCheck アラーム
resource "aws_cloudwatch_metric_alarm" "ec2_statuscheck" {
  alarm_name = "ec2-statuscheck-failed"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 2
  metric_name= "StatusCheckFailed_Instance"
  namespace  = "AWS/EC2"
  period  = 60
  statistic  = "Sum"
  threshold  = 1
  alarm_description= "EC2 インスタンスの StatusCheck が連続 2 回失敗"
  dimensions = {
 InstanceId = var.ec2_instance_id
  }
}

# EKS ノード CPU アラーム (Container Insights 必須)
resource "aws_cloudwatch_metric_alarm" "eks_node_cpu" {
  alarm_name = "eks-node-cpu-high"
  comparison_operator = "GreaterThanOrEqualToThreshold"
  evaluation_periods  = 3
  metric_name= "node_cpu_utilization"
  namespace  = "ContainerInsights"
  period  = 60
  statistic  = "Average"
  threshold  = 90
  alarm_description= "EKS ノードの CPU 使用率が 3 分間 90% 超"
  dimensions = {
 ClusterName = var.eks_cluster_name
 NodeName = var.eks_node_name
  }
}

# Composite Alarm: どちらか一方が ALARM で発報
resource "aws_cloudwatch_composite_alarm" "ec2_eks_recovery_trigger" {
  alarm_name  = "ec2-eks-recovery-trigger"
  alarm_description = "EC2/EKS いずれかの障害で SSM Automation を起動"
  alarm_rule  = "ALARM(\"ec2-statuscheck-failed\") OR ALARM(\"eks-node-cpu-high\")"

  alarm_actions = [aws_sns_topic.recovery_trigger.arn]
}

Composite Alarm の alarm_actions には SNS Topic の ARN を指定し、SNS の Subscription で EventBridge または Lambda に転送する。CloudWatch Composite Alarm から直接 EventBridge Rule は起動できないため、この中継が必要になる。

5-2. SSM Automation 診断 Step

EC2EKSAutoRecovery Document は schemaVersion: "0.3" で、診断→戦略選択→復旧実行→通知の流れを定義する。aws:executeScript で対象リソースの状態を確認し、その結果を aws:branch で評価して後続ステップに振り分ける。

schemaVersion: "0.3"
description: "EC2/EKS ノード障害自動診断と復旧"
parameters:
  TargetType:
 type: String
 allowedValues: [ec2, eks]
 description: "復旧対象のリソース種別"
  ResourceId:
 type: String
 description: "EC2 Instance ID または EKS Node Group 名"
  ClusterName:
 type: String
 default: ""
 description: "EKS クラスター名 (TargetType=eks 時必須)"
  Namespace:
 type: String
 default: "default"
 description: "Pod 再スケジュール対象の Namespace"
  SNSTopicArn:
 type: String
 description: "通知先 SNS Topic ARN"
assumeRole: "{{ AutomationAssumeRole }}"
mainSteps:
  - name: diagnose
 action: aws:executeScript
 description: "リソース状態を診断して復旧戦略の入力値を生成"
 inputs:
Runtime: python3.11
Handler: diagnose_handler
Script: |
  import boto3
  def diagnose_handler(events, context):
t = events["TargetType"]
rid = events["ResourceId"]
if t == "ec2":
 ec2 = boto3.client("ec2")
 resp = ec2.describe_instance_status(
  InstanceIds=[rid], IncludeAllInstances=True
 )
 s = "impaired"
 if resp["InstanceStatuses"]:
  s = resp["InstanceStatuses"][0]["InstanceStatus"]["Status"]
 return {"target_type": "ec2", "status": s}
return {"target_type": "eks", "status": "cpu-high"}
InputPayload:
  TargetType: "{{ TargetType }}"
  ResourceId:  "{{ ResourceId }}"
 outputs:
- Name: target_type
  Selector: $.Payload.target_type
  Type: String
- Name: resource_status
  Selector: $.Payload.status
  Type: String

  - name: choose_recovery
 action: aws:branch
 description: "診断結果に基づいて復旧ステップを選択"
 inputs:
Choices:
  - NextStep: reboot_ec2
 Variable: "{{ diagnose.target_type }}"
 StringEquals: ec2
  - NextStep: restart_eks_nodegroup
 Variable: "{{ diagnose.target_type }}"
 StringEquals: eks
Default: notify_failure
【QG-3: 仮想マシンとコンテナ Pod 障害自動診断と復旧チェックリスト】

  • CloudWatch Composite Alarm: ec2-statuscheck-failed (EC2 StatusCheckFailed_Instance) と eks-node-cpu-high (Container Insights node_cpu_utilization) の 2 本が ALARM 判定を持ち、Composite Alarm の alarm_rule で OR 結合されていること
  • SSM Automation Step 構成: aws:executeScript (診断) → aws:branch (復旧戦略選択) → aws:executeAwsApi (EC2 Reboot または EKS Node Scale) の 3 ステップが正常完了し、Execution ステータスが Success であること
  • EKS IRSA 権限: SSM Automation 実行 Role に eks:UpdateNodegroupConfigeks:DescribeNodegroupec2:RebootInstances が付与され、EKS OIDC プロバイダーとの信頼関係が設定されていること (EKS IRSA 実践 参照)
  • Slack 通知: 復旧成功・失敗の両パスで SNS Topic への Publish が実行され、Lambda 経由で Slack チャンネルに通知が届いていること

5-3. branch Step で復旧戦略選択

aws:branchec2 を選択した場合は reboot_ec2 ステップで ec2:RebootInstances を直接呼び出す。AWS マネージドドキュメント AWS-RestartEC2Instanceaws:executeAutomation で呼ぶ方法もあるが、ここでは実行ログの一元管理のために aws:executeAwsApi を使う。

  - name: reboot_ec2
 action: aws:executeAwsApi
 description: "EC2 インスタンスを再起動"
 inputs:
Service: ec2
Api: RebootInstances
InstanceIds:
  - "{{ ResourceId }}"
 nextStep: notify_success

  - name: restart_eks_nodegroup
 action: aws:executeAwsApi
 description: "EKS ノードグループをスケールダウン (Pod 強制退避)"
 inputs:
Service: eks
Api: UpdateNodegroupConfig
clusterName: "{{ ClusterName }}"
nodegroupName: "{{ ResourceId }}"
scalingConfig:
  desiredSize: 0
 nextStep: scale_up_eks_nodegroup

  - name: scale_up_eks_nodegroup
 action: aws:executeAwsApi
 description: "EKS ノードグループをスケールアップ (Pod 再スケジュール)"
 inputs:
Service: eks
Api: UpdateNodegroupConfig
clusterName: "{{ ClusterName }}"
nodegroupName: "{{ ResourceId }}"
scalingConfig:
  desiredSize: 1
 nextStep: notify_success

  - name: notify_success
 action: aws:executeAwsApi
 inputs:
Service: sns
Api: Publish
TopicArn: "{{ SNSTopicArn }}"
Subject: "[RECOVERY SUCCESS] {{ TargetType }}/{{ ResourceId }}"
Message: |
  SSM Automation が {{ TargetType }} リソース {{ ResourceId }} の復旧を完了しました。
  Execution ID: {{ automation:EXECUTION_ID }}
 isEnd: true

  - name: notify_failure
 action: aws:executeAwsApi
 inputs:
Service: sns
Api: Publish
TopicArn: "{{ SNSTopicArn }}"
Subject: "[RECOVERY FAILED] {{ TargetType }}/{{ ResourceId }} — 手動対応が必要です"
Message: |
  SSM Automation が {{ TargetType }} リソース {{ ResourceId }} の自動復旧に失敗しました。
  Execution ID: {{ automation:EXECUTION_ID }}
  直ちに手動で状態を確認してください。
 isEnd: true

EKS ノードグループを desiredSize=0 にするとそのノード上の全 Pod が退避する。PodDisruptionBudget (PDB) を設定して最低稼働 Pod 数を保証し、maxUnavailable: 1 の範囲でローリング退避させることが本番要件。スケールダウン/アップの間に待機ステップが必要な場合は aws:sleep を挿入する。

個別の不健全 Pod だけを再起動したい場合は、EKS ノードに SSM Agent をインストールして aws:runCommandkubectl delete pod を実行するアプローチが有効。その際ノード上の SSM Agent から kubectl を呼ぶには、Node の IAM ロールに EKS 認証設定 (aws-auth ConfigMap) が必要。詳細は EKS Vol3 ALB と ArgoCD 本番運用 で解説している ArgoCD 自動 Rollback の実装を参照。

5-4. コンテナ Pod 自動復旧

EKS Pod の自動復旧において IRSA の正しい設定が不可欠。SSM Automation が EKS API を呼び出す IAM Role には最小権限原則を適用し、必要なアクションだけを付与する。

resource "aws_iam_role" "ssm_ec2_eks_recovery" {
  name = "SSMEc2EksRecoveryRole"

  assume_role_policy = jsonencode({
 Version = "2012-10-17"
 Statement = [{
Effect = "Allow"
Principal = { Service = "ssm.amazonaws.com" }
Action = "sts:AssumeRole"
 }]
  })
}

resource "aws_iam_role_policy" "ssm_ec2_eks_recovery" {
  role = aws_iam_role.ssm_ec2_eks_recovery.id

  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Sid = "EKSRecovery"
  Effect = "Allow"
  Action = [
 "eks:UpdateNodegroupConfig",
 "eks:DescribeNodegroup",
 "eks:ListNodegroups"
  ]
  Resource = "arn:aws:eks:${var.region}:${var.account_id}:cluster/${var.eks_cluster_name}"
},
{
  Sid = "EC2Recovery"
  Effect = "Allow"
  Action = [
 "ec2:RebootInstances",
 "ec2:DescribeInstanceStatus"
  ]
  Resource = "*"
},
{
  Sid = "SNSPublish"
  Effect = "Allow"
  Action = ["sns:Publish"]
  Resource = aws_sns_topic.recovery_trigger.arn
}
 ]
  })
}

Pod 再スケジュール後の動作確認として、Application Signals の SLO メトリクス (Error Rate / Latency) が回復していることを CloudWatch コンソールで検証する。SLO Burn Rate Alarm が SSM Automation の起動トリガーとなる構成では、復旧後に Alarm が OK 状態へ戻るまで 3〜5 分の待機が必要です。詳細は 観測性 Vol3 Application Signals SLO を参照。

5-5. Slack 通知と段階 Escalation

SSM Automation Document の最終ステップで SNS に Publish し、SNS Subscription に登録した Lambda が Slack チャンネルへの通知とエスカレーション判定を担う。

import json, os, urllib.request

SLACK_WEBHOOK_URL = os.environ["SLACK_WEBHOOK_URL"]
PAGERDUTY_URL  = os.environ["PAGERDUTY_URL"]

def handler(event, context):
 for record in event["Records"]:
  message = json.loads(record["Sns"]["Message"])
  subject = record["Sns"]["Subject"]

  is_failure = "[RECOVERY FAILED]" in subject
  channel = "#incident-sev1" if is_failure else "#ops-log"

  payload = {
"channel": channel,
"text": f"*{subject}*\n{message}",
"username": "SSM Recovery Bot"
  }
  _post(SLACK_WEBHOOK_URL, payload)

  if is_failure:
pd_payload = {
 "routing_key": os.environ["PAGERDUTY_ROUTING_KEY"],
 "event_action": "trigger",
 "payload": {
  "summary": subject,
  "severity": "critical",
  "source": "SSM Automation"
 }
}
_post(PAGERDUTY_URL, pd_payload)

def _post(url, data):
 req = urllib.request.Request(
  url,
  data=json.dumps(data).encode(),
  headers={"Content-Type": "application/json"}
 )
 urllib.request.urlopen(req, timeout=5)

復旧成功通知は #ops-log に送り、失敗通知は #incident-sev1 に送った上で PagerDuty を起動する二段構えの設計が推奨。Sev1 対応が完了したら SSM Automation の実行 ID を使って CloudWatch Logs で詳細ログを確認し、事後分析 の材料とする。

5-6. 3 点セット

Terraform

resource "aws_ssm_document" "ec2_eks_recovery" {
  name= "EC2EKSAutoRecovery"
  document_type= "Automation"
  document_format = "YAML"
  content= file("${path.module}/ssm_docs/ec2_eks_recovery.yaml")

  tags = {
 Environment = var.environment
 ManagedBy= "Terraform"
  }
}

resource "aws_cloudwatch_event_rule" "ssm_recovery_trigger" {
  name  = "ssm-ec2-eks-recovery-trigger"
  description = "Composite Alarm 発報で SSM Automation を起動"

  event_pattern = jsonencode({
 source= ["aws.cloudwatch"]
 detail-type = ["CloudWatch Alarm State Change"]
 detail = {
alarmName = ["ec2-eks-recovery-trigger"]
state  = { value = ["ALARM"] }
 }
  })
}

resource "aws_cloudwatch_event_target" "ssm_recovery" {
  rule  = aws_cloudwatch_event_rule.ssm_recovery_trigger.name
  arn= "arn:aws:ssm:${var.region}:${var.account_id}:automation-definition/EC2EKSAutoRecovery"
  role_arn = aws_iam_role.events_ssm_invoke.arn

  input_transformer {
 input_paths = {
alarm_name = "$.detail.alarmName"
 }
 input_template = jsonencode({
TargetType  = ["ec2"]
ResourceId  = [var.ec2_instance_id]
SNSTopicArn = [aws_sns_topic.recovery_notification.arn]
 })
  }
}

AWS CLI

# SSM Automation 手動起動 (EC2)
aws ssm start-automation-execution \
  --document-name EC2EKSAutoRecovery \
  --parameters \
 "TargetType=ec2,\
ResourceId=i-0123456789abcdef0,\
SNSTopicArn=arn:aws:sns:ap-northeast-1:123456789012:recovery-notification,\
AutomationAssumeRole=arn:aws:iam::123456789012:role/SSMEc2EksRecoveryRole"

# SSM Automation 手動起動 (EKS ノードグループ)
aws ssm start-automation-execution \
  --document-name EC2EKSAutoRecovery \
  --parameters \
 "TargetType=eks,\
ResourceId=my-nodegroup,\
ClusterName=my-eks-cluster,\
SNSTopicArn=arn:aws:sns:ap-northeast-1:123456789012:recovery-notification,\
AutomationAssumeRole=arn:aws:iam::123456789012:role/SSMEc2EksRecoveryRole"

# 実行状態確認
aws ssm get-automation-execution \
  --automation-execution-id <execution-id>

# ステップ詳細確認
aws ssm describe-automation-step-executions \
  --automation-execution-id <execution-id>

# EC2 単体再起動 (緊急時手動)
aws ec2 reboot-instances \
  --instance-ids i-0123456789abcdef0

# EKS ノードグループ設定更新 (緊急時手動)
aws eks update-nodegroup-config \
  --cluster-name my-eks-cluster \
  --nodegroup-name my-nodegroup \
  --scaling-config desiredSize=1,minSize=1,maxSize=3

コンソール操作

CloudWatch コンソールで Composite Alarm を確認するには、左ペインの「Alarms」から「Composite Alarms」タブを選択する。発報中のアラームは赤いバッジで表示され、アラーム詳細画面の「History」タブで状態変化の履歴と SSM Automation の実行 ID を確認できる。

EKS ノードの状態は EKS コンソールの「Clusters」→ 対象クラスター → 「Compute」タブ → 「Node Groups」で確認する。ノードグループの「Health issues」欄に障害原因が表示され、SSM Automation による自動復旧後は「Active」状態に戻っていることを検証する。SSM Automation の実行履歴は Systems Manager コンソールの「Automation」→「Executions」で参照できる。


6. シナリオ3 S3 アクセス異常から自動隔離と再開

fig04: シナリオ3 S3 異常から自動隔離フロー

S3 バケットへの不審なアクセスを 脅威検知 が検知した瞬間、EventBridge 経由で SSM Automation Document が自動起動し、Bucket Policy を隔離ポリシーに差し替えてセキュリティ調査の時間を確保する。調査完了後は同じ Runbook が Bucket Policy を自動復元し通常運用を再開する。このクローズドループにより、セキュリティ担当者がコンソールを開く前に初動対応が完了し MTTR を大幅に短縮できる。

6-1. 脅威検知 Finding Type の S3 関連カタログ

脅威検知 は S3 関連の脅威を複数の Finding Type で分類して検知する。自動隔離の対象とする Finding Type を事前に選定し、EventBridge Rule の Severity フィルターと組み合わせることで誤検知による誤隔離を防ぐ。

Finding Type説明デフォルト Severity
Stealth:S3/ServerAccessLoggingDisabledServer Access Logging が無効化された2.0 (低)
Policy:S3/BucketBlockPublicAccessDisabledBlock Public Access が無効化された2.0 (低)
Policy:S3/BucketAnonymousAccessGranted匿名アクセスが許可された6.0 (中)
UnauthorizedAccess:S3/TorIPCallerTor 出口ノードからのアクセスが検知された5.0 (中)
UnauthorizedAccess:S3/MaliciousIPCaller既知の悪意ある IP からのアクセスが検知された5.0 (中)
Discovery:S3/BucketEnumerationバケット列挙操作が検知された5.0 (中)
Exfiltration:S3/ObjectRead大量オブジェクト読み取りによるデータ流出の可能性8.0 (高)
Impact:S3/ObjectDelete大量オブジェクト削除による破壊行為の可能性8.0 (高)

本シナリオでは Severity が 7.0 以上 の Finding Type を自動隔離トリガーとする設計を採用する。Exfiltration:S3/ObjectReadImpact:S3/ObjectDelete が代表例で、実際のデータ漏洩やデータ損失が進行中の可能性が高い状況を示す。Severity 7 未満の Finding は通知のみとし、段階的隔離の設計でリスクと可用性のバランスをとる。

脅威検知 の検知基盤については 脅威検知 と Security Hub の本番運用 で詳しく解説している。そこで設計した 脅威検知 Detector と S3 Protection の有効化が本シナリオの前提となるため、未設定の場合は先に参照されたい。

6-2. EventBridge Rule Pattern

脅威検知 が生成する Finding イベントは自動的に EventBridge のデフォルトイベントバスに配信される。aws_cloudwatch_event_rule リソースで Severity 7 以上の S3 関連 Finding のみをフィルタリングし、SSM Automation へ転送する。

resource "aws_cloudwatch_event_rule" "guardduty_s3_isolation" {
  name  = "guardduty-s3-high-severity-isolation"
  description = "Triggers SSM Automation on GuardDuty S3 high-severity finding"

  event_pattern = jsonencode({
 source  = ["aws.guardduty"]
 "detail-type" = ["GuardDuty Finding"]
 detail = {
severity = [{ numeric = [">=", 7] }]
type = [
  { prefix = "Exfiltration:S3/" },
  { prefix = "Impact:S3/" }
]
 }
  })
}

resource "aws_cloudwatch_event_target" "guardduty_s3_ssm" {
  rule= aws_cloudwatch_event_rule.guardduty_s3_isolation.name
  target_id = "SSMAutomationS3Isolation"
  arn = "arn:aws:ssm:${var.region}:${data.aws_caller_identity.current.account_id}:automation-definition/${aws_ssm_document.s3_isolation_runbook.name}"
  role_arn  = aws_iam_role.eventbridge_ssm_invoke.arn

  input_transformer {
 input_paths = {
bucket_name = "$.detail.resource.s3BucketDetails[0].name"
finding_id  = "$.detail.id"
severity = "$.detail.severity"
 }
 input_template = "{\"BucketName\":[\"<bucket_name>\"],\"FindingId\":[\"<finding_id>\"],\"Severity\":[\"<severity>\"]}"
  }
}

EventBridge の input_transformer を使うことで 脅威検知 Finding の JSON から s3BucketDetails[0].name を動的に抽出し、SSM Automation Document の Parameter として渡せる。バケット名を静的に決め打ちする必要がないため、アカウント内の全 S3 バケットを保護範囲にできる。

【QG-4: 脅威検知 Finding から SSM Automation 連携 — 5 要素確認チェックリスト】

  • Finding Type フィルター: Severity ≥ 7 の Exfiltration:S3/*Impact:S3/* のみを対象とし、Severity 7 未満は通知止まり (誤隔離防止)
  • EventBridge Rule Pattern: source = aws.guardduty + detail.severity >= 7 + detail.type prefix の 3 条件を AND 接続し、input_transformer でバケット名を動的抽出
  • SSM Automation 隔離 Step: Bucket Policy 隔離 → Versioning 有効化 → IR チーム通知 → 承認待機 → Bucket Policy 復元 の 5 ステップで完結
  • 解析後自動再開: aws:approve で IR チームの承認を待機し、承認後に Bucket Policy を自動復元。タイムアウト 86400 秒 (24 時間) で未承認の場合は Runbook が失敗扱い
  • Audit Trail: CloudTrail データイベント + 脅威検知 Findings S3 エクスポート + SSM Automation 実行ログを統合し 90 日以上保管

6-3. SSM Automation 隔離 Step

S3 隔離 Runbook の核心は schemaVersion: "0.3" を使った 5 ステップの SSM Automation Document だ。BucketName / FindingId / NotificationTopicArn の 3 パラメータを EventBridge の input_transformer から受け取り、完全自動で隔離と復元を実装します。

resource "aws_ssm_document" "s3_isolation_runbook" {
  name= "s3-isolation-runbook"
  document_type= "Automation"
  document_format = "YAML"

  content = <<-YAML
 schemaVersion: "0.3"
 description: "Isolate S3 bucket triggered by GuardDuty high-severity finding"
 parameters:
BucketName:
  type: String
  description: "Target S3 bucket name to isolate"
FindingId:
  type: String
  description: "GuardDuty finding ID for audit trail"
NotificationTopicArn:
  type: String
  description: "SNS topic ARN for IR team notification"
 mainSteps:
- name: ApplyIsolationPolicy
  action: aws:executeAwsApi
  description: "Deny all S3 access except IR team role"
  inputs:
 Service: s3
 Api: PutBucketPolicy
 Bucket: "{{ BucketName }}"
 Policy: >
{"Version":"2012-10-17","Statement":[{"Sid":"DenyAllExceptIRTeam",
"Effect":"Deny","Principal":"*","Action":["s3:GetObject","s3:PutObject",
"s3:DeleteObject"],"Resource":"arn:aws:s3:::{{ BucketName }}/*",
"Condition":{"StringNotLike":
{"aws:PrincipalArn":"arn:aws:iam::*:role/ir-team-role"}}}]}
- name: EnableVersioning
  action: aws:executeAwsApi
  description: "Enable versioning to preserve object history for forensics"
  inputs:
 Service: s3
 Api: PutBucketVersioning
 Bucket: "{{ BucketName }}"
 VersioningConfiguration:
Status: Enabled
- name: NotifyIRTeam
  action: aws:executeAwsApi
  description: "Send SNS notification to IR team with finding details"
  inputs:
 Service: sns
 Api: Publish
 TopicArn: "{{ NotificationTopicArn }}"
 Subject: "[SECURITY] S3 Bucket Isolated: {{ BucketName }}"
 Message: >
GuardDuty Finding {{ FindingId }} triggered automatic isolation of
s3://{{ BucketName }}. Review the finding and approve restoration
via SSM Automation when security analysis is complete.
- name: WaitForApproval
  action: aws:approve
  description: "Pause until IR team approves bucket restoration"
  timeoutSeconds: 86400
  inputs:
 NotificationArn: "{{ NotificationTopicArn }}"
 Message: >
Security analysis complete? Approve to restore S3 bucket
{{ BucketName }} and remove the isolation policy.
 MinRequiredApprovals: 1
 Approvers:
- "arn:aws:iam::{{global:ACCOUNT_ID}}:role/ir-team-role"
- name: RestoreBucketPolicy
  action: aws:executeAwsApi
  description: "Remove isolation policy to restore normal bucket access"
  inputs:
 Service: s3
 Api: DeleteBucketPolicy
 Bucket: "{{ BucketName }}"
  YAML
}

各ステップの設計意図を整理する。

Step 1 (ApplyIsolationPolicy) は IR チームの IAM Role 以外の全プリンシパルに s3:GetObject / s3:PutObject / s3:DeleteObject を Deny する。既存の Bucket Policy を上書きするため、本番環境では Step 0 として現行ポリシーを Systems Manager Parameter Store (/ir/s3-policy/{{ BucketName }}) に退避するステップを追加することを強く推奨する。退避しておくことで Step 5 での完全復元が可能になる。

Step 2 (EnableVersioning) はバージョニングを有効化し、隔離期間中のオブジェクト変更履歴を保全する。法的証拠保全の観点でも有効だ。

Step 4 (WaitForApproval)aws:approve アクションで IR チームの承認を待つ。timeoutSeconds: 86400(24 時間)を超えた場合は Runbook が失敗扱いとなるため、長期調査が予想されるインシデントではタイムアウト値を延長するか手動再起動の運用フローを事前に定義しておく。

6-4. 解析後自動再開と段階的隔離

False Positive による誤隔離を最小化するため、Severity に応じた段階的隔離を設計する。脅威検知 はレート制限の誤検知やサードパーティ製スキャンツールの通常動作を中程度アラートとして誤報することがある。全 Finding に完全隔離を適用するとサービス停止リスクが高まるため、段階分けが重要だ。

Severity 範囲判断自動対応
1.0 – 3.9 (低)通知のみSSM Automation 起動しない
4.0 – 6.9 (中)書き込み制限s3:PutObject / s3:DeleteObject のみ Deny し読み取りを維持
7.0 – 8.9 (高)完全隔離 + 承認待機全アクセス Deny (IR チーム除く) + aws:approve で人手承認
9.0 – 10.0 (重大)完全隔離 + 即時通報上記に加え PagerDuty や On-call エンジニアへ電話アラートも発報

Severity 4.0–6.9 の中程度 Finding 用に書き込み制限 Runbook を別途用意し、EventBridge Rule を 2 本構成(Severity 7 以上 / Severity 4–6.9)で運用するとより柔軟なリスク管理が実現できる。

自動再開の承認フローでは、IR チームが Slack から直接承認できる環境を整備すると MTTR が短縮する。SNS トピックに Slack のメール統合エンドポイントを登録すると aws:approve の承認メールが Slack チャンネルに届き、承認 URL を一クリックするだけで WaitForApproval ステップが完了して RestoreBucketPolicy ステップへ自動で進む。

AWS Config の自動修復と組み合わせる場合は AWS Config Conformance Pack 自動修復 を参照されたい。隔離中のバケットが Config ルールの評価で NON_COMPLIANT と判定されて誤って修復されるリスクを防ぐため、隔離期間中は対象バケットへの Config Remediation を一時停止する設定を Runbook に追加することを推奨する。

6-5. Audit Trail

隔離操作の全履歴を法令対応可能な形式で保管するため、3 つのログソースを統合する。

CloudTrail データイベントs3:PutBucketPolicy / s3:DeleteBucketPolicy / s3:PutBucketVersioning を記録し、いつ誰がバケット設定を変更したかを証明する。Data Events を有効化した Trail を専用の監査 S3 バケット(別 AWS アカウント推奨)に集約することで、攻撃者が証拠を消去できない環境を構築できる。

resource "aws_cloudtrail" "s3_audit" {
  name  = "s3-isolation-audit"
  s3_bucket_name = aws_s3_bucket.audit_logs.id
  include_global_service_events = true
  is_multi_region_trail= true
  enable_log_file_validation = true

  event_selector {
 read_write_type  = "WriteOnly"
 include_management_events = true

 data_resource {
type= "AWS::S3::Object"
values = ["arn:aws:s3:::"]
 }
  }
}

脅威検知 Findings S3 エクスポートaws_guardduty_publishing_destination リソースで有効化する。Finding を JSON 形式で S3 に保存し、Amazon Athena でのクエリや SIEM との連携が可能になる。保管期間は PCI DSS / SOC 2 準拠のために最低 90 日、ISO 27001 準拠のためには 365 日以上を推奨する。

resource "aws_guardduty_publishing_destination" "findings_export" {
  detector_id= aws_guardduty_detector.main.id
  destination_arn  = aws_s3_bucket.guardduty_findings.arn
  kms_key_arn= aws_kms_key.guardduty.arn
  destination_type = "S3"
}

SSM Automation 実行ログ は各ステップの開始時刻・終了時刻・入出力パラメータ・成功/失敗ステータスを CloudWatch Logs グループ /aws/ssm/automation/s3-isolation に出力する。3 つのログソースを Amazon Athena で結合クエリすることで、脅威検知 Finding 発報から隔離適用・承認・復元までの完全なタイムラインを単一レポートとして出力できる。§7 で解説する 事後分析 自動生成の入力データとしても再利用できる。

6-6. 3 点セット

Terraformaws_guardduty_detector(S3 Protection 有効化)/ aws_cloudwatch_event_rule(Severity 7 以上フィルター)/ aws_ssm_document "s3_isolation_runbook"(5 ステップ Runbook)/ aws_cloudtrail(Data Events 有効化)/ aws_guardduty_publishing_destination の 5 リソースが §6 の最小実装セットだ。

resource "aws_guardduty_detector" "main" {
  enable = true

  datasources {
 s3_logs {
enable = true
 }
 malware_protection {
scan_ec2_instance_with_findings {
  ebs_volumes {
 enable = true
  }
}
 }
  }
}

AWS CLI — 脅威検知 Finding の確認から手動での SSM Automation 起動まで、以下のコマンドで操作できる。

# GuardDuty Detector ID 取得
DETECTOR_ID=$(aws guardduty list-detectors \
  --query 'DetectorIds[0]' --output text)

# Severity 7 以上の S3 Finding 一覧取得
aws guardduty list-findings \
  --detector-id "${DETECTOR_ID}" \
  --finding-criteria '{"Criterion":{"severity":{"Gte":7},"type":{"Eq":["Exfiltration:S3/ObjectRead","Impact:S3/ObjectDelete"]}}}'

# Finding の詳細取得 (バケット名・リージョン確認)
aws guardduty get-findings \
  --detector-id "${DETECTOR_ID}" \
  --finding-ids "<finding-id>"

# 現行 Bucket Policy 確認 (隔離前バックアップ)
aws s3api get-bucket-policy \
  --bucket "<bucket-name>" \
  --query Policy --output text | python3 -m json.tool

# SSM Automation 手動起動 (緊急時)
aws ssm start-automation-execution \
  --document-name "s3-isolation-runbook" \
  --parameters "BucketName=<bucket-name>,FindingId=<finding-id>,NotificationTopicArn=<topic-arn>"

# SSM Automation 実行ステータス確認
aws ssm get-automation-execution \
  --automation-execution-id "<execution-id>" \
  --query 'AutomationExecution.{Status:AutomationExecutionStatus,Steps:StepExecutions[*].{Name:StepName,Status:StepStatus}}'

# 復元確認 (隔離ポリシー削除済みか確認)
aws s3api get-bucket-policy --bucket "<bucket-name>" 2>&1 \
  | grep -q "NoSuchBucketPolicy" \
  && echo "Isolation policy removed - normal access restored" \
  || echo "Policy still exists - check Automation execution"

コンソール脅威検知 > Findings で Severity フィルターを 7 以上に設定すると高リスク S3 Finding だけを一覧表示できる。Finding の Affected Resources タブからバケット名と ARN を確認し対応状況を記録する。SSM Automation の実行履歴は Systems Manager > Automation > Executions で確認でき、各ステップの詳細ログと WaitForApproval の承認待機状態をリアルタイムで可視化できる。承認操作も同画面から直接実行可能で Actions > Approve/Deny で Runbook の進行を制御する。S3 バケットの現在の状態は S3 > <バケット名> > Permissions > Bucket Policy で確認する。


7. 事後分析 自動生成

fig05: 事後分析自動生成アーキテクチャ (Bedrock連携)

SSM Automation による自動復旧が完了した後もインシデント対応は終わらない。障害の根本原因を記録し再発防止策を策定する 事後分析 が残っている。深夜対応後の疲弊した状態では手書き 事後分析 の品質が安定しない。本節では S3 に集約したイベントログを Lambda で取得し、Amazon Bedrock を呼び出して Markdown 形式の 事後分析 を自動生成し、Slack・Email・GitHub PR で配信する仕組みを Terraform で完全実装する。観測性 Vol1 CloudWatch Logs Insights で構築したログ分析基盤と組み合わせることで、インシデント発生からドキュメント完成まで自動化できる。

7-1. Bedrock Model 選定と月額コスト試算

事後分析 生成に使う Bedrock モデルはインシデントの複雑さに応じて 2 択を用意する。Claude は長大なログと複雑な因果関係の推論に強く、Amazon Nova Lite はコスト優先の定型ログに向く。

ModelModel ID入力 $1M tokens出力 $1M tokens推奨用途
Claude Sonnet 4.5us.anthropic.claude-sonnet-4-5-20251001-v1:0$3.00$15.00Sev1・複雑インシデント
Amazon Nova Liteus.amazon.nova-lite-v1:0$0.06$0.24Sev3・定型インシデント

月額コスト試算 (Claude Sonnet 4.5): 1 インシデントあたり入力 8,000 トークン・出力 2,000 トークン。月 50 件なら入力 $1.20 + 出力 $1.50 ≒ 月額 $2.70。Nova Lite なら $0.05 以下に抑制できる。Sev1 のみ Claude、Sev2-3 は Nova Lite という 2 段構えで費用対効果を最大化できます。

モデルアクセスは Amazon Bedrock コンソール (us-east-1 または ap-northeast-1) の「Model access」から事前承認が必要。承認なしに invoke-model を呼ぶと ValidationException: The provided model identifier is invalid が返る。本番リージョンで承認済みかデプロイ前に必ず確認する。

Bedrock Guardrails を有効化して PII (IP アドレス・アクセスキー) を出力前にマスクすることを強く推奨する。Guardrails の sensitiveInformationPolicyConfigAWS_ACCESS_KEY タイプを BLOCKIP_ADDRESS タイプを ANONYMIZE に設定しておくと、Lambda 側の前処理と二重防御になる。

7-2. S3 Event log 集約

SSM Automation のステップ実行ログは CloudWatch Logs グループ /aws/ssm/automation に記録される。Lambda は Logs Insights クエリでログを取得し、S3 に集約している CloudTrail・VPC Flow Logs・脅威検知 Findings と組み合わせてインシデントタイムラインを構成する。S3 Event Notification で Lambda をトリガーする設計にすることで、SSM Automation 完了後に自動的に 事後分析 生成が始まる。

resource "aws_s3_bucket" "postmortem_logs" {
  bucket = "${var.project}-postmortem-logs-${var.account_id}"
}

resource "aws_s3_bucket_server_side_encryption_configuration" "postmortem_logs" {
  bucket = aws_s3_bucket.postmortem_logs.id
  rule {
 apply_server_side_encryption_by_default {
sse_algorithm  = "aws:kms"
kms_master_key_id = aws_kms_key.postmortem.arn
 }
  }
}

resource "aws_s3_bucket_notification" "ssm_execution_log" {
  bucket = aws_s3_bucket.postmortem_logs.id

  lambda_function {
 lambda_function_arn = aws_lambda_function.postmortem_generator.arn
 events  = ["s3:ObjectCreated:*"]
 filter_prefix = "ssm-automation/"
 filter_suffix = ".json"
  }

  depends_on = [aws_lambda_permission.s3_invoke_postmortem]
}

resource "aws_lambda_permission" "s3_invoke_postmortem" {
  statement_id  = "AllowS3Invoke"
  action  = "lambda:InvokeFunction"
  function_name = aws_lambda_function.postmortem_generator.function_name
  principal  = "s3.amazonaws.com"
  source_arn = aws_s3_bucket.postmortem_logs.arn
}

SSM Automation Document の最終ステップで実行レポートを S3 に書き出すには、aws:executeScriptboto3s3.put_object を呼ぶ。オブジェクトキーは ssm-automation/{execution_id}/{timestamp}.json として Lambda の S3 イベントフィルターで捕捉する。SSM Automation が S3 に書き込む IAM 権限は s3:PutObject を特定バケットの ssm-automation/* プレフィックスに限定して最小権限を保つ。

7-3. Lambda 事後分析 生成関数

Lambda 関数は S3 ObjectCreated イベントを受け取り、ログ集約→機微情報フィルタ→Bedrock 呼び出し→S3 保存→SNS 通知の順に処理する。Lambda Powertools による構造化ログ活用については Lambda Vol3 Powertools の Layer 管理を参照。

import json, os, re, time, boto3
from datetime import datetime, timezone

s3= boto3.client("s3")
bedrock = boto3.client("bedrock-runtime", region_name=os.environ["BEDROCK_REGION"])
sns  = boto3.client("sns")
logs = boto3.client("logs")

POSTMORTEM_BUCKET = os.environ["POSTMORTEM_BUCKET"]
SNS_TOPIC_ARN  = os.environ["SNS_TOPIC_ARN"]
MODEL_ID = os.environ.get(
 "BEDROCK_MODEL_ID", "us.anthropic.claude-sonnet-4-5-20251001-v1:0"
)
GUARDRAIL_ID = os.environ.get("GUARDRAIL_ID", "")
LOG_GROUP = os.environ.get("SSM_LOG_GROUP", "/aws/ssm/automation")

def handler(event, context):
 for record in event["Records"]:
  bucket= record["s3"]["bucket"]["name"]
  key= record["s3"]["object"]["key"]
  obj= s3.get_object(Bucket=bucket, Key=key)
  log_data = json.loads(obj["Body"].read())
  exec_id  = log_data.get("ExecutionId", "unknown")

  cw_logs  = _query_cw_logs(exec_id)
  combined = json.dumps(
{"ssm_report": log_data, "cw_logs": cw_logs}, ensure_ascii=False
  )
  log_text = _mask_pii(combined)

  kwargs = {
"modelId": MODEL_ID, "contentType": "application/json",
"accept": "application/json",
"body": json.dumps({
 "anthropic_version": "bedrock-2023-05-31", "max_tokens": 4096,
 "messages": [{"role": "user", "content": _build_prompt(log_text)}]
})
  }
  if GUARDRAIL_ID:
kwargs["guardrailIdentifier"] = GUARDRAIL_ID
kwargs["guardrailVersion"] = "DRAFT"

  resp  = bedrock.invoke_model(**kwargs)
  markdown = json.loads(resp["Body"].read())["content"][0]["text"]

  ts= datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
  out_key = f"postmortem/{ts}-{exec_id[:8]}.md"
  s3.put_object(
Bucket=POSTMORTEM_BUCKET, Key=out_key,
Body=markdown.encode(), ContentType="text/markdown"
  )
  sns.publish(
TopicArn=SNS_TOPIC_ARN,
Subject=f"[Postmortem] {ts}",
Message=json.dumps({"s3_key": out_key, "markdown": markdown, "exec_id": exec_id})
  )

def _query_cw_logs(exec_id: str) -> list:
 query_id = logs.start_query(
  logGroupName=LOG_GROUP,
  startTime=int(time.time()) - 7200, endTime=int(time.time()),
  queryString=f"fields @timestamp, @message | filter @message like /{exec_id}/ | limit 100"
 )["queryId"]
 for _ in range(10):
  time.sleep(2)
  result = logs.get_query_results(queryId=query_id)
  if result["status"] in ("Complete", "Failed", "Cancelled"):
return result.get("results", [])
 return []

def _mask_pii(text: str) -> str:
 text = re.sub(r"AKIA[0-9A-Z]{16}", "AKIAXXXXXXXXXXXXXXXXXX", text)
 text = re.sub(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", "[IP_MASKED]", text)
 return text

def _build_prompt(log_text: str) -> str:
 return (
  "以下は AWS SSM Automation のインシデント実行ログです。"
  "このログを分析し、日本語で Postmortem ドキュメントを Markdown 形式で作成してください。\n\n"
  "## 必須フォーマット\n"
  "- 発生日時・影響範囲・根本原因・タイムライン・再発防止策 を含めること\n"
  "- タイムラインは箇条書き (- HH:MM ...) で記述すること\n"
  "- 再発防止策は 3 つ以上提示すること\n\n"
  f"## ログデータ\n{log_text[:12000]}"
 )
【QG-5: 事後分析 自動生成チェックリスト (Bedrock 連携)】

  • Bedrock Model 選定: Claude Sonnet 4.5 または Amazon Nova Lite で Model access 承認済み。bedrock-runtime invoke-model200 OK を返し、Markdown 形式の 事後分析 が出力されること
  • S3 Event log 集約: SSM Automation 完了後に実行レポート JSON が ssm-automation/{execution_id}/*.json キーで S3 に保存され、s3:ObjectCreated イベントが Lambda をトリガーしていること
  • Lambda 事後分析 生成: PII フィルタ処理 (アクセスキーマスク・IP アドレスマスク) を通過した上で Bedrock が呼び出され、事後分析 Markdown が postmortem/YYYYMMDDTHHMMSSZ-{exec_id}.md で S3 に保存されていること
  • Slack・Email 配信: SNS Publish が成功し、Slack チャンネル #postmortem と Email の両方に 事後分析 リンクが届いていること
  • GitHub PR 自動作成: 事後分析 Markdown が postmortem/{date}-{execution_id}.md というブランチ名で PR として作成され、Tech Lead がレビュー担当者にアサインされていること

7-4. Slack・Email 配信と GitHub PR 自動作成

SNS の後段に 2 本の Lambda Subscription を接続する。1 本目は Slack Webhook への POST、2 本目は Amazon SES 経由のメール送信と GitHub API による PR 作成を担当する。Lambda の SnapStart 活用については Lambda Vol2 SnapStart の Layer 管理を参照。

import json, os, base64, urllib.request, boto3
from datetime import datetime, timezone

SLACK_WEBHOOK  = os.environ["SLACK_WEBHOOK_URL"]
GITHUB_TOKEN= os.environ["GITHUB_TOKEN"]
GITHUB_REPO = os.environ["GITHUB_REPO"]
SES_FROM_EMAIL = os.environ["SES_FROM_EMAIL"]
SES_TO_EMAIL= os.environ["SES_TO_EMAIL"]

ses = boto3.client("ses")

def handler(event, context):
 for record in event["Records"]:
  body  = json.loads(record["Sns"]["Message"])
  subject  = record["Sns"]["Subject"]
  s3_key= body["s3_key"]
  markdown = body["markdown"]
  _post_slack(subject, s3_key, markdown[:500])
  _send_email(subject, markdown)
  _create_github_pr(s3_key, markdown)

def _post_slack(subject, s3_key, preview):
 payload = {
  "text": f"*{subject}*",
  "attachments": [{"color": "#36a64f", "text": preview + " ...", "footer": f"S3: {s3_key}"}]
 }
 req = urllib.request.Request(
  SLACK_WEBHOOK, data=json.dumps(payload).encode(),
  headers={"Content-Type": "application/json"}
 )
 urllib.request.urlopen(req, timeout=5)

def _send_email(subject, markdown):
 ses.send_email(
  Source=SES_FROM_EMAIL,
  Destination={"ToAddresses": [SES_TO_EMAIL]},
  Message={"Subject": {"Data": subject}, "Body": {"Text": {"Data": markdown}}}
 )

def _create_github_pr(s3_key, markdown):
 ts  = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
 branch = f"postmortem/{ts}"
 path= f"postmortem/{ts}.md"
 api = f"https://api.github.com/repos/{GITHUB_REPO}"
 hdrs= {
  "Authorization": f"Bearer {GITHUB_TOKEN}",
  "Accept": "application/vnd.github+json",
  "Content-Type": "application/json"
 }
 sha = _gh(f"{api}/git/ref/heads/main", hdrs)["object"]["sha"]
 _gh(f"{api}/git/refs", hdrs, {"ref": f"refs/heads/{branch}", "sha": sha})
 _gh(f"{api}/contents/{path}", hdrs, {
  "message": f"chore: auto-generate postmortem {ts}",
  "content": base64.b64encode(markdown.encode()).decode(),
  "branch":  branch
 })
 _gh(f"{api}/pulls", hdrs, {
  "title": f"[Postmortem] {ts}", "head": branch, "base": "main",
  "body": "SSM Automation による自動 Postmortem。Tech Lead のレビュー後にマージしてください。"
 })

def _gh(url, headers, data=None):
 req = urllib.request.Request(
  url, data=json.dumps(data).encode() if data else None,
  headers=headers, method="POST" if data else "GET"
 )
 with urllib.request.urlopen(req, timeout=10) as r:
  return json.loads(r.read())

GitHub PR 作成で 401 Unauthorized が返る場合は GITHUB_TOKEN の有効期限またはリポジトリスコープを確認する。403 Forbidden の場合はブランチ保護ルールで直接 push が禁止されているため Required status checks を見直す。

7-5. 機微情報フィルタ

事後分析 に機微情報が混入するリスクは 3 層で防御する。第 1 層は Lambda 前処理 (_mask_pii): アクセスキーパターン (AKIA*) と IPv4 アドレスを正規表現でマスク。第 2 層は Bedrock Guardrails: AWS_ACCESS_KEYBLOCKIP_ADDRESSANONYMIZE に設定。第 3 層は GitHub CODEOWNERS ルール: postmortem/* ファイルのマージには Tech Lead の Approve を必須化。IAM Identity Center の Permission Sets 設計については セキュリティ Vol2 IAM Identity Center を参照。

resource "aws_bedrock_guardrail" "postmortem_pii" {
  name = "${var.project}-postmortem-pii-filter"
  blocked_input_messaging= "入力に機微情報が含まれています。"
  blocked_outputs_messaging = "出力に機微情報が含まれています。"

  sensitive_information_policy_config {
 pii_entities_config {
type= "AWS_ACCESS_KEY"
action = "BLOCK"
 }
 pii_entities_config {
type= "IP_ADDRESS"
action = "ANONYMIZE"
 }
 pii_entities_config {
type= "EMAIL"
action = "ANONYMIZE"
 }
  }
}

S3 に保存した 事後分析 オブジェクトは aws:kms で暗号化し、KMS Key ポリシーで許可された IAM ロールのみアクセス可能にする。Conformance Pack による構成ドリフト検出については セキュリティ Vol3 AWS Config を参照。

7-6. 3 点セット

Terraform

resource "aws_lambda_function" "postmortem_generator" {
  function_name = "${var.project}-postmortem-generator"
  runtime = "python3.12"
  handler = "postmortem_generator.handler"
  role = aws_iam_role.postmortem_lambda.arn
  filename= data.archive_file.postmortem_lambda_zip.output_path
  timeout = 300
  memory_size= 512

  environment {
 variables = {
POSTMORTEM_BUCKET = aws_s3_bucket.postmortem_logs.bucket
SNS_TOPIC_ARN  = aws_sns_topic.postmortem_notification.arn
BEDROCK_REGION = var.bedrock_region
BEDROCK_MODEL_ID  = var.bedrock_model_id
GUARDRAIL_ID= aws_bedrock_guardrail.postmortem_pii.guardrail_id
SSM_LOG_GROUP  = "/aws/ssm/automation"
 }
  }
}

resource "aws_sns_topic" "postmortem_notification" {
  name = "${var.project}-postmortem-notification"
}

resource "aws_sns_topic_subscription" "postmortem_delivery" {
  topic_arn = aws_sns_topic.postmortem_notification.arn
  protocol  = "lambda"
  endpoint  = aws_lambda_function.postmortem_delivery.arn
}

resource "aws_iam_role_policy" "postmortem_lambda_policy" {
  role = aws_iam_role.postmortem_lambda.id
  policy = jsonencode({
 Version = "2012-10-17"
 Statement = [
{
  Sid = "S3ReadWrite", Effect = "Allow"
  Action = ["s3:GetObject", "s3:PutObject"]
  Resource = "${aws_s3_bucket.postmortem_logs.arn}/*"
},
{
  Sid = "BedrockInvoke", Effect = "Allow"
  Action = ["bedrock:InvokeModel", "bedrock:ApplyGuardrail"]
  Resource = "*"
},
{
  Sid = "SNSPublish", Effect = "Allow"
  Action = ["sns:Publish"]
  Resource = aws_sns_topic.postmortem_notification.arn
},
{
  Sid = "LogsInsights", Effect = "Allow"
  Action = ["logs:StartQuery", "logs:GetQueryResults", "logs:DescribeLogGroups"]
  Resource = "*"
}
 ]
  })
}

AWS CLI

# Bedrock モデルアクセス確認
aws bedrock list-foundation-models \
  --region us-east-1 \
  --query "modelSummaries[?contains(modelId,'claude')].{id:modelId,status:modelLifecycle.status}"

# Bedrock InvokeModel 動作確認
aws bedrock-runtime invoke-model \
  --model-id "us.anthropic.claude-sonnet-4-5-20251001-v1:0" \
  --region us-east-1 \
  --body '{"anthropic_version":"bedrock-2023-05-31","max_tokens":256,"messages":[{"role":"user","content":"Postmortem テスト"}]}' \
  --content-type "application/json" \
  --accept "application/json" \
  /tmp/postmortem_test.json && cat /tmp/postmortem_test.json

# SNS Publish テスト
aws sns publish \
  --topic-arn arn:aws:sns:ap-northeast-1:123456789012:postmortem-notification \
  --subject "[Postmortem] 動作確認" \
  --message '{"s3_key":"postmortem/test.md","markdown":"# Test","exec_id":"test-001"}'

# S3 Postmortem オブジェクト一覧
aws s3 ls s3://my-project-postmortem-logs/postmortem/ \
  --human-readable --summarize

コンソール操作

Bedrock Guardrails の動作確認は Amazon Bedrock コンソール → 左ペイン「Safeguards」→「Guardrails」から対象 Guardrail を選択し、「Test」タブで実際のログテキストを入力してマスク動作を確認する。Lambda 実行ログは CloudWatch コンソール → 「Log groups」→ /aws/lambda/{project}-postmortem-generator で確認する。S3 コンソールの postmortem/ プレフィックス配下に最新オブジェクトが生成されていれば正常動作。SLO Burn Rate アラームとの統合については 観測性 Vol3 Application Signals SLO のアラーム連携設定を参照。


8. まとめと復旧・運用編 Vol4 予告

fig06: Phase進行ロードマップ (Vol3→Vol4予告)

本記事では SSM Automation × IR Runbook を Terraform で完全 IaC 化する方法を、3 大シナリオ (Aurora 障害自動復旧・仮想マシンとコンテナ Pod 障害自動診断・S3 アクセス異常の自動隔離) と Bedrock 事後分析 自動生成で網羅した。各シナリオは独立したデプロイが可能だが、EventBridge ルールを共通化し IAM Role を最小権限設計することで、運用コストを最小化しながらクローズドループの IR 自動化基盤を構築できる。

RACI Matrix (Incident Commander・Tech Lead・Comms・Scribe) を SSM Document Parameter と IAM Role で制度化し、Severity 1-4 の階層に応じた Stop Condition と Cooldown を設定することで、過剰な自動化による本番事故リスクをコントロールする。Vol2 (FIS) で機械検証した障害シナリオを、本 Vol3 (SSM Automation) が自動復旧するクローズドループを実現した。これにより「再現性のある障害」を「再現性のある自動復旧」で閉じる运用体制が整う。

本記事の実装で得られる主要ベネフィットを 4 点で整理する。第 1 に MTTR の短縮: 検知から復旧完了まで SSM Automation が全ステップを実行するため、人間の判断待ちが発生しない。Aurora Failover の RTO 自動実測により改善効果を定量化できる。第 2 に RACI Matrix の機械化: Incident Commander・Tech Lead・Comms の役割分担を Document Parameter と IAM Role で実装するため、深夜対応でも手順の属人化が起きない。第 3 に 事後分析 品質の均一化: Bedrock InvokeModel による Markdown 生成で、疲弊した状態でも発生日時・影響範囲・根本原因・タイムライン・再発防止策を含む標準フォーマットの 事後分析 を自動作成できる。第 4 に 14 リソースとのシリーズ連携: 観測性 3 部作・セキュリティ 3 部作・Lambda・EKS・Aurora と連携することで、本記事単独では実現できない深度の自動化基盤を最短パスで構築できる。

8-1. 本記事のチートシート

本記事で扱った SSM Automation × IR Runbook の Terraform 完全 IaC 化を 5 フェーズで整理する。Vol1 で構築した DR 基盤・Vol2 の FIS 機械検証・本 Vol3 の自動復旧 Runbook が連結し、障害検知から 事後分析 配信まで人手ゼロで完結する。

フェーズ目的主要 AWS リソース
1. 検知障害・異常を即時検知CloudWatch Composite Alarm / 脅威検知 Finding / EventBridge Rule
2. 起動SSM Automation を自動起動EventBridge Target / aws_cloudwatch_event_target / SSM assumeRole
3. 実行Document Step で自動復旧aws:executeScript / aws:branch / aws:executeAwsApi / aws:approve
4. 通知復旧結果を関係者へ配信SNS Topic / Lambda Slack Webhook / SES / PagerDuty
5. 記録事後分析 自動生成S3 Event log / Lambda / Bedrock InvokeModel / GitHub PR

Terraform リソース早見表:

Terraform リソース用途
aws_ssm_documentAutomation Document 定義 (YAML・JSON)
aws_cloudwatch_event_ruleEventBridge ルール (障害検知トリガー)
aws_cloudwatch_event_targetEventBridge → SSM Automation 起動
aws_cloudwatch_composite_alarm複数アラームの OR・AND 結合
aws_iam_role + aws_iam_role_policySSM Automation 実行ロール (最小権限)
aws_lambda_function事後分析 生成・Slack 通知・GitHub PR
aws_sns_topic + aws_sns_topic_subscriptionSSM → Lambda 通知ブリッジ
aws_s3_bucket_notificationS3 Event → Lambda トリガー
aws_bedrock_guardrail事後分析 PII フィルタ
aws_cloudwatch_metric_alarm単一メトリクスアラーム (Composite Alarm の入力単位)
aws_ssm_maintenance_window計画メンテナンス中の Automation 自動スキップ設定
aws_ssm_parameterRunbook パラメータ (ARN・Region・Severity 閾値) の集中管理

IaC チェックポイント: terraform plan で変更差分を確認してから apply する。SSM Document の content フィールドは YAML・JSON どちらでも記述可能だが、document_format パラメータと一致させる必要がある。EventBridge Rule の event_pattern は JSON エスケープに注意し、jsonencode() 関数を使うと安全。IAM Role の assume_role_policyssm.amazonaws.com を Principal に指定し sts:AssumeRole を許可する。Bedrock モデルアクセスは Terraform ではなく Bedrock コンソールで手動承認が必要な点に注意する。

8-2. 落とし穴 10 選

実際の本番運用で遭遇しやすい落とし穴を厳選した。

  1. SSM Automation の assumeRole 未設定: AutomationAssumeRole を Document Parameter として明示しないと、実行者の権限で動くため本番では予期しない権限エラーが起きます。専用 IAM ロールを必ず作成して渡しましょう。

  2. EventBridge から SSM Automation を直接起動できない事前知識不足: aws_cloudwatch_event_targetarn には arn:aws:ssm:{region}:{account}:automation-definition/{doc-name} を指定する。Lambda 経由不要だが、role_arn (EventBridge 側の実行ロール) と assumeRole (SSM 側の実行ロール) の 2 本の IAM ロールが必要な点を見落としやすい。

  3. CloudWatch Composite Alarm から EventBridge を直接起動できない: Composite Alarm の alarm_actions には SNS Topic ARN のみ指定できる。EventBridge を起動するには SNS → Lambda → start-automation-execution の中継が必要。

  4. aws:approve ステップのタイムアウト設計漏れ: デフォルトのタイムアウトは無限大。本番では timeoutSeconds: 86400 (24 時間) を設定し、タイムアウト後は Deny 動作にするか専用の通知ステップへ誘導する。

  5. 脅威検知 Finding の False Positive で誤隔離: Severity フィルター (>= 7) を EventBridge Rule Pattern に入れず全 Finding を SSM Automation に流すと、低リスク Finding でバケット隔離が走る。detail.severity で閾値フィルタリングを必須化する。

  6. Aurora Global Database の Switchover 後の Secondary 再追加漏れ: SSM Automation が Switchover を完了させても元のリージョンが Secondary として再追加されない。Runbook の末尾ステップに AddRegionToGlobalClusteraws:executeAwsApi を追加する。詳細は 復旧・運用編 Vol1 の DR 構成も参照。

  7. EKS ノードグループの desiredSize=0 と PodDisruptionBudget の競合: PDB で minAvailable: 1 を設定した Deployment が存在すると UpdateNodegroupConfig がタイムアウトする。スケールダウン前に PDB を一時無効化するステップを挟むか、EKS Drain API で Pod を安全に退避させる。

  8. Bedrock モデルのリージョン制限: Bedrock InvokeModel は東京リージョン (ap-northeast-1) でも利用できるが、一部のモデルは us-east-1・us-west-2 のみ提供。bedrock_region 変数を Terraform 変数で分離し、Lambda の BEDROCK_REGION 環境変数で制御する。

  9. 事後分析 の機微情報混入 (PII): CloudTrail ログや VPC Flow Logs には内部 IP アドレスが含まれる。Lambda の _mask_pii 前処理と Bedrock Guardrails の二重フィルタを必ず適用し、GitHub PR マージには CODEOWNERS で Tech Lead の Approve を必須化する。

  10. SSM Automation 実行コストの積み上がり: aws:executeScript (Python ランタイム) は 1 回の実行あたり数秒〜数分のコンピューティングコストが発生する。意図しない EventBridge Rule のマッチで大量の自動実行が起きないよう、Rule の event_pattern を必要最小限に絞り、CloudWatch Metrics で AutomationExecutionsCount を監視する。

落とし穴への対策まとめ: 上記 10 点の共通対策は「Terraform Plan の差分を必ず確認してから Apply する」「IAM Role は SSM Automation 実行ロールと EventBridge 起動ロールを分離し最小権限を維持する」「本番適用前に FIS を使った Stage 環境での障害注入テストで Runbook の動作を機械的に検証する」の 3 点に集約できる。特に FIS との統合テストは 復旧・運用編 Vol2 で確立した GameDay 手順をそのまま流用できる。Runbook の単体テスト (SSM Automation 手動起動) と統合テスト (FIS → EventBridge → SSM Automation の chain) を CI・CD パイプラインに組み込んで変更のたびに自動検証することが、本番障害対応の安全性を高める最善策となる。

8-3. 既存 14 リソース双方向クロスリンク

本記事は復旧・運用編シリーズの第 3 巻として、既存 14 リソースと密接に連携する。本記事を公開することで、Terraform で本番運用基盤を構築する読者向けシリーズ全体が揃います。各リソースとの連携ポイントを把握することで、重複実装を避けて最短パスで本番環境を構築できます。

連携の読み方: §N の表記は本記事のセクション番号を示す。「—」の後は連携内容の概要。クロスリンク先の記事を事前に読んでおくと本記事の実装がより深く理解できるが、本記事単独でも Aurora・仮想マシンとコンテナ・S3 脅威検知・事後分析 の 4 シナリオを完結させることができる。

【既存 14 リソースクロスリンク一覧】

本記事公開時点で、14 リソースのうち復旧・運用編 Vol3 (本記事) を加えた 14 本目が完成する。各記事が単独で完結しながら、シリーズ全体として「構築 (Lambda・EKS・Aurora) → 観測 (CloudWatch・X-Ray・SLO) → セキュリティ (脅威検知・IAM・Config) → 復旧・運用 (DR・Chaos・IR Runbook)」という技術スタックの全层をカバーする体系が整った。Vol4 (Multi-Region Active・Active vs Active・Passive) でシリーズを完結させる。

8-4. 復旧・運用編 Vol4 予告

本記事 (Vol3) では SSM Automation × IR Runbook の Terraform 完全 IaC 化を完結させた。Vol4 では、Vol1〜Vol3 で構築した本番運用基盤を Multi-Region Active・Active vs Active・Passive アーキテクチャへ拡張する。Active・Active 構成では 2 つのリージョンが同時に読み書きトラフィックを受け持つため、Aurora Global Database のコンフリクト解決・Route 53 Latency Based Routing の切り替え設計・DynamoDB Global Tables の結果整合性が主要実装テーマになる。Active・Passive との設計トレードオフとコスト比較も Terraform で具体的に示す。

Vol4 の主要実装テーマ (予告):

テーマActive-ActiveActive-Passive
RTO 目標< 1 分 (自動フェイルオーバー)5〜15 分 (手動・半自動)
RPO 目標ほぼ 0 (同期レプリケーション)数十秒〜数分 (非同期)
Aurora Global DBWrite Forwarding 有効化Secondary リードのみ
Route 53Latency-Based + Health CheckFailover ルーティング
コスト2 倍近い (両リージョン本番稼働)1.2〜1.5 倍 (Warm Standby)
Terraformaws_rds_global_cluster + aws_route53_recordaws_route53_health_check + Failover

Vol3 で確立した SSM Automation による自動復旧 Runbook は、Vol4 の Active・Active アーキテクチャでも引き続き活用する。フェイルオーバー完了後の状態確認・SSM Automation 実行 + 事後分析 自動生成・双方向クロスリージョン Runbook 連携が Vol4 の固有テーマとなる。

Vol3 → Vol4 の接続ポイント:

  • SSM Automation Runbook の Multi-Region 化: Vol3 で実装した AuroraAutoRecovery Document を 2 リージョンに展開し、どちらのリージョンで Aurora Global Database の書き込みが止まっても Runbook が起動するよう EventBridge ルールを両リージョンに配置する。
  • Route 53 Failover ヘルスチェックと SSM Automation の連携: Route 53 のヘルスチェック失敗を EventBridge (CloudWatch Alarm 経由) で捕捉し、SSM Automation が Route 53 レコードの自動切り替えと Aurora Switchover を順次実行する。
  • DynamoDB Global Tables の整合性確認 Runbook: Active・Active では両リージョンへの同時書き込みが発生する。Vol3 の aws:executeScript ステップを拡張し、整合性チェック (DynamoDB scan → カウント比較) と不整合時のアラート通知を追加する。
  • 事後分析 の Cross-Region 拡張: Vol3 で実装した 事後分析 自動生成 Lambda を us-east-1 と ap-northeast-1 の両リージョンにデプロイし、どちらのリージョンで Runbook が実行されても 事後分析 が生成・GitHub PR 化される仕組みを Vol4 で完成させる。
【次回予告: 復旧・運用編シリーズ Vol4】
Multi-Region Active-Active vs Active-Passive 設計実装。Vol1 と Vol2 と Vol3 で確立した本番運用基盤を、Active-Active アーキテクチャへ拡張する設計トレードオフと Terraform 実装。Route 53・Aurora Global Database・DynamoDB Global Tables の 3 点セットで完全 IaC 化する。

  • Aurora Global Database の Write Forwarding + Switchover 自動化 (Vol1 DR 基盤の拡張)
  • Route 53 Latency-Based Routing + Failover Health Check の Terraform 実装
  • DynamoDB Global Tables の結果整合性確認 SSM Automation Runbook
  • Active-Active vs Active-Passive のコスト・RTO・RPO トレードオフ試算