ミッション クリティカルなワークロードに対するアプリケーション設計の考慮事項

ベースラインのミッション クリティカルなリファレンス アーキテクチャは、シンプルなオンライン カタログ アプリケーションを使用して、信頼性の高いワークロードについて説明します。 ユーザーは、項目のカタログを参照、項目の詳細を表示、項目の評価やコメントを投稿することができます。 この記事では、要求の非同期処理やソリューション内で高スループットを実現する方法など、ミッション クリティカルなアプリケーションの信頼性と回復性の側面について説明します。

重要

Azure 上でのミッションクリティカルなアプリケーション開発を紹介するGitHub ロゴ製品グレードのリファレンス実装は、この記事のガイダンスをサポートします。 この実装は、実稼働に向けた最初のステップで、さらなるソリューション開発の基盤として使用できます。

アプリケーションの構成

大規模でミッション クリティカルなアプリケーションでは、エンドツーエンドのスケーラビリティと回復性のためにアーキテクチャを最適化することが不可欠です。 コンポーネントは、独立して動作できる機能単位に分離できます。 アプリケーション スタックのすべてのレベルでこの分離を適用して、システムの各部分を独立してスケーリングし、需要の変化に対応できるようにします。 この実装は、このアプローチを示しています。

アプリケーションでステートレス API エンドポイントを使用すると、実行時間の長い書き込み要求がメッセージング ブローカーを介して非同期的に分離されます。 ワークロードの構成を使用すると、Azure Kubernetes Service (AKS) クラスター全体と、スタンプ内の他の依存関係をいつでも削除して再作成できます。 アプリケーションの主要コンポーネントを次に示します。

  • ユーザー インターフェイス (UI):ユーザーがアクセスできるシングルページ Web アプリケーション。 UI は、Azure Storage アカウントの静的 Web サイト ホストとしてホストされます。

  • API (CatalogService): UI アプリケーションが呼び出す REST API。ただし、他の潜在的なクライアント アプリケーションにも使用できます。

  • Worker (BackgroundProcessor): メッセージ バスの新しいイベントをリッスンし、データベースへの書き込みリクエストを処理するバックグラウンド ワーカー。 このコンポーネントは API を公開しません。

  • 正常性サービス API (HealthService): データベースやメッセージング バスなど重要なコンポーネントが動作しているかを確認し、アプリケーションの正常性を報告する API。

    アプリケーション フローを示す図。

ワークロードは、API、ワーカー、正常性チェックアプリケーションで構成されています。 コンテナとしてワークロードをホストする workload という専用 AKS 名前空間。 ポッド間の直接通信は発生しません。 ポッドは、ステートレスであり、独立してスケーリングできます。

ワークロードの詳細な構成を示す図。

クラスタで実行されているその他の対応コンポーネントは、以下のとおりです。

  • NGINX イングレス コントローラー: 受信リクエストをワークロードにルーティングし、ポッド間での負荷を分散します。 NGINX イングレス コントローラーは、パブリック IP アドレスを持つ Azure Load Balancer を経由して公開されますが、Azure Front Door を介してのみアクセスできます。

  • 証明書マネージャー: イングレス ルール向けの Let's Encrypt を使用した Jetstackcert-manager の 自動プロビジョン Transport Layer Security (TLS) 資格認定書。

  • Secrets Store CSI Driver: Secrets Store CSI Driver 向け Azure Key Vault プロバイダは、Key Vault からの接続文字列などのシークレットを安全に読み取ります。

  • 監視エージェント: デフォルトの OMSAgentForLinux 構成は、Azure Monitor Logs ワークスペースに送信される監視データ量を減らすために調整されます。

データベース接続

デプロイ スタンプは一時的なものであるため、状態をスタンプ内に保持することはできるだけ避けます。 状態は、外部化されたデータ ストアで保持します。 信頼性サービス レベル目標 (SLO) をサポートするには、回復性のあるデータ ストアを作成します。 マネージドまたは Platform as a Service (PaaS) 、ソリューションを、タイムアウト、切断、その他エラー状態を自動で処理するネイティブ SDK ライブラリと組み合わせて使用することが推奨されます。

