Article

失敗しない!段階的なクラウド移行プラン

高田晃太郎
失敗しない!段階的なクラウド移行プラン

Flexeraの2024年調査では、企業の約87%がマルチクラウド戦略を掲げ、クラウドコストの無駄は平均28%と自己申告されています。[1][2] 一方で移行の初期段階で可観測性(メトリクス・ログ・トレースの統合可視化)やガバナンス(権限や監査の方針)を後回しにすると、運用負債が雪だるま式に増え、コスト・信頼性・速度のいずれも損なわれます。DORAの研究が示すように、変更のバッチサイズを小さく継続的にデプロイするチームほど、変更失敗率が低く復旧も速いという示唆は、クラウド移行にもそのまま当てはまります。[3] 移行を一度きりの大規模イベントではなく、明確な出口条件を持つフェーズの連続として設計することが、実務では事故率を抑える現実的なアプローチです。

移行はイベントではなくプロセス:現実的な3フェーズ戦略

段階的なクラウド移行は、採用を技術選定の一発勝負から、反復可能な運用に軸足を移します。まず経営・開発・運用の意図合わせを行う評価フェーズから始め、続いて可用性を落とさずに小さく価値を出すパイロット移行を重ね、最後に最適化と近代化で継続的な改善に入る構図が筋のよい流れです。各フェーズには完了基準(出口条件)を置き、曖昧な成功定義を排します。[5]

評価と整合の段階では、サービスを業務クリティカル度と依存関係の観点で棚卸しし、Tier分けを行います。例えば顧客向け決済はRTO(復旧時間目標)を15分、内部分析基盤は数時間というように復旧目標を具体的に置きます。合わせて、p95レイテンシ(95%のリクエストが下回る応答時間)、変更失敗率、月次インフラコスト、1リクエスト当たりコストのようなKPIを初日から可視化します。この段階でランディングゾーン(アカウント構造と共通ガードレールの基盤)、ID連携、ネットワーク、監査ログ、タグ基準のガードレールを最低限用意しておくと、以降の移行波に統一感が生まれます。[4]

パイロットとウェーブ移行の段階では、顧客影響の小さいサービスからストラングラー・パターン(既存システムの外縁機能から段階的に置き換える手法)で切り出します。DNSやIngressでの重み付け、ヘッダベースのルーティング、または地域別の段階展開を活用し、トラフィックを少しずつ新基盤に流します。各ウェーブごとに、SLO(サービスの目標値)を満たすこと、ロールバック時間が10分以内であること、ダッシュボードとアラートが実装されていることを出口条件にします。障害をゼロにするのではなく、障害を短時間で検出・緩和・復旧できる体制を鍛えるのが目的です。[5]

最適化と近代化の段階では、単にlift & shift(仮想マシンの持ち上げ移行)で止めず、コンテナ化、サーバレス化、マネージドDBへの移行など、運用コストと変更容易性のバランスが取れるポイントに移し替えます。同時に、コスト最適化は購買の一回勝負ではなく継続運用の一部だと捉え、タグやコスト配賦、予約・貯蓄プランのガードレールをポリシーとして自動適用するよう仕組み化します。

準備と土台づくり:ランディングゾーン、ID、ネットワーク、監視

移行の失敗要因の多くは、アプリではなく土台にあります。アカウント/サブスクリプションの構造、IDフェデレーション、VPC/ネットワーク設計、監査・構成管理、可観測性のベースラインを先に固めると、以降の判断がすべて速くなります。必要性の高い機能から薄く広く用意し、後から深くしていくのが現実的です。[4]

ランディングゾーンは、組織単位のガードレールをコードで表現し、アカウント追加や共通ログ取得を自動化します。SaaSのIdP(アイデンティティプロバイダ)とクラウドのロールをSAML/OIDCで結び、権限は最小権限+期限付き昇格に統一します。ネットワークはハブアンドスポークで開始し、共有サービスVPCにNAT/プロキシ/ログ集約を集めると運用が見通しやすくなります。監査はCloudTrailやActivity Logの集中集約と改ざん耐性をまず確保し、構成ドリフトはConfig(構成監視)やポリシーで検出します。[4]

可観測性は、アプリ側にOpenTelemetry SDKを先に仕込み、トレースIDをログ・メトリクスと連動させるのが要点です。SLOはユーザー中心に定義し、ダッシュボードとアラートをInfrastructure as Codeと同じリポジトリで管理します。エラー予算(SLOから許容される障害時間を算出)は移行中こそ価値が高く、SLOを満たさない期間はリリースを絞る、満たした期間は技術負債返済に時間を配分するなどのルールで意思決定の軸を固定すると、混乱が減ります。

以下は、最小構成のVPCをTerraformで定義する例です。Provider定義、タグ戦略、AZ分散、出力まで一貫してコード化します。

terraform {
  required_version = ">= 1.6.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.region
}

variable "region" { type = string }
variable "name"   { type = string }

locals {
  tags = {
    System     = local.name
    Owner      = "platform"
    CostCenter = "ENG-OPS"
    Environment= "staging"
  }
  name = var.name
}

