運用・保守要件定義書とは

運用・保守要件定義書は、システムリリース後の安定稼働を維持するために必要な監視・バックアップ・デプロイ・インシデント対応・メンテナンスの要件を定義した設計書です。開発チームと運用チームの引き継ぎ基準書としても機能します。

💡 運用要件は開発と並行して定義する

「運用は開発が終わってから考える」という進め方は失敗の元です。監視項目やアラート閾値はシステムの構成が決まった時点で定義し、ログ出力形式やメトリクス収集方法は開発中に実装します。リリース直前に「監視できない」状態になることを防ぐためにも、基本設計フェーズで運用要件を定義します。

① システム監視要件

死活監視・リソース監視・アプリケーション監視の三層で監視体制を定義します。

監視種別監視対象・指標閾値(警告)閾値(緊急)監視間隔
死活監視Webサーバー・APサーバー・DBサーバーのping / ヘルスチェックエンドポイントタイムアウト発生2回連続失敗30秒
CPU使用率APサーバー・DBサーバーのCPU使用率70%以上が5分継続90%以上が1分継続1分
メモリ使用率APサーバー・DBサーバーのメモリ使用率80%以上が5分継続95%以上が1分継続1分
ディスク使用率全サーバーのディスク使用率80%以上90%以上5分
レスポンスタイム代表画面・APIのレスポンスタイム3秒以上10秒以上 / タイムアウト1分
エラーレートHTTP 5xx エラー率1%以上5%以上1分
DBコネクション数DBコネクションプールの使用率70%以上90%以上1分
バッチ処理監視バッチの実行完了・エラー発生想定終了時刻から30分超過エラー終了時5分

② バックアップ・リストア要件

対象バックアップ方式頻度保持期間保存場所
DBデータ(フルバックアップ)pg_dumpall(論理バックアップ)週1回(日曜2:00)12ヶ月S3(別リージョン)
DBデータ(差分)WALアーカイブ(物理バックアップ)継続的30日S3(同リージョン)
アプリケーションコードGitリポジトリ(タグ管理)リリース時永続保持GitLab / GitHub
アップロードファイルS3クロスリージョンレプリケーションリアルタイム5年S3(別リージョン)
設定ファイル・秘密情報AWS Secrets Manager / Vault変更時バージョン管理マネージドサービス

⚠️ リストアテストを定期実施する

バックアップが取得されていても、リストアできなければ意味がありません。四半期に1回、実際にバックアップからリストアし、データが正常に復旧できることを確認するリストアテストを計画に含めます。特にRPO(目標復旧時点)を1時間と定義している場合、1時間前のデータが実際に復旧できるか検証します。

③ デプロイ・リリース手順

デプロイ方式とリリース時の手順・ロールバック方針を定義します。

項目定義内容
デプロイ方式Blue/Greenデプロイ(ダウンタイムなしリリース)
リリース頻度本番環境: 月1〜2回(定例リリース)。緊急修正: 随時
デプロイ手順① ステージング環境でリグレッションテスト → ② 本番DB マイグレーション → ③ Blue環境(新バージョン)デプロイ → ④ ヘルスチェック確認 → ⑤ ロードバランサー切り替え → ⑥ Green環境(旧バージョン)待機
ロールバック手順問題発生時にロードバランサーをGreen環境(旧バージョン)に即時切り戻し。所要時間目標: 5分以内
DBマイグレーション方針後方互換性を維持したマイグレーションのみ本番適用。カラム削除は2リリース後に実施(展開後1ヶ月以上経過後)

④ インシデント対応・エスカレーション

インシデント発生時の対応フローと連絡体制を定義します。

重大度定義初動対応目標時間エスカレーション先
P1(致命的)本番環境が完全停止 / データ損失・漏洩の可能性15分以内即時: 開発リーダー・インフラリーダー・管理職
P2(重大)主要機能が利用不可 / 全ユーザーに影響30分以内30分以内に開発リーダー・インフラリーダー
P3(中)一部機能が利用不可 / 一部ユーザーに影響 / 性能劣化2時間以内翌営業日までに開発チームが対応
P4(低)軽微な表示不具合・操作性の問題 / 単一ユーザーのみ影響次回定例リリースで対応バックログに登録

⑤ 定期メンテナンス計画

メンテナンス種別頻度内容所要時間
定期メンテナンス(計画停止)月1回(第2日曜 02:00〜04:00)OSパッチ適用・DBバキューム・ログローテーション最大2時間
セキュリティパッチCVSSスコア7.0以上: 1週間以内 / その他: 翌月定例OS・ミドルウェア・依存ライブラリの脆弱性対応30分〜2時間
DB統計情報更新日次(深夜3:00)ANALYZE実行によるクエリプランの最適化10〜30分
ログアーカイブ月次古いログのS3退避・ローカルログの削除1時間
SSL証明書更新有効期限30日前(自動更新失敗時の手動対応)証明書の手動更新・設置・再起動30分

⑥ ユーザーサポート・ヘルプデスク要件

