ゼロダウンタイム移行計画
サービスを停止せずにインフラを移行するための計画と手順を説明します。
概要
Bazbiiシステムでは、サービスを停止することなく、段階的な移行を実現します。主な移行シナリオは以下の通りです:
- MVPから本番環境への移行: 2サーバー構成から3サーバー構成への移行
- スケールアップ移行: リソース拡張時の移行
- データベースマイグレーション: スキーマ変更を伴う移行
- アプリケーションデプロイ: 新バージョンへの更新
移行の基本原則
1. 後方互換性の維持
- 新しいバージョンは古いバージョンと互換性を保つ
- APIの破壊的変更は段階的に実施
- データベーススキーマは段階的に変更(Expand-Contractパターン)
2. 段階的移行
- 一度にすべてを移行せず、段階的に実施
- 各段階で検証を行い、問題があればロールバック
- 新旧システムを並行運用可能な期間を設ける
3. 検証とモニタリング
- 各段階でヘルスチェックを実施
- メトリクス・ログ・トレースを継続的に監視
- エラー率やレイテンシの変化を検知
4. ロールバック計画
- 各段階でロールバック手順を事前に準備
- ロールバックに要する時間を把握
- ロールバック時の影響範囲を明確化
移行シナリオ
シナリオ1: MVPから本番環境への移行
目標: 2サーバー構成(MVP)から3サーバー構成(本番)への移行
現在の構成(MVP)
Server 1: API + Gateway (2vCPU/4GB/100GB)
Server 2: PostgreSQL (2vCPU/4GB/100GB)
移行後の構成(本番)
Server 1: API (4vCPU/8GB/200GB)
Server 2: Gateway (2vCPU/4GB/100GB)
Server 3: PostgreSQL + レプリカ (4vCPU/8GB/500GB)
移行手順
Phase 1: Server 3の準備
-
Server 3を新規作成
- KonohaVPSでServer 3を作成(4vCPU/8GB/500GB)
- OS初期設定(Ubuntu 22.04 LTS)
- ネットワーク設定(Cloudflare Tunnel設定)
-
PostgreSQLレプリカの構築
- Server 2(既存DB)からServer 3へレプリカを構築
- WAL(Write-Ahead Log)を使用したストリーミングレプリケーション
- レプリケーションラグを監視
-
検証
- レプリカの整合性確認
- レプリケーションラグが許容範囲内であることを確認
Phase 2: API Serverの分離
-
Server 1の現在の状態を確認
- API + Gatewayが動作していることを確認
- トラフィックを記録(ベースライン)
-
GatewayをServer 2へ移動
- Server 2にGatewayコンテナを起動
- Cloudflare Tunnelの設定を更新(Gatewayのエンドポイントを変更)
- ヘルスチェックで動作確認
-
トラフィック切り替え
- Cloudflare Load BalancerまたはDNS設定でトラフィックを段階的に切り替え
- 10% → 50% → 100%の順でトラフィックを移行
- 各段階でエラー率・レイテンシを監視
-
Server 1からGatewayを削除
- Gatewayコンテナを停止
- Server 1をAPI専用サーバーとして運用
Phase 3: サーバーリソースの拡張
-
Server 1のリソース拡張
- API Serverのみが動作していることを確認
- KonohaVPSでServer 1のリソースを拡張(2vCPU/4GB → 4vCPU/8GB、100GB → 200GB)
- ダウンタイムなしでリソース拡張(KonohaVPSの機能による)
-
Server 2の用途変更
- Gateway専用として運用継続(変更なし)
Phase 4: PostgreSQLの移行
-
Server 3へのマイグレーション
- Server 3のPostgreSQLをマスターとして設定
- Server 2のPostgreSQLをレプリカに変更
- アプリケーションの接続先をServer 3に変更
-
トラフィック切り替え
- API Serverのデータベース接続設定を更新
- 段階的に接続を移行(接続プールの一部から開始)
- エラー率・レイテンシを監視
-
Server 2のPostgreSQLを停止
- レプリカとして動作していたPostgreSQLを停止
- Server 2はGateway専用として運用継続
移行中のトラフィック制御
- Cloudflare Load Balancer: 複数のエンドポイント間でトラフィックを分散
- DNS切り替え: TTLを短く設定し、段階的にDNSレコードを更新
- ヘルスチェック: 各サーバーのヘルスチェックを実施し、異常時は自動的に切り離し
シナリオ2: データベースマイグレーション(スキーマ変更)
目標: 後方互換性を保ちながら、データベーススキーマを変更する
Expand-Contractパターン
データベースマイグレーションは、Expand-Contractパターンを使用してゼロダウンタイムを実現します。
手順:
- Expand(拡張): 新しいカラムやテーブルを追加(既存コードに影響なし)
- Migrate(データ移行): 既存データを新しいスキーマに移行
- Contract(縮小): 古いカラムやテーブルを削除(新しいコードのみが動作)
具体例: カラム追加
変更内容: postsテーブルにcategoryカラムを追加
Phase 1: Expand(拡張)
-- マイグレーション: 0002_add_category_column.up.sql
ALTER TABLE posts
ADD COLUMN category VARCHAR(50) NULL;
-- 既存コードは動作し続ける(NULL許可のため)
Phase 2: アプリケーション更新(段階的)
-
新バージョンのデプロイ
categoryカラムを使用する新しいコードをデプロイ- 古いコードと新コードが並行して動作(後方互換)
-
データ移行
- 既存の
postsレコードにcategoryのデフォルト値を設定 - バックグラウンドジョブで段階的に更新
- 既存の
Phase 3: Contract(縮小)(将来)
-- マイグレーション: 0003_make_category_required.up.sql
-- 注意: この時点で、すべてのアプリケーションが新バージョンに更新済みである必要がある
ALTER TABLE posts
ALTER COLUMN category SET NOT NULL,
ALTER COLUMN category SET DEFAULT 'default';
具体例: テーブル分割
変更内容: postsテーブルからpost_metadataテーブルへ分割
Phase 1: Expand(拡張)
-- 新しいテーブルを作成
CREATE TABLE post_metadata (
post_id uuid PRIMARY KEY REFERENCES posts(id),
metadata jsonb NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 既存コードは動作し続ける
Phase 2: データ移行とアプリケーション更新
-
データ移行
- 既存の
posts.contentからpost_metadataへデータを移行 - バックグラウンドジョブで段階的に移行
- 既存の
-
アプリケーション更新
- 新バージョンで
post_metadataテーブルを使用 - 読み取りは
post_metadataを優先、フォールバックはposts.content - 書き込みは両方に書き込む(デュアルライト)
- 新バージョンで
Phase 3: Contract(縮小)
-- posts.contentカラムを削除(すべてのコードがpost_metadataを使用していることを確認後)
ALTER TABLE posts DROP COLUMN content;
シナリオ3: アプリケーションの新バージョンデプロイ
目標: 新しいバージョンのアプリケーションをダウンタイムなくデプロイする
ブルー・グリーンデプロイメント
KubernetesのRolling Updateを使用して、段階的に新バージョンをデプロイします。
手順:
-
新バージョンのイメージをビルド
docker build -t bazbii/api:v2.0.0 .
docker push registry.example.com/bazbii/api:v2.0.0 -
段階的なデプロイ
# KubernetesのRolling Update
kubectl set image deployment/api api=registry.example.com/bazbii/api:v2.0.0 -
Kubernetesの動作
- 新しいPodを1つずつ起動
- ヘルスチェックで正常性を確認
- 正常なPodが起動したら、古いPodを1つずつ停止
- このプロセスを繰り返し、すべてのPodを更新
-
監視と検証
- 各Podの更新時にエラー率・レイテンシを監視
- 異常を検知した場合は自動ロールバック
カナリアリリース
新バージョンを一部のトラフィックで検証してから、全体に展開します。
手順:
-
カナリア環境のデプロイ
- 新バージョンのPodを1つ起動
- Cloudflare Load Balancerで10%のトラフィックを新バージョンにルーティング
-
検証期間
- 24時間〜48時間、カナリア環境で動作を監視
- エラー率、レイテンシ、ビジネス指標を確認
-
段階的展開
- 問題がなければ、トラフィックを50%に増加
- さらに検証後、100%に展開
-
ロールバック
- 問題が検出された場合、トラフィックを0%に戻す
- 古いバージョンに完全に切り戻す
シナリオ4: DNS/CDNの切り替え
目標: DNSレコードやCDN設定を変更する際のダウンタイム回避
DNS切り替え
手順:
-
TTLを短く設定
# CloudflareでTTLを5分に設定(デフォルトより短く)
# これにより、DNS変更の反映が速くなる -
新しいDNSレコードの追加
- 新しいエンドポイントのAレコードを追加
- 既存のレコードは維持
-
段階的な切り替え
- Cloudflare Load Balancerを使用して複数のエンドポイントを設定
- 重み付けで段階的にトラフィックを移行
-
検証
- 新エンドポイントの動作確認
- トラフィックの正常なルーティングを確認
-
古いDNSレコードの削除
- すべてのトラフィックが新エンドポイントに移行したことを確認
- 古いDNSレコードを削除
Cloudflare設定の切り替え
手順:
-
新設定の準備
- Cloudflareダッシュボードで新設定を準備
- テスト環境で動作確認
-
段階的な適用
- 新しいルーティングルールを追加(既存ルールは維持)
- テストトラフィックで検証
-
設定の切り替え
- トラフィックを段階的に新設定に移行
- 監視しながら切り替え
-
古い設定の削除
- すべてのトラフィックが新設定で動作していることを確認
- 古い設定を削除
ロールバック計画
ロールバックのタイミング
以下の場合にロールバックを実行します:
- エラー率の急増: エラー率が1%を超える
- レイテンシの悪化: p95レイテンシが目標値の2倍を超える
- データ整合性の問題: データの不整合が検出される
- 機能の不具合: 重要な機能が動作しない
ロールバック手順
アプリケーションのロールバック
# Kubernetesの場合
kubectl rollout undo deployment/api
kubectl rollout undo deployment/gateway
# ロールバック状況の確認
kubectl rollout status deployment/api
データベースのロールバック
# マイグレーションのロールバック(1段階戻す)
make db/mig-down
# 特定バージョンまでロールバック
make db/mig-down.to version=<target_version>
注意: データベースのロールバックは慎重に実施。データ損失のリスクがある場合は、バックアップからの復元を検討。
DNSのロールバック
- CloudflareダッシュボードでDNSレコードを元に戻す
- TTLが短い場合、数分で反映される
- 必要に応じて、CloudflareのLoad Balancer設定を変更
移行スケジュール例
MVPから本番環境への移行(想定: 4週間)
Week 1: 準備
- 移行計画の作成
- Server 3の準備
- テスト環境での移行リハーサル
Week 2: Server 3とPostgreSQLレプリカ
- Server 3の構築
- PostgreSQLレプリカの構築と検証
- レプリケーションの安定性確認
Week 3: API Serverの分離
- GatewayをServer 2へ移動
- トラフィックの段階的切り替え
- Server 1をAPI専用に変更
Week 4: リソース拡張とPostgreSQL移行
- Server 1のリソース拡張
- PostgreSQLの移行
- 最終検証とドキュメント更新