resource "aws_vpc" "this" {
  cidr_block           = "10.20.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags = merge(local.tags, { Name = "${local.name}-vpc" })
}

resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.this.id
  tags   = local.tags
}

resource "aws_subnet" "public_a" {
  vpc_id                  = aws_vpc.this.id
  cidr_block              = "10.20.0.0/24"
  availability_zone       = "${var.region}a"
  map_public_ip_on_launch = true
  tags                    = merge(local.tags, { Tier = "public" })
}

output "vpc_id" {
  value = aws_vpc.this.id
}

実行の中核:ゼロダウンタイムを支えるアーキテクチャと手順

ゼロダウンタイム移行の鍵は、トラフィックを制御する面とデータを同期する面の二つを分けて設計することです。前者はIngressやAPIゲートウェイで重みを操作し、後者はスナップショット+増分レプリケーションや二重書き込みでラグを吸収します。すべてを一気に切り替えないことで、変更を検証可能な大きさに分割できます。

Kubernetesを使う場合、NGINX Ingress Controllerのカナリア機能は小さく安全にトラフィックを新クラスタへ流すのに有効です。以下はヘッダベースで一部トラフィックを新バージョンに誘導するIngressの例です。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web-canary
  annotations:
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-by-header: "X-Canary"
    nginx.ingress.kubernetes.io/canary-by-header-value: "1"
spec:
  rules:
  - host: example.internal
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-stable
            port:
              number: 80

同じ思想をレイヤ7プロキシで実施する例を示します。ヘッダで安全に分岐させ、障害時は即座に安定系にフォールバックします。

worker_processes auto;
error_log /var/log/nginx/error.log warn;

http {
  upstream stable {
    server 10.0.1.10:8080;
  }
  upstream canary {
    server 10.0.2.20:8080;
  }
  server {
    listen 80;
    location / {
      if ($http_x_canary = 1) {
        proxy_pass http://canary;
      }
      proxy_next_upstream error timeout http_502 http_503;
      proxy_pass http://stable;
    }
  }
}

アプリのリリースはCI/CDで一貫させます。GitHub ActionsでKubernetesへ適用し、ロールアウト完了と健全性を待つ例です。失敗時は自動で直前の安定マニフェストに戻します。

name: deploy-canary
on:
  push:
    branches: [ main ]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Set up kubectl
        uses: azure/setup-kubectl@v4
        with:
          version: '1.29.0'
      - name: Kubeconfig
        run: |
          mkdir -p ~/.kube
          echo "$KUBECONFIG_BASE64" | base64 -d > ~/.kube/config
      - name: Apply manifests
        run: |
          kubectl apply -f k8s/
          kubectl rollout status deploy/web --timeout=180s
      - name: Smoke test
        run: |
          set -euo pipefail
          for i in {1..5}; do curl -fsS http://example.internal/health && break || sleep 3; done
      - name: Rollback if failed
        if: failure()
        run: |
          kubectl rollout undo deploy/web

データ側では、スナップショットからの初期取り込みと増分同期の二段構えが安全です。以下はRDSスナップショットをクロスアカウント/リージョンにコピーして完了を待つPythonの例です。明示的な例外処理を入れることで、失敗時の動作が読みやすくなります。

import os
import sys
import time
import boto3
from botocore.exceptions import ClientError, WaiterError

SRC_REGION = os.getenv("SRC_REGION", "ap-northeast-1")
DST_REGION = os.getenv("DST_REGION", "ap-northeast-2")
SNAPSHOT_ID = os.getenv("SNAPSHOT_ID")
TARGET_ID = f"copy-{SNAPSHOT_ID}"

if not SNAPSHOT_ID:
    print("SNAPSHOT_ID is required", file=sys.stderr)
    sys.exit(2)

src = boto3.client("rds", region_name=SRC_REGION)
dst = boto3.client("rds", region_name=DST_REGION)

try:
    # 共有ポリシー設定などが必要な場合は事前に実施
    dst.copy_db_snapshot(
        SourceDBSnapshotIdentifier=f"arn:aws:rds:{SRC_REGION}:{os.getenv('SRC_ACCOUNT')}:snapshot:{SNAPSHOT_ID}",
        TargetDBSnapshotIdentifier=TARGET_ID,
        CopyTags=True,
        KmsKeyId=os.getenv("KMS_KEY_ID")
    )
    waiter = dst.get_waiter('db_snapshot_available')
    waiter.wait(DBSnapshotIdentifier=TARGET_ID, WaiterConfig={"Delay": 30, "MaxAttempts": 240})
    print("Snapshot copy completed")
except (ClientError, WaiterError) as e:
    print(f"Copy failed: {e}", file=sys.stderr)
    sys.exit(1)

性能のリスクは小さなトラフィックで早めに発見します。k6でp95レイテンシとエラーレートを閾値化しておくと、移行の度に手動で悩む必要がなくなります。[7]

import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
  vus: 50,
  duration: '3m',
  thresholds: {
    http_req_duration: ['p(95)<200'],
    http_req_failed: ['rate<0.01']
  }
};

