Article

即日対応可能なBCP対策

高田晃太郎
即日対応可能なBCP対策

Uptime Instituteの2024年年次調査によれば、重大な障害の過半が10万ドル超の損失を生むという報告が続いています。加えて直近の調査では、約20%の重大障害が100万ドル以上のコストに達したとされています¹。国内でも、中堅・中小企業の事業継続計画(BCP: Business Continuity Plan)策定率は二桁台にとどまるという政府公表資料があり²、一方で国内のクラウド利用は年々上昇しています³。つまり、「紙の計画書としてのBCP」だけでは現場の損失を止められない局面が増えています。必要なのは、同日中に稼働し、翌朝には検証済みである技術的コントロール──すなわちディザスタリカバリ(DR: 災害復旧)を含む、動く事業継続の仕組みです。筆者は、BCPを「ドキュメント」ではなく「自動で動くスクリプトと設定の集合」と見なし、最短で立ち上げ、翌週に拡張する二段ロケットで設計することを提案します。

即日で機能する“最小限のBCP”の設計思想

同日に立ち上げるBCP/DRは完璧を目指しません。目的は損失曲線を即座に寝かせ、事業継続(業務継続)のレジリエンスを底上げすることです。そこで最初に指標を数値化します。主要系はRTO(復旧時間目標)を2時間、RPO(目標復旧時点)を15分とし、長時間処理のバッチはRTOを業務終了まで、RPOを1時間に分ける、といった現実的な初期値から始めます。数値化の効用は、エンジニアリングの優先順位が即座に決まることです。RPO15分が必要なら、継続的なスナップショットとトランザクションログの保全が要件になり、通知設計は単なるチャット投稿ではなく、到達保証とエスカレーション(読まれなかったときに別経路へ自動的に引き継ぐ)を伴う仕組みが浮かび上がります。

設計の起点は単一障害点(SPOF)の削減です。クラウドではリージョン全体を跨ぐ冗長化は工数が大きくなりがちですが、当日のゴールは「単一AZ(アベイラビリティゾーン)故障、単一ノード故障、単一人的ボトルネック」の排除に置きます。技術的にはマルチAZ、ヘルスチェックとフェイルオーバーDNS、イミュータブル(改ざん不能)なバックアップ、そして即応の連絡網です。運用面では決裁権限の即時委譲と、Severity定義をひと言で決めておくことが効きます。Sev-1は顧客影響が広範で代替手段なし、Sev-2は部分影響かつ回避策あり、といった粒度で十分です。判断が迷わなければ、復旧に純粋な時間を投じられます。

一日の進め方の一例としては、朝のキックオフでRTO/RPOを掲示し、昼までに通知とバックアップを動かし、夕方までにDNSフェイルオーバーとステータス周知の最低限を整える。夜は30分の擬似障害演習で接続切替と復元の所要時間を計測し、メトリクスとログを持ち帰って改善の洗い出しに進む──このテンポが、即日対応のBCP対策として現実的です。

通知・監視を90分で“到達保証付き”にする

インシデントの初動は速さよりも、確実に「届くこと」が重要です。チャットだけでは読まれないことがあり、同日BCPでは連絡の多重化を組み込みます。SlackやTeamsにはWebhook連携があるため、監視からの自動投稿と、冗長経路としてのメール・モバイルプッシュの二段構えにします。商用のオンコール(待機)サービスがあればトライアルで即日導入できますが、最初の90分で最低限の到達保証を自前で確保することも可能です。

以下はSlackのIncoming Webhookを用いて、クリティカル時のみ即時通知を飛ばす最小のPythonスニペットです(ダミー値は自環境に置き換え)。プロキシ環境や認証エラーを考慮し、送達失敗時のリトライとバックアップ通知を併設します。

import os
import json
import time
import urllib.request

WEBHOOK_URL = os.environ.get("SLACK_WEBHOOK_URL")
BACKUP_EMAIL = os.environ.get("BACKUP_EMAIL")

