Durable Functions のためのゼロダウンタイムのデプロイ

Durable Functions の信頼性の高い実行モデルには、オーケストレーションが決定論的であることが必要です。これにより、更新プログラムをデプロイするときに考慮する必要がある追加の課題が発生します。 アクティビティ関数のシグネチャまたはオーケストレーター ロジックに対する変更がデプロイに含まれている場合、実行中のオーケストレーション インスタンスが失敗します。 この状況は、数時間または数日間にわたる作業を表す可能性がある、長時間実行されているオーケストレーションのインスタンスの場合に特に問題があります。

このような障害は 2 とおりの方法で防止できます。

  • 実行中のオーケストレーション インスタンスがすべて完了するまでデプロイを遅らせます。
  • 実行中のオーケストレーション インスタンスで既存バージョンの関数が使用されていることを確認します。

次の表では、Durable Functions のゼロダウンタイム デプロイを実現するための 3 つの主な戦略を比較します。

戦略 使用する場合 長所 短所
バージョン管理 破壊的変更が頻繁に発生しないアプリケーション。 実装が簡単。 メモリ内の関数アプリのサイズと関数の数が増える。
コードの重複。
スロットでの状態チェック 24 時間以上続く長時間のオーケストレーションまたは頻繁に重複するオーケストレーションが存在しないシステム。 シンプルなコード ベース。
追加の関数アプリ管理が不要。
追加のストレージ アカウントまたはタスク ハブの管理が必要である。
オーケストレーションが実行されていない時間帯が必要である。
アプリケーション ルーティング 24 時間以上続くオーケストレーションや、頻繁に重複するオーケストレーションの期間など、オーケストレーションが実行されていない期間がないシステム。 破壊的変更を伴うオーケストレーションが継続的に実行されている新しいバージョンのシステムを処理する。 インテリジェントなアプリケーション ルーターが必要。
サブスクリプションで許可されている関数アプリの最大数を超える可能性。 既定値は、100 です。

このドキュメントの残りの部分では、これらの戦略について詳しく説明します。

Note

これらのゼロダウンタイム デプロイ戦略の説明では、Durable Functions に既定の Azure Storage プロバイダーを使用していることを前提としています。 既定の Azure Storage プロバイダー以外のストレージ プロバイダーを使用している場合は、ガイダンスが適切でないことがあります。 さまざまなストレージ プロバイダーのオプションとその比較については、Durable Functions ストレージ プロバイダーに関するドキュメントを参照してください。

バージョン管理

関数の新しいバージョンを定義し、関数アプリでは古いバージョンのままにします。 図を見るとわかるように、関数のバージョンが名前の一部になります。 以前のバージョンの関数が保持されるため、実行中のオーケストレーション インスタンスは引き続きそれらを参照できます。 一方で、新しいオーケストレーション インスタンスに対する要求では最新バージョンが呼び出され、オーケストレーション クライアント関数はアプリ設定から参照できます。

Versioning strategy

この戦略では、すべての関数をコピーし、他の関数への参照を更新する必要があります。 スクリプトを記述することで簡単にできます。 次に示すのは、移行スクリプトを使用したサンプル プロジェクトです。

Note

この戦略では、デプロイ スロットを使用して、デプロイ時のダウンタイムが回避されます。 新しいデプロイ スロットを作成して使用する方法の詳細については、「Azure Functions デプロイ スロット」を参照してください。

スロットでの状態チェック

現在のバージョンの関数アプリが運用スロットで実行されている間に、関数アプリの新しいバージョンをステージング スロットにデプロイします。 運用スロットとステージング スロットをスワップする前に、実行中のオーケストレーション インスタンスがあるかどうかを確認します。 すべてのオーケストレーション インスタンスが完了したら、スワップを実行できます。 この戦略は、実行中のオーケストレーション インスタンスがなくなる期間を予測できる場合に有効です。 これは、オーケストレーションが長時間実行されない場合や、オーケストレーションの実行で重複が頻度に発生しない場合に、最適な方法です。

関数アプリの構成

次の手順でこのシナリオを設定します。

  1. ステージングと運用のために、関数アプリにデプロイ スロットを追加します

  2. スロットごとに、共有ストレージ アカウントの接続文字列に AzureWebJobsStorage アプリケーション設定を設定します。 このストレージ アカウント接続文字列は、関数のアクセス キーを安全に格納するために Azure Functions ランタイムによって使用されます。

  3. スロットごとに、新しいアプリ設定を作成します (例: DurableManagementStorage)。 その値を異なるストレージ アカウントの接続文字列に設定します。 これらのストレージ アカウントは、信頼性の高い実行のために Durable Functions 拡張機能によって使用されます。 スロットごとに個別のストレージ アカウントを使用します。 この設定をデプロイ スロットの設定としてマークしないでください。

  4. 関数アプリの host.json ファイルの durableTask セクションで、ステップ 3 で作成したアプリ設定の名前として connectionStringName (Durable 2.x) または azureStorageConnectionStringName (Durable 1.x) を指定します。

次の図では、デプロイ スロットとストレージ アカウントの説明した構成を示します。 このような事前にデプロイされている可能性のあるシナリオでは、バージョン 2 の関数アプリが運用スロットで実行されている間、バージョン 1 はステージング スロットに残っています。

Deployment slots and storage accounts

host.json の例

