第74回 実務で回すAIのランブックと運用ドキュメント作成 — Pythonで自動生成・検証・定期演習する手順

運用中に「誰がやるか分からない」「手順が曖昧で時間がかかる」といった経験はありませんか。オンコールや一次対応が一人で回す現場では、ランブック(プレイブック)があっても実際に動かせる形になっていないと意味がありません。本記事では、現場でそのまま使えるランブックの最小構成と、Pythonでの自動生成・自動検証・定期ドリル運用までを、コピペで使えるテンプレートとスニペットで提供します。

1) ランブックとは・運用ドキュメントの最小構成

ランブックは「誰が見ても同じ対応ができる」ことを目的とした手順書です。特に重要なのは以下の要素が明示されていることです。

  • トリガー(いつこれを読むか)
  • 優先度(どれだけ急ぐか)
  • 実行者(誰がやるか。代替者も)
  • 手順(主語・ツール・コマンド・期待結果が明確)
  • エスカレーション(失敗時の連絡先と条件)

下の表は、ランブックの「最小構成」を要素ごとにまとめたものです。

要素 説明
トリガー どのアラート・状態で実行するか APIエラー率が5分間で上昇 > 5%
優先度 対応の緊急度 P1(サービス停止)/P2(機能劣化)
手順 具体的な操作と期待結果 ログ確認 → プロセス再起動 → サービス確認
期待結果 手順成功を判定する基準 応答200、エラー率が0.5%以下に回復
エスカレーション 失敗時の連絡先・時間帯・次の手順 オンコール2nd、SREチーム、管理者に報告

2) ランブック設計の実務ルール(優先度, トリガー, エスカレーション)

実務で使えるランブックにするためのルールを簡潔に示します。

  • トリガーは定量化する(しきい値・時間窓を明示)
  • 優先度は影響範囲で決める(ビジネス影響→技術影響の順)
  • エスカレーション条件は「何が起きたら」「誰に」「何分以内に」連絡するかを書く
  • 主語を明確にする(例:「オンコールが」ではなく「対応者が」)
  • 期待結果は定量的に(ログの文言、HTTPステータスなど)

以下は短いルール表(運用チェックリスト)です。

項目 チェック
トリガーは測れるか はい/いいえ(しきい値があるか)
手順は一人で実行可能か はい/いいえ(権限やツールは明示)
期待結果は判定可能か はい/いいえ(定量基準があるか)

3) Pythonでランブックを自動生成する手順(テスト・メタデータ→HTML変換)

ここでは、YAMLで手順を定義し、PythonでHTMLテンプレートに埋めて配布可能なランブックを生成する最小実装例を示します。手順は自動検証できるよう構造化します。

1) 手順定義(YAMLサンプル)

---
metadata:
  title: "APIエラー率上昇対応"
  id: "api-error-01"
  priority: "P2"
  trigger:
    metric: "api_error_rate"
    threshold: 0.05
    window: "5m"
  owners:
    - name: "オンコール"
      contact: "oncall@example.com"
steps:
  - id: "s1"
    title: "ログを確認する"
    description: "直近10分のエラーログを確認し、エラーの定義(ユーザIDやパス)を特定する"
    commands:
      - "kubectl logs -n svc api-service --since=10m | grep ERROR"
    expected:
      type: "contains"
      value: "ERROR"
  - id: "s2"
    title: "プロセスを再起動する"
    commands:
      - "kubectl rollout restart deployment/api-deployment -n svc"
    expected:
      type: "http"
      url: "https://api.example.com/health"
      status: 200
  - id: "s3"
    title: "サービス状態を確認する"
    commands:
      - "curl -sS https://api.example.com/health | jq .status"
    expected:
      type: "equals"
      value: "ok"

2) HTMLテンプレート(ランブックHTMLをそのまま配布可能)

以下はコピペしてすぐ使えるHTMLテンプレートです。実装ではこのテンプレートにPythonで値を埋めます。