項目定義内容
サポート受付チャネル社内ヘルプデスクポータル(Jira Service Desk)/ メール
受付時間平日 9:00〜18:00。時間外は翌営業日対応
初回応答時間(SLO)P1: 1時間以内 / P2: 4時間以内 / P3〜P4: 翌営業日以内
問い合わせ対応フロー受付 → トリアージ(L1サポート) → 技術調査(L2: 開発チーム)→ 解決・回答 → クローズ
ナレッジベースよくある質問(FAQ)をHelp Centerに整備。L1で解決できる問い合わせを70%以上に設定

Python Tips — システム死活監視スクリプト

Python — 死活監視・メール通知スクリプト
"""
複数エンドポイントの死活監視を行い、異常検知時にメール通知するスクリプト。
pip install requests

活用例: Cronで5分おきに実行し、2回連続失敗で通知する運用監視ツール
"""
import requests
import smtplib
import json
from pathlib import Path
from datetime import datetime
from dataclasses import dataclass, field
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart


# 監視設定
TARGETS = [
    {"name": "Webサーバー (本番)", "url": "https://example.com/health/", "timeout": 10},
    {"name": "API サーバー", "url": "https://api.example.com/api/v1/health/", "timeout": 10},
    {"name": "管理画面", "url": "https://admin.example.com/health/", "timeout": 10},
]

ALERT_EMAIL = "ops-team@example.com"
SMTP_HOST = "smtp.example.com"
SMTP_PORT = 587
SMTP_USER = "monitor@example.com"
SMTP_PASS = "your-smtp-password"

# 連続失敗カウント保存ファイル
STATE_FILE = Path("/var/monitor/health_state.json")


@dataclass
class CheckResult:
    name: str
    url: str
    success: bool
    status_code: int = 0
    response_time_ms: float = 0.0
    error: str = ""


def check_health(target: dict) -> CheckResult:
    """エンドポイントにGETリクエストを送りヘルスチェックを行う"""
    try:
        start = datetime.now()
        resp = requests.get(target["url"], timeout=target["timeout"], allow_redirects=False)
        elapsed_ms = (datetime.now() - start).total_seconds() * 1000
        success = (200 <= resp.status_code < 300)
        return CheckResult(
            name=target["name"], url=target["url"],
            success=success, status_code=resp.status_code,
            response_time_ms=elapsed_ms,
            error="" if success else f"HTTP {resp.status_code}"
        )
    except requests.Timeout:
        return CheckResult(name=target["name"], url=target["url"], success=False, error="タイムアウト")
    except Exception as e:
        return CheckResult(name=target["name"], url=target["url"], success=False, error=str(e))


def load_state() -> dict:
    if STATE_FILE.exists():
        return json.loads(STATE_FILE.read_text())
    return {}


def save_state(state: dict) -> None:
    STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
    STATE_FILE.write_text(json.dumps(state, indent=2, ensure_ascii=False))


def send_alert(subject: str, body: str) -> None:
    """管理者にアラートメールを送信する"""
    msg = MIMEMultipart()
    msg["From"] = SMTP_USER
    msg["To"] = ALERT_EMAIL
    msg["Subject"] = subject
    msg.attach(MIMEText(body, "plain", "utf-8"))
    with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
        server.starttls()
        server.login(SMTP_USER, SMTP_PASS)
        server.send_message(msg)


def run_health_checks() -> None:
    state = load_state()
    now_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    alerts = []

    for target in TARGETS:
        result = check_health(target)
        key = target["name"]
        prev = state.get(key, {"fail_count": 0, "alerted": False})

        if result.success:
            if prev["alerted"]:
                # 復旧通知
                send_alert(
                    f"[RESOLVED] {key} が復旧しました",
                    f"システム: {key}\nURL: {result.url}\n復旧時刻: {now_str}"
                )
            state[key] = {"fail_count": 0, "alerted": False}
            status = f"✅ {key}: OK ({result.response_time_ms:.0f}ms)"
        else:
            fail_count = prev["fail_count"] + 1
            alerted = prev["alerted"]
            state[key] = {"fail_count": fail_count, "alerted": alerted}

            if fail_count >= 2 and not alerted:
                # 2回連続失敗で初回アラート
                alerts.append(f"❌ {key}: {result.error} ({fail_count}回連続失敗)")
                state[key]["alerted"] = True

            status = f"❌ {key}: {result.error} (失敗{fail_count}回)"

        print(f"[{now_str}] {status}")

    if alerts:
        body = "\n".join(alerts)
        body += f"\n\n確認時刻: {now_str}"
        send_alert(f"[ALERT] {len(alerts)}件のエンドポイントが応答しません", body)
        print(f"⚠️ アラート送信: {len(alerts)}件")

    save_state(state)


if __name__ == "__main__":
    run_health_checks()

本番環境ではPrometheus + Grafanaの導入を推奨

このスクリプトはシンプルな死活監視に有効ですが、本番環境では Prometheus(メトリクス収集)+ Grafana(可視化)+ Alertmanager(通知)の構成が標準的です。Prometheus の blackbox_exporter を使うと HTTP エンドポイントの死活監視とレスポンスタイム計測をYAMLで宣言的に設定できます。