Durable Functionsのダウンタイムなしのデプロイ

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

この記事の戦略では、Durable Functionsの既定のAzure Storage プロバイダーを使用していることを前提としています。 別のストレージ プロバイダーを使用している場合は、ガイダンスが適用されない可能性があります。 オーケストレーションのバージョン管理戦略は例外であり、任意のストレージ バックエンドで動作します。 ストレージ プロバイダーのオプションの詳細については、「Durable Functions ストレージ プロバイダー」を参照してください。

次の表では、ダウンタイムゼロのデプロイを実現するための 4 つの戦略を比較します。 ワークロードに最も適した戦略を選択します。

戦略 いつ使用するか 利点 デメリット
オーケストレーションのバージョン管理 (推奨) 複数のオーケストレーション バージョンを同時に実行する必要がある 破壊的変更 があるアプリケーション。 重大な変更を伴うダウンタイムなしのデプロイを有効にします。
最小限の構成を必要とする組み込み機能。
任意のストレージ バックエンドで動作します。
バージョンの互換性のために、慎重にオーケストレーター コードを変更する必要があります。
名前ベースのバージョン管理 シンプルさが優先される、破壊的 変更 の頻度が低いアプリケーション。 実装が簡単。 メモリ内の関数アプリのサイズと関数の数が増える。
コードの重複。
スロットでの状態チェック 有効期間の短いオーケストレーション (24 時間以内) と実行間の予測可能なギャップを持つシステム。 シンプルなコード ベース。
追加の関数アプリ管理が不要。
追加のストレージ アカウントまたはタスク ハブの管理が必要である。
オーケストレーションが実行されていない時間帯が必要である。
アプリケーション ルーティング 継続して動作するオーケストレーション (24 時間を超える) または頻繁に重複して実行され、アイドル時間のウィンドウがないシステム。 破壊的変更を伴うオーケストレーションが継続的に実行されている新しいバージョンのシステムを処理する。 インテリジェントなアプリケーション ルーターが必要。
サブスクリプションで許可されている関数アプリの数を最大にできます (既定値は 100)。

オーケストレーションのバージョン管理

オーケストレーションのバージョン管理機能は、破壊的変更を伴うダウンタイムのないデプロイに推奨される戦略です。 これにより、異なるバージョンのオーケストレーションを共存させ、競合なしで同時に実行できます。

オーケストレーションのバージョニングでは:

  • 各オーケストレーション インスタンスは、作成時に永続的に関連付けられたバージョンを取得します。
  • 新しいオーケストレーター バージョンを実行しているワーカーは、古いバージョンのインスタンスの実行を続行できます。
  • 以前のバージョンのオーケストレーションを実行しているワーカー は、 新しいバージョンのインスタンスを実行できません。
  • オーケストレーター関数は、それに応じてバージョンとブランチの実行を調べることができます。

この方法により、異なるバージョンのアプリケーションを実行しているワーカーが安全に共存できるローリング アップグレードが容易になります。 この記事の他の方法とは異なり、オーケストレーションのバージョン管理は バックエンドに依存せず 、任意のストレージ プロバイダーと連携します。

バージョン管理の構成、オーケストレーター コードでのバージョン分岐の処理、ローリング アップグレードの管理など、完全な実装手順については、 オーケストレーションのバージョン管理に関するページを参照してください。

残りの戦略は、オーケストレーションのバージョン管理が適さないシナリオの代替手段です。

名前ベースのバージョン管理

この方法では、関数の新しいバージョンを、同じ関数アプリの古いバージョンと共に作成します。 各関数のバージョンは、その名前の一部になります (たとえば、 MyOrchestrator_v1MyOrchestrator_v2)。 以前のバージョンは保持されるため、実行中のオーケストレーション インスタンスは引き続きそれらを参照できます。 新しいオーケストレーション インスタンスに対する要求は、オーケストレーション クライアント関数がアプリ設定から参照できる最新バージョンを呼び出します。 次の図は、この方法を示しています。

名前ベースのバージョン管理戦略図のスクリーンショットで、Durable Functions アプリ内で関数のバージョンがどのように共存するかを示しています。

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

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

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

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

関数アプリの構成

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

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

  2. スロットごとに、共有ストレージ アカウントの接続に対して AzureWebJobsStorage アプリケーション設定を設定します。 このストレージ アカウント接続は、Azure Functions ランタイムによって、functions のアクセス キーを安全に格納するために使用されます。 最高レベルのセキュリティを実現するには、ストレージ アカウントに対してマネージド ID 接続を使う必要があります。

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

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

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

Durable Functions のゼロダウンタイム デプロイメントのためのスロット スワップ前のデプロイ スロットとストレージ アカウントの構成のスクリーンショット

host.json の例

次の JSON フラグメントは、host.json ファイルの接続文字列設定を示しています。

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

レガシーなFunctions 1.x アプリの場合は、azureStorageConnectionStringName プロパティをstorageProvider.connectionStringNameセクションではなくdurableTaskセクションで直接使用します。

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

関数アプリに保留中または実行中のオーケストレーション インスタンスがない場合にのみデプロイするように、CI/CD パイプラインを構成します。 Azure Pipelinesを使用している場合は、次の C# の例のように、これらの条件をチェックする関数を作成できます。 同じパターンが他の言語にも適用されます。 Pending 状態または Running 状態のオーケストレーション インスタンスに対してクエリを実行し、存在するかどうかを返します。

[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() });
}

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

Durable Functions のゼロダウンタイム デプロイメントのための Azure Pipelines 展開ゲート構成のスクリーンショット。

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

オーケストレーション インスタンスの実行中のAzure Pipelines展開ゲート チェックのスクリーンショット。

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

ゼロ ダウンタイムのデプロイ中にステージング スロットにデプロイされた新しいDurable Functions アプリ バージョンのスクリーンショット。

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

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

スロット スワップ完了のスクリーンショットで、Durable Functions アプリの設定が本番環境に移動されました。

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

申請ルート指定

この戦略は最も複雑ですが、スロット スワップ用のアイドル ウィンドウがないオーケストレーションを継続的に実行するシステムの唯一のオプションです。

この戦略では、Durable Functionsの前にアプリケーション ルーターを作成します。たとえば、HTTP トリガーを使用するAzure関数や、バージョン ヘッダーに基づいてルーティングする API Management インスタンスなどです。 ルーターの役割は次のとおりです。

  • 関数アプリのデプロイ。
  • アクティブなアプリのバージョンの管理。
  • バージョンに基づいて、オーケストレーション要求を適切な関数アプリにルーティングします。

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

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

ルーターは、アプリのコードのバージョンが、Azure内の関数アプリにデプロイされる状態を管理します。

Durable Functions のダウンタイムゼロデプロイを行う最初のアプリケーションルーティングとデプロイフローのスクリーンショット

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

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

重大な変更がない場合の Durable Functions デプロイ時のアプリケーションルーティングのスクリーンショット

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

破壊的変更を伴うDurable Functions展開のアプリケーション ルーティングのスクリーンショット。

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

追跡ストアの設定

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

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

バージョン管理された Durable Functions アプリ全体で共有される追跡ストア設定のスクリーンショット

次のステップ