<div class="runbook" id="{{id}}">
  <h2>{{title}}</h2>
  <p><strong>優先度:</strong> {{priority}}</p>
  <p><strong>トリガー:</strong> {{trigger.metric}} > {{trigger.threshold}} ({{trigger.window}})</p>
  <h3>連絡先</h3>
  <ul>
    {% for o in owners %}
    <li>{{o.name}} - {{o.contact}}</li>
    {% endfor %}
  </ul>
  <h3>手順</h3>
  {% for s in steps %}
  <div class="step" id="{{s.id}}">
    <h4>{{s.title}}</h4>
    <p>{{s.description | default('')}}</p>
    <h5>コマンド</h5>
    <ul>
      {% for c in s.commands %}
      <li><code>{{c}}</code></li>
      {% endfor %}
    </ul>
    <h5>期待結果</h5>
    <p>{{ s.expected.type }} - {{ s.expected.value | default(s.expected.url | default(s.expected.status | default(''))) }}</p>
  </div>
  {% endfor %}
</div>

(テンプレートはJinja2形式です。Jinja2が使えない場合は Python の string.Template で置換しても構いません。)

3) Python最小実装スニペット(生成と検証)

以下はYAMLを読み、テンプレートに埋めてHTMLを出力し、簡易チェックを行うスクリプトの最小実装例です。依存: pyyaml, jinja2(インストール: pip install pyyaml jinja2)

#!/usr/bin/env python3
# runbook_gen.py
import sys
import yaml
from jinja2 import Template
from pathlib import Path
import requests

TEMPLATE = Path('runbook_template.html.j2').read_text() if Path('runbook_template.html.j2').exists() else '''{{ raw }}'''

def load_yaml(path):
    with open(path, 'r', encoding='utf-8') as f:
        return yaml.safe_load(f)

def render(runbook):
    tmpl = Template(TEMPLATE)
    return tmpl.render(**runbook)

def basic_validate(runbook):
    errors = []
    if 'metadata' not in runbook:
        errors.append('missing metadata')
    else:
        md = runbook['metadata']
        for k in ('title','id','priority','trigger'):
            if k not in md:
                errors.append(f'metadata.{k} missing')
    if 'steps' not in runbook or not isinstance(runbook['steps'], list):
        errors.append('steps must be a list')
    else:
        for i, s in enumerate(runbook['steps']):
            if 'id' not in s or 'title' not in s or 'commands' not in s:
                errors.append(f'step[{i}] missing id/title/commands')
    return errors

# 簡易的な期待結果チェック(http/equality/contains)
def check_expected(step):
    exp = step.get('expected')
    if not exp:
        return True, 'no expected'
    t = exp.get('type')
    try:
        if t == 'http':
            r = requests.get(exp['url'], timeout=5)
            ok = r.status_code == exp.get('status', 200)
            return ok, f'http {r.status_code}'
        elif t == 'equals':
            # 実際はコマンド出力を検証するが、ここではモック
            return True, 'equals (mock)'
        elif t == 'contains':
            return True, 'contains (mock)'
    except Exception as e:
        return False, str(e)
    return True, 'unknown type'

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print('usage: runbook_gen.py runbook.yml')
        sys.exit(2)
    rb = load_yaml(sys.argv[1])
    errs = basic_validate(rb)
    if errs:
        print('Validation errors:')
        for e in errs:
            print('-', e)
        sys.exit(1)
    html = render(rb)
    out = Path(rb['metadata']['id'] + '.html')
    out.write_text(html, encoding='utf-8')
    print('Wrote', out)
    # 期待結果の簡易チェック
    for s in rb.get('steps', []):
        ok, msg = check_expected(s)
        print(s.get('id'), s.get('title'), '=>', 'OK' if ok else 'FAIL', msg)

(実運用ではコマンド実行環境のラッパーや権限管理を必ず実装してください。ここでは依存関係と基本構造の提示が目的です。)

4) ランブック手順を自動検証するテストパターン(チェックリストの機械判定)