export default function () {
  const res = http.get('https://example.internal/api');
  if (res.status !== 200) {
    throw new Error(`Unexpected status: ${res.status}`);
  }
  sleep(0.1);
}

最後に、データ移行やスキーマ変更の失敗を全体障害にしないために、スクリプト自体を堅牢にします。Bashはset -euo pipefailとtrapを入れるだけで事故率が下がります。ログには相関IDを含め、再実行に耐える設計にしておきます。

#!/usr/bin/env bash
set -euo pipefail
trap 'echo "[ERROR] line $LINENO exit $?: $(date -Is)" >&2' ERR

DB_URL="${DB_URL:-postgres://user:pass@host:5432/app}"
CHANGE_ID="${CHANGE_ID:-$(date +%s)}"

echo "[INFO] change=${CHANGE_ID} start=$(date -Is)"
psql "$DB_URL" -v ON_ERROR_STOP=1 <<'SQL'
BEGIN;
ALTER TABLE orders ADD COLUMN IF NOT EXISTS note TEXT;
COMMIT;
SQL

echo "[INFO] change=${CHANGE_ID} done=$(date -Is)"

ビジネス効果の最大化:コスト、SLO、組織運用の変革

技術的な正しさだけでなく、ビジネス価値が移行の継続性を支えます。少数のパイロットがSLOとコスト目標を満たせば、以降の投資承認は進みやすくなります。一般に、段階的な移行とゼロダウンタイム展開の設計により、レイテンシの改善、リリース所要時間の短縮、月次インフラコストの削減といった効果が観測されることが多く、重要なのは測定方法を固定して比較可能にすることです。[3][6]

ROIはリソースコストの削減だけでなく、開発者のフロー効率と機会損失の低減を含めて見ます。例えば、月間デプロイ頻度が増えるとA/Bテストの回転率が上がり、学習のループが短くなります。エスカレーションの件数が減ると、夜間対応の人件費や離職リスクも間接的に下がります。これらはSREのSLO/エラー予算と整合させると、経営会議でも説明しやすくなります。

ガバナンスは「止める仕組み」ではなく「速く安全に進める仕組み」にします。タグの付与、脆弱性スキャン、アカウント間の通信制御、PIIの保護、コスト上限のガードレールは自動で適用し、逸脱時はリリースパイプラインが赤くなることでチームが自律的に修正できる状態にします。レビュー会議で後追い審査をするのではなく、パイプラインの成功が暗黙の承認である文化に寄せると、速度と安全性の両立が現実的になります。

移行期間の見立ては規模や前提で変わりますが、10〜30サービス規模なら、土台整備に6〜8週間、パイロット数件に6〜8週間、残りのウェーブに12〜24週間程度を初期計画の仮説とし、進捗に応じて見直すのが現実的です。全社一括の大移行を掲げるより、四半期ごとにKPI付きのロードマップを公開し、達成と学びを蓄積していく方がステークホルダーの支持を得やすく、リスクも低く抑えられます。

まとめ:小さく始め、大きく育てる設計こそ最速の近道

クラウド移行の本質は、一発勝負の成功ではなく、学習速度と復元力を高める仕組みづくりにあります。フェーズごとの出口条件を明確にし、ゼロダウンタイムのためのトラフィック制御とデータ同期を分けて設計し、SLOとコストの実測で意思決定する。この三点がそろえば、障害は致命傷にならず、移行は着実に前へ進みます。

次に何から始めるべきかと問われたら、監査ログと可観測性を今日から有効化し、最小のパイロットを一件選ぶことだと答えます。あなたの組織にとっての最初の一歩はどれでしょうか。基盤づくり、パイロット選定、SLO設計のいずれからでも構いません。小さく始めて測り、学び、そして次へ進みましょう。

参考文献

  1. Flexera. Flexera 2024 State of the Cloud: Managing Spending Top Challenge. Press Center, 2024. https://www.flexera.com/about-us/press-center/flexera-2024-state-of-the-cloud-87
  2. Flexera. Cloud computing trends: Flexera 2024 State of the Cloud Report. Blog, 2024. https://www.flexera.com/blog/cloud/cloud-computing-trends-flexera-2024-state-of-the-cloud-report
  3. CloudBees. 2018 Accelerate: State of DevOps Report Identifies Elite Performers. Blog, 2018. https://www.cloudbees.com/blog/2018-accelerate-state-devops-report-identifies-elite-performers
  4. AWS Prescriptive Guidance. Landing Zone considerations for a large migration. https://docs.aws.amazon.com/prescriptive-guidance/latest/large-migration-foundation-playbook/landing-zone.html
  5. AWS Architecture Blog. Designing a Successful Pilot Phase for Your Cloud Migration. 2019. https://aws.amazon.com/blogs/architecture/designing-a-successful-pilot-phase-for-your-cloud-migration/
  6. AWS Solutions. Guidance for Cloud Financial Management on AWS. https://aws.amazon.com/solutions/guidance/cloud-financial-management-on-aws/
  7. Datadog. Best practices for monitoring a cloud migration. https://www.datadoghq.com/blog/cloud-migration-monitoring/