運用・保守要件定義書とは
運用・保守要件定義書は、システムリリース後の安定稼働を維持するために必要な監視・バックアップ・デプロイ・インシデント対応・メンテナンスの要件を定義した設計書です。開発チームと運用チームの引き継ぎ基準書としても機能します。
💡 運用要件は開発と並行して定義する
「運用は開発が終わってから考える」という進め方は失敗の元です。監視項目やアラート閾値はシステムの構成が決まった時点で定義し、ログ出力形式やメトリクス収集方法は開発中に実装します。リリース直前に「監視できない」状態になることを防ぐためにも、基本設計フェーズで運用要件を定義します。
① システム監視要件
死活監視・リソース監視・アプリケーション監視の三層で監視体制を定義します。
| 監視種別 | 監視対象・指標 | 閾値(警告) | 閾値(緊急) | 監視間隔 |
|---|---|---|---|---|
| 死活監視 | 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 — システム死活監視スクリプト
"""
複数エンドポイントの死活監視を行い、異常検知時にメール通知するスクリプト。
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で宣言的に設定できます。