payload = {
    "text": "[SEV-1] APIエラーレートが閾値超過。自動フェイルオーバー評価を開始します。",
    "username": "incident-bot",
    "icon_emoji": ":rotating_light:"
}

def post_slack(payload, retries=3):
    data = json.dumps(payload).encode('utf-8')
    req = urllib.request.Request(WEBHOOK_URL, data=data, headers={'Content-Type': 'application/json'})
    for i in range(retries):
        try:
            with urllib.request.urlopen(req, timeout=5) as r:
                if r.status == 200:
                    return True
        except Exception:
            time.sleep(2 ** i)
    return False

if not post_slack(payload):
    # 送達失敗時はバックアップ経路へ(例:シンプルなSMTP送信)
    import smtplib
    from email.mime.text import MIMEText
    msg = MIMEText(payload["text"])
    msg["Subject"] = "[SEV-1] Slack送達失敗: 重要通知"
    msg["From"] = "no-reply@example.com"
    msg["To"] = BACKUP_EMAIL
    with smtplib.SMTP("smtp.example.com", 587) as s:
        s.starttls(); s.login("user", "pass"); s.send_message(msg)

監視側からのトリガーは既存のPrometheusアラートやクラウドのアラームを流用できます。重要なのは、通知テキスト内で決裁者と切替条件が即読できることです。「Sev-1の場合、ヘルスチェック連続失敗3回でDNSをフェイルオーバー、完了後にステータス更新」という一行を常に添えます。運用マニュアルを開かずに動ける粒度が肝です。あわせて、当面のSLO(サービス目標値)は「検知からSlack到達まで30秒以内、初回エスカレーションまで3分以内」といった短い数値から始めると、継続的改善が回りやすくなります。

内部の運用プラクティスを体系化したい方は、インシデント体制の設計観点をまとめた関連記事も参考になります。

バックアップを“破壊不能”に。RPO15分を実現する

RPOを語るとき、同一アカウント・同一リージョン内のコピーだけでは足りません。ランサムウェアや誤削除への耐性として、バージョニングとオブジェクトロック(WORM: Write Once Read Many、一定期間は削除・上書き不可)を今すぐ有効化します。S3のオブジェクトロックはWORMモデルでの保護を提供し、一定期間の削除や上書きを防止できます⁴。ストレージはクラウド標準機能で十分に戦えます。同日に完了させるなら、S3のバージョニングとオブジェクトロック、データベースの自動バックアップ、そして暗号化とクロスアカウントのリカバリ権限が実用的です。

S3バケットのバージョニングとオブジェクトロックをCLIで即時適用する例を示します。検証用バケットに対して行い、完了後に運用バケットへ展開します。

# バージョニングの有効化
aws s3api put-bucket-versioning \
  --bucket my-prod-backup \
  --versioning-configuration Status=Enabled

# オブジェクトロックの有効化(新規バケット限定)
aws s3api create-bucket --bucket my-prod-backup-locked --object-lock-enabled-for-bucket
aws s3api put-object-lock-configuration \
  --bucket my-prod-backup-locked \
  --object-lock-configuration 'ObjectLockEnabled=Enabled,Rule={DefaultRetention={Mode=COMPLIANCE,Days=7}}'

リレーショナルデータベースを使っている場合は、自動バックアップとトランザクションログの保持期間を同日中に見直します。以下はRDSでスナップショット保持とマルチAZを整える例です。

# 7日保持、マルチAZ化を適用
aws rds modify-db-instance \
  --db-instance-identifier prod-db \
  --backup-retention-period 7 \
  --preferred-backup-window 04:00-04:30 \
  --multi-az \
  --apply-immediately

アプリケーションの論理バックアップも併用すると復旧の選択肢が増えます。GitHub Actionsを使えば、同日中に暗号化したダンプをオブジェクトストレージへ送る夜間ジョブを用意できます。