このリファレンス実装では、 Azure Cosmos DB がアプリケーションのメイン データ ストアとして機能します。 Azure Cosmos DB では、複数リージョンの書き込みが可能です。 各スタンプは、同じリージョン内の Azure Cosmos DB レプリカに書き込むことができ、リージョン間のデータ レプリケーションと同期が Azure Cosmos DB によって内部的に処理されます。 Azure Cosmos DB for NoSQL では、データベース エンジンのすべての機能がサポートされています。

詳細については、「ミッション クリティカルなワークロードのデータ プラットフォーム」を参照してください。

Note

新しいアプリケーションでは、Azure Cosmos DB for NoSQL を使用します。 別の NoSQL プロトコルを使用するレガシ アプリケーションの場合は、Azure Cosmos DB への移行パスを評価します。

パフォーマンスよりも可用性を優先するミッション クリティカルなアプリケーションの場合は、厳密な整合性レベルの 単一リージョンの書き込みと複数リージョンの読み取りが推奨されます。

このアーキテクチャは、Storage を使用して、Event Hubs チェックポイント設定のために、スタンプに状態を一時的に格納します。

ワークロード コンポーネントはすべて、Azure Cosmos DB .NET Core SDK を使用して、データベースと通信します。 SDK には、データベース接続を維持し、障害を処理するための堅牢なロジックが用意されています。 主要な構成設定は次のとおりです。

  • 直接接続モード: この設定は、パフォーマンスが向上するため、.NET SDK v3 の既定値です。 直接接続モードでは、HTTP を使用するゲートウェイ モードと比較して、ネットワーク ホップが少なくなります。

  • 書き込み時にコンテンツ応答を返す: このアプローチは、Azure Cosmos DB クライアントが、ネットワーク トラフィックを削減する作成、アップサート、パッチ、置換操作からドキュメントを返すことができないため、無効になっています。 クライアントでの追加処理では、この設定は必要ありません。

  • カスタム シリアル化: このプロセスでは、JsonNamingPolicy.CamelCase が .NET プロパティを標準の JSON プロパティに変換するように JSON プロパティの名前付けポリシーを設定します。 また、JSON プロパティを .NET プロパティに変換することもできます。 既定の無視条件により、シリアル化中は、JsonIgnoreCondition.WhenWritingNull のような Null 値を持つプロパティは無視されます。

  • ApplicationRegion: このプロパティはスタンプのリージョンに設定されます。これにより、SDK は最も近い接続エンドポイントを検索できます。 エンドポイントは、同じリージョンにすることが推奨されます。

参照実装では、次のコード ブロックが表示されます。

//
// /src/app/AlwaysOn.Shared/Services/CosmosDbService.cs
//
CosmosClientBuilder clientBuilder = new CosmosClientBuilder(sysConfig.CosmosEndpointUri, sysConfig.CosmosApiKey)
    .WithConnectionModeDirect()
    .WithContentResponseOnWrite(false)
    .WithRequestTimeout(TimeSpan.FromSeconds(sysConfig.ComsosRequestTimeoutSeconds))
    .WithThrottlingRetryOptions(TimeSpan.FromSeconds(sysConfig.ComsosRetryWaitSeconds), sysConfig.ComsosMaxRetryCount)
    .WithCustomSerializer(new CosmosNetSerializer(Globals.JsonSerializerOptions));

if (sysConfig.AzureRegion != "unknown")
{
    clientBuilder = clientBuilder.WithApplicationRegion(sysConfig.AzureRegion);
}

_dbClient = clientBuilder.Build();

非同期メッセージング

疎結合を実装する場合、サービスは他のサービスに依存しません。 の特徴により、サービスは独立して動作できます。 結合の特徴により、明確に定義されたインターフェイスを介したサービス間通信が可能になります。 ミッション クリティカルなアプリケーションの場合、疎結合により、ダウンストリームの障害がフロントエンドやその他のデプロイ スタンプにカスケードするのを防ぎ、高可用性を実現します。