次の JSON フラグメントは、host.json ファイルでの接続文字列の設定の例です。

Functions 2.0

{
  "version": 2.0,
  "extensions": {
    "durableTask": {
      "hubName": "MyTaskHub",
      "storageProvider": {
        "connectionStringName": "DurableManagementStorage"
      }
    }
  }
}

Functions 1.x

{
  "durableTask": {
    "azureStorageConnectionStringName": "DurableManagementStorage"
  }
}

CI/CD パイプラインの構成

関数アプリに保留中または実行中のオーケストレーション インスタンスがない場合にのみデプロイするように、CI/CD パイプラインを構成します。 Azure Pipelines を使用する場合は、次の例のように、これらの条件がチェックされる関数を作成できます。

[FunctionName("StatusCheck")]
public static async Task<IActionResult> StatusCheck(
    [HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestMessage req,
    [DurableClient] IDurableOrchestrationClient client,
    ILogger log)
{
    var runtimeStatus = new List<OrchestrationRuntimeStatus>();

    runtimeStatus.Add(OrchestrationRuntimeStatus.Pending);
    runtimeStatus.Add(OrchestrationRuntimeStatus.Running);

    var result = await client.ListInstancesAsync(new OrchestrationStatusQueryCondition() { RuntimeStatus = runtimeStatus }, CancellationToken.None);
    return (ActionResult)new OkObjectResult(new { HasRunning = result.DurableOrchestrationState.Any() });
}

次に、オーケストレーションが実行されなくなるまで待機するように、ステージング ゲートを構成します。 詳細については、「ゲートを使用してデプロイの制御をリリースする」を参照してください

Deployment gate

Azure Pipelines では、デプロイ開始前に、関数アプリで実行中のオーケストレーション インスタンスが確認されます。

Deployment gate (running)

ここで、新しいバージョンの関数アプリを、ステージング スロットにデプロイする必要があります。

Staging slot

最後に、スロットをスワップします。

デプロイ スロット設定としてマークされていないアプリケーション設定もスワップされるため、バージョン 2 のアプリではストレージ アカウント A への参照が維持されます。オーケストレーションの状態がストレージ アカウントで追跡されるため、バージョン 2 のアプリで実行中のすべてのオーケストレーションは、中断されることなく、新しいスロットで引き続き実行されます。

Deployment slot

両方のスロットに同じストレージ アカウントを使用するには、タスク ハブの名前を変更します。 この場合は、ユーザーがスロットの状態とアプリの HubName の設定を管理する必要があります。 詳しくは、Durable Functions におけるタスク ハブに関するページをご覧ください。

アプリケーション ルーティング

この戦略は最も複雑です。 しかし、オーケストレーションの実行と実行の間に時間がない関数アプリに使用できます。

この戦略では、Durable Functions の外側にアプリケーション ルーターを作成する必要があります。 このルーターは Durable Functions で実装できます。 ルーターは次の役割を担います。

  • 関数アプリをデプロイします。
  • Durable Functions のバージョンを管理します。
  • オーケストレーション要求を関数アプリにルーティングします。

初めてオーケストレーション要求を受信したとき、ルーターでは次のタスクが実行されます。

  1. Azure で新しい関数アプリを作成します。
  2. 関数アプリのコードを Azure の新しい関数アプリにデプロイします。
  3. オーケストレーション要求を新しいアプリに転送します。

ルーターでは、アプリのコードのどのバージョンが Azure のどの関数アプリにデプロイされているかという状態が管理されます。

Application routing (first time)

ルーターでは、要求と共に送信されたバージョンに基づいて、デプロイとオーケストレーション要求が適切な関数アプリに転送されます。 パッチ バージョンは無視されます。

破壊的変更が含まれないアプリの新しいバージョンをデプロイするときは、パッチ バージョンをインクリメントできます。 ルーターでは、既存の関数アプリに対してデプロイが行われ、コードの古いバージョンと新しいバージョンに対する要求は、同じ関数アプリにルーティングされます。

Application routing (no breaking change)

破壊的変更が含まれるアプリの新しいバージョンをデプロイするときは、メジャー バージョンまたはマイナー バージョンをインクリメントできます。 次に、アプリケーション ルーターによって Azure に新しい関数アプリが作成され、それに対してデプロイが行われて、新しいバージョンのアプリに対する要求はそれにルーティングされます。 次の図では、アプリの 1.0.1 バージョンで実行中のオーケストレーションは実行し続けますが、1.1.0 バージョンに対する要求は新しい関数アプリにルーティングされます。

Application routing (breaking change)

ルーターでは、1.0.1 バージョンでのオーケストレーションの状態が監視され、すべてのオーケストレーションが完了した後でアプリが削除されます。

追跡ストアの設定

各関数アプリでは、個別のスケジュール キュー (可能な場合は異なるストレージ アカウントの) を使用する必要があります。 アプリケーションのすべてのバージョンですべてのオーケストレーション インスタンスのクエリを実行したい場合は、関数アプリ間でインスタンス テーブルと履歴テーブルを共有できます。 host.json settings ファイルで trackingStoreConnectionStringNametrackingStoreNamePrefix の設定を構成し、すべてのテーブルで同じ値が使用されるようにすることで、テーブルを共有できます。

詳細については、「Azure における Durable Functions でのインスタンスの管理」を参照してください。

Tracking store settings

次のステップ