name: nightly-db-backup
on:
  schedule:
    - cron: '0 18 * * *'  # JST 03:00
jobs:
  dump-and-upload:
    runs-on: ubuntu-latest
    steps:
      - name: Install client
        run: sudo apt-get update && sudo apt-get install -y postgresql-client
      - name: Dump
        env:
          PGPASSWORD: ${{ secrets.DB_PASSWORD }}
        run: |
          pg_dump -h ${{ secrets.DB_HOST }} -U ${{ secrets.DB_USER }} -F c -Z 9 -f backup.dump ${{ secrets.DB_NAME }}
          openssl enc -aes-256-cbc -salt -in backup.dump -out backup.dump.enc -pass pass:${{ secrets.BACKUP_PASS }}
      - name: Upload to S3 compatible
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: ap-northeast-1
        run: |
          aws s3 cp backup.dump.enc s3://my-prod-backup/`date +%F`/backup.dump.enc --sse AES256

ここまで整えば、障害時の復元は「どのスナップショットを、どの環境へ適用するか」の判断に集中できます。RPO15分は、ログの連続適用やストリーミングレプリケーションを組み合わせると現実的になります。復元手順の検証は /episodes/backup-strategy-rpo-rto で詳述しています。

DNSフェイルオーバーで外形SLOを守る

アプリの可用性を外形的(ユーザー視点)に保つ最短経路は、ヘルスチェックを伴うDNSフェイルオーバーです。Route 53のフェイルオーバーは、ヘルスチェックで正常と判定されたリソースにのみトラフィックを流すよう構成できます⁵。CDNと組み合わせれば、オリジンの可用性が部分的に落ちてもキャッシュで凌げる場面が増えます。同日BCPでは、プライマリとスタンバイの2系統を用意し、ヘルスチェック連続失敗で自動切替、復旧時は手動復帰の構えにします。自動復帰のループで不安定化する事例を避けるため、まずは人間が戻す運用が安全です。

Terraformを使うと、Route 53のヘルスチェックとフェイルオーバーレコードを一括で適用できます。既存ゾーンを流用し、別々のターゲットにプライマリとセカンダリを割り当てます。TTL(キャッシュ有効期間)は短めに設定します。

terraform {
  required_providers { aws = { source = "hashicorp/aws", version = "~> 5.0" } }
}
provider "aws" { region = "ap-northeast-1" }

resource "aws_route53_health_check" "primary" {
  fqdn              = "api.example.com"
  type              = "HTTPS"
  resource_path     = "/healthz"
  failure_threshold = 3
  request_interval  = 30
}

resource "aws_route53_record" "api_failover" {
  zone_id = var.zone_id
  name    = "api.example.com"
  type    = "A"
  set_identifier = "primary"
  ttl     = 30
  records = [var.primary_ip]
  failover_routing_policy { type = "PRIMARY" }
  health_check_id = aws_route53_health_check.primary.id
}

resource "aws_route53_record" "api_failover_secondary" {
  zone_id = var.zone_id
  name    = "api.example.com"
  type    = "A"
  set_identifier = "secondary"
  ttl     = 30
  records = [var.secondary_ip]
  failover_routing_policy { type = "SECONDARY" }
}

適用後は意図した応答になるかを外形監視で検証します。切替の体感は数十秒から一分程度です。キャッシュの都合で長引く場合、TTLを30秒以下に調整し、CDNのオリジンフェイルオーバー機能を併用すると整います。

Kubernetes/VMの“止められない”を形にする

コンテナ基盤なら、PodDisruptionBudget(PDB: 計画停止時の最小稼働数)と水平オートスケール(HPA: 負荷に応じたレプリカ自動調整)、そしてノード障害時の自己回復を同日に入れておくと、単一ノード障害に強くなります。既存クラスターに投入するだけの最小構成を示します。

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-pdb
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: api
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 2
  maxReplicas: 6
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70