非同期メッセージングの主な特徴は次のとおりです。

  • サービスは、同じコンピューティング プラットフォーム、プログラミング言語、またはオペレーティング システムを使用する必要はありません。

  • サービスは独立してスケーリングします。

  • ダウンストリームの障害がクライアント トランザクションに影響しません。

  • データの作成と永続化が別々のサービスで行われるため、トランザクションの整合性を維持することはより困難です。 トランザクションの整合性は、メッセージング サービスと永続化サービス全体の課題です。 詳細については、「Idempotent message processing」を参照してください。

  • エンドツーエンドのトレースには、複雑なオーケストレーションが必要です。

Queue-Based Load Leveling patternCompeting Consumers pattern などよく知られている設計パターンの使用が推奨されます。 これらのパターンは、プロデューサーからコンシューマーへの負荷を分散し、コンシューマーによる非同期を処理します。 たとえば、ワーカーは、API で要求を受け入れて呼び出し元にすばやく返し、ワーカーがデータベースの書き込み操作を個別に処理するようにします。

Event Hubs は、API とワーカー間のメッセージを仲介します。

重要

メッセージ ブローカーは、長期間にわたる永続的なデータ ストアとして使用しないでください。 Event Hubs サービスでは、キャプチャ機能がサポートされています。 キャプチャ機能を使用すると、イベント ハブは、リンクされたストレージ アカウントにメッセージのコピーを自動的に書き込むことができます。 このプロセスは、メッセージをバックアップするメカニズムとして使用状況とサービスを制御します。

書き込み操作の実装の詳細

評価の投稿やコメントの投稿などの書き込み操作は非同期的に処理されます。 最初に、API から、アクションの種類やコメント データなど、すべての関連情報を含むメッセージがメッセージ キューに送信され、作成されるオブジェクトの Location ヘッダーを含む HTTP 202 (Accepted) がすぐに返されます。

キュー内で BackgroundProcessor インスタンスがメッセージと処理し、書き込み操作の実際のデータベース通信を処理します。 BackgroundProcessor が、キュー上のメッセージ ボリュームに基づいて動的にスケールインおよびスケールアウトします。 プロセッサ インスタンスのスケールアウト制限は、Event Hubs パーティションの最大数 (Basic レベルと Standard レベルでは 32、プレミアム レベルでは 100、専用レベルでは 1024) によって定義されます。

実装における評価投稿機能の非同期性を示す図。

BackgroundProcessor の Azure Event Hubs プロセッサ ライブラリは、Azure Blob Storage を使用して、パーティションの所有権、さまざまなワーカー インスタンス間の負荷分散を管理し、チェックポイントを使用して進行状況を追跡します。 チェックポイントは、すべてのメッセージに高価な遅延を追加するため、すべてのイベントの後に Blob Storage に書き込まれることはありません。 代わりに、チェックポイントはタイマー ループに書き込まれ、期間を構成できます。 既定の設定は 10 秒です。

参照実装では、次のコード ブロックが表示されます。

while (!stoppingToken.IsCancellationRequested)
{
    await Task.Delay(TimeSpan.FromSeconds(_sysConfig.BackendCheckpointLoopSeconds), stoppingToken);
    if (!stoppingToken.IsCancellationRequested && !checkpointEvents.IsEmpty)
    {
        string lastPartition = null;
        try
        {
            foreach (var partition in checkpointEvents.Keys)
            {
                lastPartition = partition;
                if (checkpointEvents.TryRemove(partition, out ProcessEventArgs lastProcessEventArgs))
                {
                    if (lastProcessEventArgs.HasEvent)
                    {
                        _logger.LogDebug("Scheduled checkpointing for partition {partition}. Offset={offset}", partition, lastProcessEventArgs.Data.Offset);
                        await lastProcessEventArgs.UpdateCheckpointAsync();
                    }
                }
            }
        }
        catch (Exception e)
        {
            _logger.LogError(e, "Exception during checkpointing loop for partition={lastPartition}", lastPartition);
        }
    }
}

