第12章: リリース

変更されないソフトウェアは、改善されないソフトウェアです。リリースサービスは、フリート全体のローリングデプロイメントを管理し、スケジューラと連携してダウンタイムなしでインスタンスを1バッチずつ置き換えます。

インターフェース

release/src/lib.rs リリースサービスは5つのプロシージャを公開します。CREATE_RELEASEは新しいデプロイメントを開始します。GET_RELEASELIST_RELEASESは状態を検査します。ADVANCE_RELEASEはデプロイメントを1バッチ前進させます。ROLLBACKは進行中のデプロイメントを元に戻します。

pub const CREATE_RELEASE_PROCEDURE: ProcedureId = 501;
pub const GET_RELEASE_PROCEDURE: ProcedureId = 502;
pub const LIST_RELEASES_PROCEDURE: ProcedureId = 503;
pub const ADVANCE_RELEASE_PROCEDURE: ProcedureId = 504;
pub const ROLLBACK_PROCEDURE: ProcedureId = 505;

#[derive(Debug, Serializable, Deserializable)]
pub struct CreateReleaseArgs {
    pub service: String,
    pub version: String,
    pub description: String,
}

リリースライフサイクル

release/src/main.rs リリースはシンプルな状態マシンを通じて進行します:createddeployingdeployed(またはrolled_back)。各状態遷移は明示的です——運用者がリリースダッシュボードを通じてリリースを前進させることで、ロールアウトのペースを制御できます。

struct Release {
    id: String,
    service: String,
    version: String,
    description: String,
    status: String,         // "created", "deploying", "deployed", "rolled_back"
    old_instances: Vec<String>,
    new_instances: Vec<String>,
    batch_progress: i32,
}

ローリングアップデート

release/src/main.rscreate_release リリースが作成されると、サービスはスケジューラにターゲットサービスの現在のインスタンスを問い合わせ、それらを「旧」インスタンスとしてスナップショットします。バッチサイズはmax(1, total / 10)として計算されます——バッチあたりフリートの約10%、最低1インスタンスです。

let svc_result = scheduling::get_service(
    SCHEDULER_ADDR, args.service.clone()
).await;
let old_instances: Vec<String> = svc_result.instances
    .split(';').map(|s| s.to_string()).collect();
let total = old_instances.len() as i32;
let batch_size = std::cmp::max(1, total / 10);

release/src/main.rsadvance_release ADVANCE_RELEASEへの各呼び出しは1バッチを置き換えます。ハンドラはスケジューラにスケールアップを指示し、ヘルスを待ち、次に1つの旧インスタンスを停止します:

  1. スケジューラにバッチ用の新しいインスタンスをスポーンさせる
  2. 新しいインスタンスがヘルスチェックに合格するまで待つ
  3. スケジューラに対応する旧インスタンスを停止させる
  4. 旧インスタンスは古さベースのクリーンアップによりディスカバリから登録解除される
// Scale up: add one replica
scheduling::scale_service(
    SCHEDULER_ADDR, release.service.clone(), current_total + 1,
).await;

// Wait for new instance to become healthy
sleep(Duration::from_secs(2)).await;

// Scale down: stop one old instance
let old_id = release.old_instances.remove(0);
scheduling::stop_instance(SCHEDULER_ADDR, old_id).await;

release.batch_progress += 1;
if release.old_instances.is_empty() {
    release.status = "deployed".to_string();
}

このプロセスにより、ロールアウト中のどの時点でも、サービスにはトラフィックを処理するのに十分な健全なインスタンスがあることが保証されます。ルーティング層とディスカバリサービスは、停止されたインスタンスからのトラフィックを自動的に新しいインスタンスに振り向けます。

ロールバック

release/src/main.rsrollback デプロイメントに問題が発生した場合、ROLLBACKプロシージャはアクティブなリリースをrolled_backとしてマークします。旧インスタンスは新しいインスタンスの健全性が確認された後にのみ停止されるため、デプロイメント中のロールバックは単にプロセスを停止するだけです——残りの旧インスタンスはトラフィックの提供を続けます。高速ロールバックの鍵は、新しいものが証明される前に古いものを決して削除しないことです。

スケジューラとの統合

release/src/lib.rs リリースサービスはプロセスを直接スポーンしません。代わりに、すべてのプロセス管理をRPC呼び出しを通じてスケジューラに委譲します:レプリカの追加にscheduling::scale_service()、削除にscheduling::stop_instance()を使用します。この関心の分離により、スケジューラは何が実行されているかの唯一の真実の源として維持され、リリースサービスは変更の順序ペースを管理します。リリースダッシュボードでリリースを作成し、ローリングデプロイメントを段階的に進めることができます。