ランブックは「書いて終わり」ではなく、定期的に動かして検証することが重要です。自動検証は下記のパターンに分けられます。

  • 静的検証: 必須フィールド、コマンド構造、機密情報の混入チェック
  • 動的検証: 期待結果に対する実際のAPI呼び出しやモック実行
  • 演習検証: ドリルで人が手順を踏んだログを収集して結果を評価

Pytestを使った簡易テスト例(モック)を示します。

# test_runbook.py
import runbook_gen as rg

def test_basic_validation():
    rb = rg.load_yaml('example.yml')
    errs = rg.basic_validate(rb)
    assert errs == []

def test_step_expected(monkeypatch):
    # HTTP呼び出しをモック
    class FakeResp:
        status_code = 200
    def fake_get(url, timeout=5):
        return FakeResp()
    monkeypatch.setattr(rg.requests, 'get', fake_get)
    rb = rg.load_yaml('example.yml')
    for s in rb['steps']:
        ok, _ = rg.check_expected(s)
        assert ok

5) 定期演習(ドリル)とレビューサイクル

ドリルは実際に担当者が手順を実行して、時間、成功/失敗、障害発生時の判断を記録する仕組みです。運用ルールとKPIの例を示します。

項目 推奨値
検証頻度 月1回(重要ランブックは週1回)
成功率目標 90%以上
ログ保存期間 12ヶ月(ドリルの振り返り用)

ドリル用のシナリオはPythonでランダム化して作成し、CSVでログ化するとレビューがしやすくなります。簡単な生成例:

import csv
import random

scenarios = ['partial outage', 'high error rate', 'slow response']
with open('drills.csv', 'w', newline='', encoding='utf-8') as f:
    w = csv.writer(f)
    w.writerow(['date','scenario','executor','result','duration_secs','notes'])
    for i in range(10):
        w.writerow([
            '2026-05-26',
            random.choice(scenarios),
            'alice',
            random.choice(['pass','fail']),
            random.randint(60, 1800),
            ''
        ])

6) 配布・アクセス制御・更新運用の実際

配布は社内WikiやCMSにHTMLを配置します。注意点は以下です。

  • HTML配布は読みやすさ優先。PDF化はオフライン参照用のみ。
  • 機密情報はテンプレートに含めない(シークレットは参照IDのみ)
  • 更新トリガーを明確化(例:モデル更新後、重大インシデント後、月次レビュー後)
  • バージョン管理(Git)で変更履歴を残す
  • アクセス制御:編集権限と閲覧権限を分離する

更新タイミングの具体例:

トリガー 対応
モデル/サービスのリリース リリース前にランブックのレビューとテスト
重大インシデント 事後レビューでランブックに反映(72時間内に更新案)
月次ドリルで失敗発生 原因分析と手順の明確化(1週間以内に反映)

7) よくある失敗例と対策

現場でよく見かける失敗と、その対策を挙げます。

失敗例 原因 対策
手順が曖昧で担当者が混乱する 主語や期待結果が不明確 手順を「誰が」「何を」「どう確認するか」で書き直す
テンプレートにシークレットが入っている コピペ運用で秘匿情報が混在 テンプレートには参照IDのみ、実際の値はSecrets Managerで管理
自動化しすぎて現場判断ができなくなる 自動化が唯一の手順になっている 人による確認ポイントを必ず残す(チェックリスト)

まとめ

ランブックは書くだけで終わらせず、「生成」「検証」「演習」「更新」を回すことで初めて現場で使える資産になります。本記事で提供したものは最小構成のテンプレートと、Pythonでの自動生成・簡易検証・ドリル生成のサンプルです。まずは1本の重要なランブックを選び、YAMLで定義して自動生成→月1回のドリルを回すことを目標にしてください。

関連回・参照:

ダウンロード用のサンプル(YAML、テンプレート、runbook_gen.py、test_runbook.py、drills.csv)は記事末のリンクから取得できる想定です。まずは記事内のYAMLとスクリプトをコピーして、ローカルで動かしてみてください。

Manage AI シリーズ: AIとPythonの実務 — 一人でも回る運用を目指す連載です。