プロセッサ アプリケーションでエラーが発生した場合や、メッセージを処理する前に停止した場合は、次のようになります。

  • 別のインスタンスが再処理のためにメッセージを取得します。これは、Storage 内にチェックポイントが適切に設定されなかったためです。

  • 競合は、ワーカーが失敗する前に、以前のワーカーがドキュメントをデータベースに保存した場合に発生します。 このエラーは、同じ ID とパーティション キーが使用されるために発生します。 ドキュメントは既に永続化されているため、プロセッサはメッセージを無視しても問題ありません。

  • 以前のワーカーがデータベースに書き込む前に終了した場合、新しいインスタンスは、手順を繰り返して、永続化を最終化します。

読み取り操作の実装詳細

API は直接読み取り操作を処理し、すぐにユーザーにデータを返します。

読み取り操作プロセスを示す図。

操作が正常に完了した場合、クライアントと通信するためのバックチャネル メソッドは確立されません。 クライアント アプリケーションから、Location HTTP ヘッダーに指定された項目の更新に対して API を事前にポーリングする必要があります。

スケーラビリティ

個々のワークロード コンポーネントは、それぞれのコンポーネントのロード パターンが異なるため、独立してスケールアウトする必要があります。 スケーリング要件は、サービスの機能によって異なります。 特定のサービスはユーザーに直接影響し、迅速な応答と肯定的なユーザー エクスペリエンスを確保するために積極的にスケールアウトする必要があります。

実装では、サービスをコンテナー イメージとしてパッケージし、Helm チャートを使用して各スタンプにサービスをデプロイします。 サービスは、予想される Kubernetes の要求と制限、事前構成済みの自動スケーリング規則が適用されるように構成されます。 CatalogServiceBackgroundProcessor のワークロード コンポーネントは、どちらのサービスもステートレスなので、個別にスケールインおよびスケールアウトできます。

ユーザーが直接 CatalogService を操作するため、ワークロードのこの部分はどのような負荷でも応答する必要があります。 各クラスターには、少なくとも 3 つのインスタンスがあり、Azure リージョンで 3 つの Availability Zone を分散します。 AKS の水平ポッド オートスケーラー (HPA) では、必要に応じてポッドが自動的に追加されます。 Azure Cosmos DB オートスケール機能では、コレクションで使用できる要求ユニット (RU) を動的に増減できます。 CatalogService と Azure Cosmos DB を組み合わせて、スタンプ内にスケール ユニットを形成します。

HPA は、レプリカの最大数と最小数を構成可能な Helm チャートを使ってデプロイされます。 ロード テストでは、各インスタンスが標準の使用パターンで 1 秒あたり約 250 件の要求を処理できることを確認しました。

BackgroundProcessor サービスには非常に異なる要件があり、ユーザー エクスペリエンスへの効果が限られているバックグラウンド ワーカーと見なされます。 そのため BackgroundProcessor は、CatalogService と比較して、異なるオートスケール構成があり、2 から 32 のインスタンス間でスケーリングできます。 イベント ハブで使用するパーティション数に基づいて、この制限を決定します。 パーティションよりも多くのワーカーは必要ありません。

コンポーネント minReplicas maxReplicas
CatalogService 3 20
BackgroundProcessor 2 32

ingress-nginx などの依存性を含むワークフローの各コンポーネントには、 ポッド中断バジェット (PDB) があり、クラスターが変更された際に使用できるインスタンスの最小数を確保するよう設定が構成されています。

参照実装では、次のコード ブロックが表示されます。

#
# /src/app/charts/healthservice/templates/pdb.yaml
# Example pod distribution budget configuration.
#
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: {{ .Chart.Name }}-pdb
spec:
  minAvailable: 1
  selector:
    matchLabels:
      app: {{ .Chart.Name }}