これだけでも、メンテナンスやノード更新のたびに全落ちする事故は実質的に防げます。VM環境では、同等の考え方として2台構成とヘルスチェック付きロードバランサを当日に用意します。アプリ側にヘルスチェックエンドポイントがない場合は、静的ファイルの配信やDB接続の簡易検査だけでも差が出ます。

運用体制を“今夜”動かす。演習と可視化

技術的コントロールが動き出したら、運用を同じ速度で追いつかせます。決裁権限は当直リーダーに委譲し、Sev-1の宣言は1名の判断で可能にします。コミュニケーションはSlackのインシデント専用チャンネルを用意し、開始時刻、指揮者、対策、次のチェックポイントを一行ずつ残します。重要なのは、人間が動くとメトリクスが残る仕組みです。Issueテンプレートやステータスページに集約すると、翌日の振り返りが定量化され、MTTA(検知から対応開始までの時間)やMTTR(復旧完了までの時間)の改善が現実の数字で見えてきます。

擬似障害演習は30分で十分です。ヘルスチェックを落としてDNSが切り替わるか、バックアップから検証用環境に復元できるか、通知が届かなかった人がいないか、の三点だけを確かめます。初回の演習では、DNSが戻らない、暗号鍵が見つからない、ログイン権が不足、といった基本の躓きが可視化されることが多い。そこで平時からの「ブレークグラス」(非常時のみ使う緊急権限)アカウントを用意し、多要素認証のバックアップコードを金庫とパスワードマネージャの双方に保管する運用に改めます。アクセスの最小権限は守りつつ、非常時にだけ使える権限経路を明確にするのが現実解です。

費用対効果も同日の打ち手で概算できます。S3のバージョニングとオブジェクトロックは保管コストの増分こそありますが、クラウド事業者の標準機能で完結します⁴。DNSフェイルオーバーは月数千円台、通知の多重化に至っては無料枠でも開始可能です。これらのBCP対策により、一般にMTTRの短縮や停止損失の低減が期待できます。意思決定層との合意形成は、RTO/RPO、MTTR、稼働率と損失の換算表を一枚で提示すると進みが早くなります。

まとめ:今日の2時間で、明日の損失を減らす

BCP(事業綶続計画)は巨大な計画から始める必要はありません。今日の2時間で、通知の到達保証、破壊に強いバックアップ、切替可能なDNSを用意し、夜の30分で動作検証を済ませる。これだけで、最初のハードルは越えられます。明日はメトリクスの可視化と演習の頻度を少しずつ高め、翌週に権限設計と監査証跡を整える。その反復で、紙の計画はいつの間にか「動く仕組み」(BCP/DRの実装)に置き換わります。

あなたのシステムで最初に小さく始められるのはどこでしょうか。通知か、バックアップか、DNSか。思い当たる箇所から着手し、同日中に検証まで辿り着くことを目標にしてみてください。

参考文献

  1. cafe-dc.com. Uptime Institute Outages in 2024: Less Frequent and Severe, but More Expensive. https://cafe-dc.com/research/uptime-institute-outages-in-2024-less-frequent-and-severe-but-more-expensive/ (アクセス日: 2025-08-30)
  2. 内閣府 防災情報. 企業の事業継続(BCP)に関する取組状況(令和6年). https://www.bousai.go.jp/kohou/kouhoubousai/r06/110/news_04.html (アクセス日: 2025-08-30)
  3. OPTAGE Bizクロ. 日本のクラウドサービス利用動向. https://optage.co.jp/business/contents/article/cloud-market-share-japan.html (アクセス日: 2025-08-30)
  4. AWS 公式ブログ. Amazon S3 Object Lock でデータを保護する. https://aws.amazon.com/jp/blogs/news/protecting-data-with-amazon-s3-object-lock/ (アクセス日: 2025-08-30)
  5. AWS ドキュメント. Configuring DNS Failover in Amazon Route 53. https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/dns-failover-configuring.html (アクセス日: 2025-08-30)