Note

Load Testing を介して、各コンポーネントのポッドの実際の最小数と最大数を決定します。 ポッドの数は、ワークロードごとに異なる場合があります。

インストルメンテーション

インストルメンテーションを使用して、ワークロード コンポーネントがシステムにもたらす可能性のあるパフォーマンスのボトルネックと正常性の問題を評価します。 数量を決定するため、各コンポーネントはメトリックとトレース ログを介して十分な情報を出力する必要があります。 アプリケーションをインストルメント化する際は、次の重要事項を考慮してください。

  • ログ、メトリック、別のテレメトリをスタンプのログ システムに送信します。
  • 情報をクエリできるように、プレーン テキストではなく、構造化ログを使用します。
  • イベントの相関関係を実装して、エンドツーエンドのトランザクション ビューを取得します。 参照実装では、各 API 応用に、追跡可能性のための HTTP ヘッダーとして操作 ID が含まれています。
  • stdout ロギングまたはコンソール ロギングのみに依存しないでください。 ただし、これらのログを使用すると、障害が発生したポッドのトラブルシューティングをすぐに行うことができます。

このアーキテクチャは、アプリケーション監視データ用の Application Insights と Azure Monitor Logs を使用して分散トレースを実装します。 ワークフロー用のログとメトリックそしてインフラストラクチャ コンポーネントに対して Azure Monitor Logs を使用します。 このアーキテクチャは、API から送信され、イベント ハブを経由で Azure Cosmos DB に送信される要求の完全なエンドツーエンド追跡を実装します。

重要

スタンプ監視リソースを別の監視リソース グループにデプロイします。 リソースにはスタンプとは異なるライフサイクルがあります。 詳細については、「スタンプ リソースのデータの監視」を参照してください。

別々のグローバル サービス、監視サービス、スタンプ デプロイの図。

アプリケーション監視の実装詳細

BackgroundProcessor コンポーネントでは、 Microsoft.ApplicationInsights.WorkerService NuGet パッケージを使用して、すぐに使用できるインストルメンテーションをアプリケーションから取得します。 Serilog は、アプリケーション内のすべてのログ記録にも使用されます。 Application Insights は、コンソール シンクに加えてシンクとして構成されます。 Application Insights 用 TelemetryClient インスタンスは、別のメトリックを追跡する必要があるときのみ直接使用されます。

参照実装では、次のコード ブロックが表示されます。

//
// /src/app/AlwaysOn.BackgroundProcessor/Program.cs
//
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
    .ConfigureServices((hostContext, services) =>
    {
        Log.Logger = new LoggerConfiguration()
                            .ReadFrom.Configuration(hostContext.Configuration)
                            .Enrich.FromLogContext()
                            .WriteTo.Console(outputTemplate: "[{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")
                            .WriteTo.ApplicationInsights(hostContext.Configuration[SysConfiguration.ApplicationInsightsConnStringKeyName], TelemetryConverter.Traces)
                            .CreateLogger();
    }

エンドツーエンドのトレース機能のスクリーンショット。

実践的な要求追跡可能性を実証するため、各成功した API 要求と失敗した API 要求は、相関関係 ID ヘッダーを呼び出し元に返します。 アプリケーション サポート チームは、この ID を使用して Application Insights を検索し、前の図で示されている完全なトランザクションの詳細ビューを取得します。

参照実装では、次のコード ブロックが表示されます。

//
// /src/app/AlwaysOn.CatalogService/Startup.cs
//
app.Use(async (context, next) =>
{
    context.Response.OnStarting(o =>
    {
        if (o is HttpContext ctx)
        {
            // ... code omitted for brevity
            context.Response.Headers.Add("X-Server-Location", sysConfig.AzureRegion);
            context.Response.Headers.Add("X-Correlation-ID", Activity.Current?.RootId);
            context.Response.Headers.Add("X-Requested-Api-Version", ctx.GetRequestedApiVersion()?.ToString());
        }
        return Task.CompletedTask;
    }, context);
    await next();
});

Note

Application Insights SDK のアダプティブ サンプリングは既定で有効になっています。 アダプティブ サンプルは、すべての要求が、クラウドに送信され、ID で検索可能ではないことを意味します。 ミッション クリティカルなアプリケーション チームはすべての要求を確実にトレースできる必要があるため、このリファレンス実装では、運用環境のアダプティブ サンプリングが無効になっています。

Kubernetes 監視の実装の詳細

診断設定を使用して、AKS ログとメトリックを Azure Monitor Logs に送信できます。 AKS でコンテナー分析情報機能を使用することもできます。 Container Insights を有効にすると、Kubernetes デーモンセットを介して AKS クラスター内の各ノードに OMSAgentForLinux をデプロイできます。 OMSAgentForLinux は、Kubernetes クラスター内から多くのログとメトリックを収集することができ、それらを対応する Azure Monitor Logs ワークスペースに送信します。 これには、ポッド、デプロイ、サービス、およびクラスターの全体的な正常性に関するより詳細なデータが含まれます。

広範囲に及ぶログはコストに悪影響を与え、メリットはありません。 このため、Container Insights 構成のワークロード ポッドでは、stdout ログ収集と Prometheus スクレイピングが無効になっています。これは、すべてのトレースが Application Insights を通じて既にキャプチャされており、重複レコードが生成されるからです。

参照実装では、次のコード ブロックが表示されます。

#
# /src/config/monitoring/container-azm-ms-agentconfig.yaml
# This is just a snippet showing the relevant part.
#
[log_collection_settings]
    [log_collection_settings.stdout]
        enabled = false

        exclude_namespaces = ["kube-system"]

詳細については、「完全な構成ファイル」を参照してください。

アプリケーションの正常性の監視

アプリケーションの監視は、システムの問題をすばやく特定し、現在のアプリケーション状態について正常性モデルに通知するために使用されます。 正常性エンドポイントを使用して正常性の監視を表示できます。 正常性プローブは、正常性監視データを使用して情報を提供します。 主要なロード バランサーは、その情報を使用して、異常なコンポーネントをすぐにローテーションから外します。

このアーキテクチャは、次のレベルで正常性監視を適用します。

  • AKS で実行されるワークロード ポッド。 これらのポッドには正常性プローブと liveness probe があるため、AKS はそれらのライフサイクルを管理できます。

  • 正常性サービス は、クラスター上の専用コンポーネントです。 Azure Front Door は、各スタンプの正常性サービスをプローブし、異常なスタンプを負荷分散から自動的に削除するように構成されています。

正常性サービスの実装詳細

HealthService は、コンピューティング クラスター上の CatalogServiceBackgroundProcessor などのその他コンポーネントと共に実行されているワークフロー コンポーネントです。 HealthService は、Azure Front Door 正常性チェックから呼び出される REST API を指定し、スタンプの可用性を判断します。 基本的な liveness probe とは異なり、正常性サービスはより複雑なコンポーネントで、その状態に加えて依存関係の状態を追加します。

正常性サービスから Azure Cosmos DB、Event Hub、Storage にクエリを実行している図。

AKS クラスターがダウンしている場合、常性サービスは応答せず、ワークフローを異常な状態にします。 サービスが実行されている場合、ソリューションの重要コンポーネントに対して定期的なチェックを実行します。 すべてのチェックが 非同期的かつ並列に行われます。 いずれかのチェックが失敗した場合、スタンプ全体を使用できません。

警告

要求が複数の point of presence (PoP) の場所から送信されるため、Azure Front Door 正常性プローブは、正常性サービスに大きな負荷を与える場合があります。 ダウンストリーム コンポーネントのオーバーロードを防ぐために、効果的なキャッシュを実装します。

正常性サービスは、各スタンプの Application Insights リソースで明示的に構成された URL ping テストにも使用されます。

HealthService 実装の詳細については、「アプリケーション正常性サービス」を参照してください。

次のステップ