.NET 向けモダン Web アプリ パターン
この記事では、モダン Web アプリ パターンを実装する方法について説明します。 モダン Web アプリ パターンは、クラウド内の Web アプリをモダン化し、サービス指向アーキテクチャを導入する方法を定義したものです。 モダン Web アプリ パターンは、Azure Well-Architected Framework の原則に沿った規範的なアーキテクチャ、コード、および構成のガイダンスを提供し実行可能な Web アプリ パターンに基づいて構築。
モダン Web アプリ パターンを使用する理由
モダン Web アプリ パターンは、Web アプリの需要の高い領域を最適化するのに役立ちます。 これらの領域を分離するための詳細なガイダンスを提供します。これにより、コストの最適化のための独立したスケーリングが可能になります。 この方法では、重要なコンポーネントに専用リソースを割り当てることで、全体的なパフォーマンスを向上させることができます。 個別のサービスを分離すると、アプリの一部の速度低下により他の部分に影響が生じることを防ぎ、信頼性を向上させることができます。 分離を使用すると、個々のアプリ コンポーネントを個別にバージョン管理することもできます。
モダン Web アプリ パターンの実装方法
この記事には、モダン Web アプリ パターンを実装するためのアーキテクチャ、コード、および構成ガイダンスが含まれています。 次のリンクを使用して、必要なガイダンスに移動します。
- アーキテクチャのガイダンス。 Web アプリ コンポーネントをモジュール化し、適切なサービスとしてのプラットフォーム (PaaS) ソリューションを選択する方法について説明します。
- コード ガイダンス。 分離されたコンポーネントを最適化するための 4 つの設計パターンを実装します。ストラングラー Fig、Queue-Based 負荷平準化、競合コンシューマー、正常性エンドポイント監視。
- 構成ガイダンス。 分離されたコンポーネントの認証、承認、自動スケール、コンテナー化を構成します。
ヒント
モダン Web パターンの 参照実装 (サンプル アプリ) があります。 これは、モダン Web アプリの実装の最終状態を表します。 この記事で説明する、コード、アーキテクチャ、構成に対するすべての更新プログラムを取り入れた実稼働グレードの Web アプリです。 この参照実装をデプロイすることで、モダン Web アプリ パターンを実装するためのガイドとして使用できます。
アーキテクチャのガイダンス
モダン Web アプリ パターンは、信頼性の高い Web アプリ パターンに基づいています。 実装するには、いくつかの追加のアーキテクチャ コンポーネントが必要です。 メッセージ キュー、コンテナー プラットフォーム、分離されたサービス データ ストア、およびコンテナー レジストリが必要です。 次の図は、ベースライン アーキテクチャを示しています。
より高いサービス レベル目標 (SLO) では、Web アプリ アーキテクチャに 2 つ目のリージョンを追加できます。 2 つ目のリージョンを追加する場合は、アクティブ/アクティブ構成またはアクティブ/パッシブ構成をサポートするように、そのリージョンにトラフィックをルーティングするようにロード バランサーを構成する必要があります。 ハブアンドスポーク ネットワーク トポロジを使用して、ネットワーク ファイアウォールなどのリソースを一元化して共有します。 ハブ仮想ネットワーク経由でコンテナー リポジトリにアクセスします。 仮想マシンがある場合は、要塞ホストをハブ仮想ネットワークに追加して、セキュリティを強化して管理します。 次の図は、このアーキテクチャを示しています。
アーキテクチャを分離する
モダン Web アプリ パターンを実装するには、既存の Web アプリ アーキテクチャを分離する必要があります。 アーキテクチャを分離するには、モノリシック アプリケーションを、それぞれ特定の機能を担当する小規模な独立したサービスに分割する必要があります。 このプロセスでは、現在の Web アプリの評価、アーキテクチャの変更、最後にコンテナー プラットフォームへの Web アプリ コードの抽出が必要です。 目標は、分離されることで最もメリットのあるアプリケーション サービスを体系的に特定して抽出することです。 アーキテクチャを分離するには、次の推奨事項に従います。
サービスの境界を特定します。 ドメイン駆動型の設計原則を適用して、モノリシック アプリケーション内の境界付けられたコンテキストを識別します。 境界付けられた各コンテキストは論理境界を表しており、別個のサービスとする候補と考えられます。 個別のビジネス機能を表す、依存関係が少ないサービスは、分離の候補として適しています。
サービスの利点を評価します。 独立したスケーリングにより最大のメリットが得られるサービスに焦点を当てます。 これらのサービスを切り離し、処理タスクを同期操作から非同期操作に変換することで、より効率的なリソース管理が可能になり、独立したデプロイがサポートされ、更新または変更中にアプリケーションの他の部分に影響するリスクが軽減されます。 たとえば、注文のチェックアウトを注文処理から切り離すことができます。
技術的な実現可能性を評価します。 現在のアーキテクチャを調べて、分離プロセスに影響する可能性がある技術的な制約と依存関係を特定します。 サービス間でデータを管理および共有する方法を計画します。 分離されたサービスでそれぞれ独自のデータを管理し、サービスの境界を越えた直接データベース アクセスを最小限に抑える必要があります。
Azure サービスをデプロイします。 抽出する Web アプリ サービスをサポートするために必要な Azure サービスを選択してデプロイします。 ガイダンスについては、「 適切な Azure サービスを選択する」を参照してください。
Web アプリ サービスを分離します。 新しく抽出された Web アプリ サービスがシステムの他の部分と対話できるように、明確なインターフェイスと API を定義します。 一貫性と整合性を確保しながら、各サービスが独自のデータを管理できるようにするデータ管理戦略を設計します。 この抽出プロセスで使用する特定の実装戦略と設計パターンについては、この記事の コード ガイダンス セクションを参照してください。
分離されたサービス用に独立したストレージを使用します。 分離された各サービスには、独立したバージョン管理、デプロイ、スケーラビリティを容易にし、データの整合性を維持するために、独自の分離されたデータ ストアが必要です。 たとえば、参照実装では、チケット レンダリング サービスを Web API から分離し、サービスが API のデータベースにアクセスする必要がなくなります。 代わりに、サービスは、チケット イメージが生成された URL を Azure Service Bus メッセージを介して Web API に渡し、API はそのデータベースへのパスを保持します。
分離されたサービスごとに個別のデプロイ パイプラインを実装します。 個別のデプロイ パイプラインを使用することで、各サービスを独自のペースで更新できます。 社内のチームや組織がそれぞれ異なるサービスを所有している場合、個別のデプロイ パイプラインを使用することで、各チームが独自のデプロイを制御できます。 Jenkins、GitHub Actions、Azure Pipelines などの継続的インテグレーションおよび継続的デリバリー (CI/CD) ツールを使用して、これらのパイプラインを設定します。
セキュリティの制御を見直します。 ファイアウォール規則やアクセス制御など、新しいアーキテクチャを考慮してセキュリティ制御が更新されていることを確認します。
適切な Azure サービスを選択する
自社のアーキテクチャ内の各 Azure サービスについては、Well-Architected Framework の関連する Azure サービス ガイドを参照してください。 モダン Web アプリ パターンでは、非同期メッセージングをサポートするためのメッセージング システム、コンテナー化をサポートするアプリケーション プラットフォーム、コンテナー イメージ リポジトリが必要です。
メッセージ キューを選択します。 メッセージ キューは、サービス指向アーキテクチャの重要なコンポーネントです。 これによりメッセージの送信者と受信者が分離され、非同期メッセージングが有効になります。 Azure メッセージング サービスの選択に関するガイダンスを使用して、設計のニーズに対応した Azure メッセージング システムを選択します。 Azure には、Azure Event Grid、Azure Event Hubs、Azure Service Bus の 3 つのメッセージング サービスがあります。 既定の選択肢として Service Bus から開始し、Service Bus がニーズを満たしていない場合は、他の 2 つのオプションを使用します。
サービス ユース ケース サービスバス エンタープライズ アプリケーションで価値の高いメッセージを信頼性が高く、順序付けして、場合によってはトランザクション配信を行う場合は、Service Bus を選択します。 イベントグリッド 多数の個別のイベントを効率的に処理する必要がある場合は、Event Grid を選択します。 Event Grid は、待機時間の短いパブリッシュ/サブスクライブ モデルで多数の小さな独立したイベント (リソース状態の変更など) をサブスクライバーにルーティングする必要があるイベント ドリブン アプリケーションに対してスケーラブルです。 イベント ハブ テレメトリ、ログ、リアルタイム分析など、大量の高スループットのデータ インジェストのために Event Hubs を選択します。 Event Hubs は、一括データを継続的に取り込んで処理する必要があるストリーミング シナリオ向けに最適化されています。 コンテナー サービスを実装します。 コンテナー化するアプリケーションのコンポーネントには、コンテナーをサポートするアプリケーション プラットフォームが必要です。 Azure コンテナー サービスの選択に関するガイダンスは、意思決定に役立ちます。 Azure には、Azure Container Apps、Azure Kubernetes Service (AKS)、Azure アプリ Service の 3 つの主要なコンテナー サービスがあります。 既定の選択肢として Container Apps から開始し、Container Apps がニーズを満たしていない場合は、他の 2 つのオプションを使用します。
サービス ユース ケース コンテナー アプリ イベント ドリブン アプリケーションでコンテナーを自動的にスケーリングおよび管理するサーバーレス プラットフォームが必要な場合は、Container Apps を選択します。 AKS Kubernetes の構成の詳細な制御や、スケーリング、ネットワーク、セキュリティの高度な機能が必要な場合は、AKS を選択します。 コンテナ用Webアプリ App Service で Web App for Containers を選択すると、最も簡単な PaaS エクスペリエンスが得られます。 コンテナー リポジトリを実装します。 コンテナー ベースのコンピューティング サービスを使用する場合は、コンテナー イメージを格納するリポジトリが必要です。 Docker Hub などのパブリック コンテナー レジストリや、Azure Container Registry などのマネージド レジストリを使用できます。 Azure のコンテナー レジストリの概要に関するガイダンスは、意思決定に役立ちます。
コードのガイダンス
独立したサービスを正常に分離して抽出するには、ストラングラー フィグ パターン、キューベースの負荷平準化パターン、競合コンシューマー パターン、正常性エンドポイント監視パターン、再試行パターンの 4 種類の設計パターンで Web アプリ コードを更新する必要があります。 これらのパターンの役割を次に示します。
ストラングラー フィグ パターン: ストラングラー フィグ パターンは、分離されたサービスにモノリシック アプリケーションから段階的に機能を移行します。 このパターンをメイン Web アプリに実装し、エンドポイントに基づいてトラフィックを転送することで、独立したサービスに徐々に機能を移行します。
キューベースの負荷平準化パターン: キュー ベースの負荷平準化パターンは、キューをバッファーとして使用して、プロデューサーとコンシューマーの間のメッセージのフローを管理します。 このパターンを、キューのメッセージを生成するコードベースに実装します。 分離されたサービスは、キューからこれらのメッセージを非同期的に使用します。
競合コンシューマー パターン: 競合コンシューマー パターンでは、分離されたサービスの複数のインスタンスが同じメッセージ キューから個別に読み取り、競い合ってメッセージを処理します。 分離されたサービスにこのパターンを実装し、複数のインスタンスにタスクを分散させます。
正常性エンドポイント監視パターン: 正常性エンドポイント監視パターンは、Web アプリのさまざまな部分の状態と正常性を監視するためのエンドポイントを公開します。 (4a) メイン Web アプリにこのパターンを実装します。 (4b) エンドポイントの正常性を追跡するために、分離されたサービスにも実装します。
再試行パターン: 再試行パターンは、断続的に失敗する可能性のある操作を再試行することによって、一時的なエラーを処理します。 (5a) メッセージ キューやプライベート エンドポイントの呼び出しなど、メイン Web アプリ内の他の Azure サービスへのすべての発信呼び出しにこのパターンを実装します。 (5b) プライベート エンドポイントの呼び出しにおける一時的な障害を処理するために、分離されたサービスにもこのパターンを実装します。
各設計パターンには、Well-Architected Framework の 1 つ以上の柱に沿った利点があります。 詳細は次の表を参照してください。
設計パターン | 実装の場所 | 信頼性 (RE) | セキュリティ (SE) | コスト最適化 (CO) | オペレーショナル エクセレンス (OE) | パフォーマンス効率 (PE) | 適切に設計されたフレームワークの原則のサポート |
---|---|---|---|---|---|---|---|
ストラングラー フィグ パターン | メイン Web アプリ | ✔ | ✔ | ✔ |
RE:08 CO:07 CO:08 OE数:06 OE:11 |
||
キューベースの負荷平準化パターン | メイン Web アプリ (メッセージ プロデューサー) | ✔ | ✔ | ✔ |
RE:07 RE:07 CO:12 PE:05 |
||
競合コンシューマー パターン | 分離されたサービス | ✔ | ✔ | ✔ |
RE:05 RE:07 CO:05 CO:07 PE:05 PE:07 |
||
正常性エンドポイントの監視パターン | メイン Web アプリと分離されたサービス | ✔ | ✔ | ✔ |
RE:07 RE:10 OE:07 PE:05 |
||
再試行パターン | メイン Web アプリと分離されたサービス | ✔ | RE:07 |
ストラングラー フィグ パターンを実装する
Strangler Fig パターンを使用して、モノリシック コード ベースから新しい独立したサービスに機能を段階的に移行します。 既存のモノリシック コード ベースから新しいサービスを抽出し、Web アプリの重要な部分を徐々にモダン化します。 ストラングラー フィグ パターンを実装するには、次の推奨事項に従います。
ルーティング層を設定します。 モノリシック Web アプリのコード ベースで、エンドポイントに基づいてトラフィックを転送するルーティング層を実装します。 必要に応じてカスタム ルーティング ロジックを使用し、トラフィックを転送するための特定のビジネス ルールを処理します。 たとえば、モノリシック アプリに
/users
エンドポイントがあり、その機能を分離されたサービスに移動すると、ルーティング層はすべての要求を新しいサービスに/users
するように指示します。機能のロールアウトを管理します。 .NET 機能管理ライブラリを使用して 機能フラグを実装 し、 段階的にロールアウト して、分離されたサービスを段階的にロールアウトします。 既存のモノリシック アプリ ルーティングでは、分離されたサービスが受信する要求の数を制御する必要があります。 新しいサービスの安定性とパフォーマンスに自信を持てるように、要求のごく一部から始めて、時間の経過と共に使用量を増やします。 たとえば、参照実装では、チケット レンダリング機能をスタンドアロン サービスに抽出します。これは、チケット レンダリング要求の大部分を処理するために徐々に導入できます。 新しいサービスが信頼性とパフォーマンスを証明するにつれて、最終的にモノリスからチケットレンダリング機能全体を引き継ぎ、移行を完了できます。
ファサード サービスを使用します (必要な場合)。 ファサード サービスは、1 つの要求で複数のサービスとやり取りする必要がある場合や、基盤となるシステムの複雑さをクライアントから認識されないようにする場合に便利です。 ただし、分離されたサービスに公開 API がない場合は、ファサード サービスは必要ない可能性があります。 モノリシック Web アプリのコード ベースで、要求を適切なバックエンド (モノリスまたはマイクロサービス) にルーティングするファサード サービスを実装します。 新しい分離されたサービスでは、ファサードを介してアクセスしたときに、新しいサービスが個別に要求を処理できることを確認します。
キューベースの負荷平準化パターンを実装する
分離されたサービスのプロデューサー部分にQueue ベースの負荷平準化パターンを実装して、即時応答を必要としないタスクを非同期的に処理します。 このパターンでは、キューを使用してワークロードの分散を管理することで、システムの全体的な応答性とスケーラビリティが向上します。 これにより、分離されたサービスは一貫した速度で要求を処理できます。 このパターンを効果的に実装するには、次の推奨事項に従います。
非ブロッキング メッセージ キューを使用します。 分離されたサービスがキュー内のメッセージを処理するのを待機している間に、キューにメッセージを送信するプロセスが他のプロセスをブロックしないようにします。 分離されたサービスの操作の結果をプロセスが必要とする場合は、キューに登録された操作の完了を待機している間に状況を処理するための別の方法があります。 たとえば、参照実装では、Service Bus と
await
キーワードとmessageSender.PublishAsync()
を使用して、このコードを実行するスレッドをブロックすることなく、メッセージをキューに非同期的に発行します。// Asynchronously publish a message without blocking the calling thread. await messageSender.PublishAsync(new TicketRenderRequestMessage(Guid.NewGuid(), ticket, null, DateTime.Now), CancellationToken.None);
この方法により、分離されたサービスがキューに登録された要求を管理可能な速度で処理する間、メイン アプリケーションの応答性が維持され、他のタスクを同時に処理できるようになります。
メッセージの再試行と削除を実装します。 キューに登録されたメッセージのうち、正常に処理できないものの処理を再試行するメカニズムを実装します。 エラーが解決しない場合は、それらのメッセージをキューから削除する必要があります。 たとえば、Service Bus には再試行機能と配信不能キュー機能が組み込まれています。
べき等メッセージ処理を構成します。 キューからのメッセージを処理するロジックは、メッセージが複数回処理される場合を処理するために べき等 である必要があります。 たとえば、参照実装では、
ServiceBusClient.CreateProcessor
とAutoCompleteMessages = true
でReceiveMode = ServiceBusReceiveMode.PeekLock
を使用して、メッセージが 1 回だけ処理され、失敗した場合に再処理できることを確認します。 次のコードは、このロジックを示しています。// Create a processor for idempotent message processing. var processor = serviceBusClient.CreateProcessor(path, new ServiceBusProcessorOptions { // Allow the messages to be auto-completed // if processing finishes without failure. AutoCompleteMessages = true, // PeekLock mode provides reliability in that unsettled messages // will be redelivered on failure. ReceiveMode = ServiceBusReceiveMode.PeekLock, // Containerized processors can scale at the container level // and need not scale via the processor options. MaxConcurrentCalls = 1, PrefetchCount = 0 });
エクスペリエンスの変更を管理します。 非同期処理を使用すると、タスクがすぐに完了しない可能性があります。 適切な予想を立て、混乱を避けるために、タスクがまだ処理中である場合をユーザーが認識ができるようにする必要があります。 タスクが進行中であることを示すには、視覚的な手掛かりまたはメッセージを使用します。 メールやプッシュ通知など、タスクの完了時に通知を受け取るオプションをユーザーに提供します。
競合コンシューマー パターンを実装する
分離されたサービスに競合コンシューマー パターンを実装して、メッセージ キューからの受信タスクを管理します。 このパターンでは、分離されたサービスの複数のインスタンスにタスクを分散する必要があります。 これらのサービスはキューからのメッセージを処理し、負荷分散を強化し、同時要求を処理するためのシステムの容量を増やします。 競合コンシューマー パターンは、次の場合に有効です。
- メッセージ処理のシーケンスが重要ではない。
- キューが、形式に誤りがあるメッセージの影響を受けない。
- 処理操作はべき等である (最初の適用以降、結果を変更することなく、複数回適用できる)。
競合コンシューマー パターンを実装するには、次の推奨事項に従います。
同時実行メッセージを処理します。 システムがキューからメッセージを受信するときは、システムが複数のメッセージを同時に処理するように設計されていることを確認します。 個別のコンシューマーが各メッセージを処理するように、同時呼び出しの最大数を 1 に設定します。
プリフェッチを無効にします コンシューマーが準備ができたときにのみメッセージをフェッチするように、メッセージのプリフェッチを無効にします。
信頼できるメッセージ処理モードを使用します。 処理に失敗したメッセージを自動的に再試行する PeekLock (またはそれと同等のもの) など、信頼性の高い処理モードを使用します。 このモードでは、削除優先の方法よりも信頼性が高くなります。 あるワーカーがメッセージの処理に失敗した場合、そのメッセージが複数回処理されるとしても、別のワーカーがエラーなしでそのメッセージを処理できる必要があります。
エラー処理を実装します。 形式が正しくないメッセージまたは処理できないメッセージを別の配信不能キューにルーティングします。 この設計により、繰り返し処理を防ぐことができます。 たとえば、メッセージ処理中に例外をキャッチし、問題のあるメッセージを別のキューに移動できます。
順序が正しくないメッセージを処理します。 誤った順序で到着したメッセージを処理するようにコンシューマーを設計します。 複数の並列コンシューマーがある場合は、メッセージが順番に処理されていない可能性があります。
キューの長さに基づいてスケーリングします。 キューからメッセージを使用するサービスでは、キューの長さに基づく自動スケールを検討するか、 追加のスケーリング条件 を使用して受信メッセージの急増をより適切に処理することを検討する必要があります。
メッセージ応答キューを使用します。 システムでメッセージ後処理の通知が必要な場合は、専用の応答キューまたは応答キューを設定します。 この設定により、操作メッセージングが通知プロセスから切り離されます。
ステートレス サービスを使用します。 ステートレス サービスを使用してキューからの要求を処理することを検討してください。 これらのサービスを使用すると、簡単なスケーリングと効率的なリソースの使用が可能になります。
ログを構成します。 メッセージ処理ワークフロー内にログ記録と特定の例外処理を統合します。 シリアル化エラーをキャプチャし、これらの問題のあるメッセージを配信不能メカニズムに転送することに重点を置きます。 これらのログは、トラブルシューティングに役立つ貴重な分析情報を提供します。
たとえば、参照実装では、Container Apps で実行されているステートレス サービスの競合コンシューマー パターンを使用して、Service Bus キューからのチケットレンダリング要求を処理します。 次の内容でキュー プロセッサを構成します。
-
AutoCompleteMessages
。 エラーなしで処理された場合、メッセージは自動的に完了します。 -
ReceiveMode
。 PeekLock モードを使用し、解決されない場合はメッセージを再配信します。 -
MaxConcurrentCalls
。 一度に 1 つのメッセージを処理するには、1 に設定します。 -
PrefetchCount
。 メッセージのプリフェッチを回避するには、0 に設定します。
プロセッサはメッセージ処理の詳細をログに記録します。これは、トラブルシューティングと監視に役立ちます。 逆シリアル化エラーをキャプチャし、無効なメッセージを配信不能キューにルーティングして、エラーのあるメッセージの繰り返し処理を防ぎます。 サービスはコンテナー レベルでスケーリングされるため、キューの長さに基づいてメッセージスパイクを効率的に処理できます。
// Create a processor for the given queue that will process
// incoming messages.
var processor = serviceBusClient.CreateProcessor(path, new ServiceBusProcessorOptions
{
// Allow the messages to be auto-completed
// if processing finishes without failure.
AutoCompleteMessages = true,
// PeekLock mode provides reliability in that unsettled messages
// are redelivered on failure.
ReceiveMode = ServiceBusReceiveMode.PeekLock,
// Containerized processors can scale at the container level
// and need not scale via the processor options.
MaxConcurrentCalls = 1,
PrefetchCount = 0
});
// Called for each message received by the processor.
processor.ProcessMessageAsync += async args =>
{
logger.LogInformation("Processing message {MessageId} from {ServiceBusNamespace}/{Path}", args.Message.MessageId, args.FullyQualifiedNamespace, args.EntityPath);
// Unhandled exceptions in the handler will be caught by
// the processor and result in abandoning and dead-lettering the message.
try
{
var message = args.Message.Body.ToObjectFromJson<T>();
await messageHandler(message, args.CancellationToken);
logger.LogInformation("Successfully processed message {MessageId} from {ServiceBusNamespace}/{Path}",args.Message.MessageId, args.FullyQualifiedNamespace, args.EntityPath);
}
catch (JsonException)
{
logger.LogError("Invalid message body; could not be deserialized to {Type}", typeof(T));
await args.DeadLetterMessageAsync(args.Message, $"Invalid message body; could not be deserialized to {typeof(T)}",cancellationToken: args.CancellationToken);
}
};
正常性エンドポイントの監視パターンを実装する
メイン アプリ コードおよび分離されたサービス コードに正常性エンドポイント監視パターンを実装すると、アプリケーション エンドポイントの正常性を追跡できます。 AKS や Container Apps などのオーケストレーターは、これらのエンドポイントをポーリングしてサービスの正常性を確認し、異常なインスタンスを再起動できます。 ASP.NET Core アプリでは、専用の正常性チェック ミドルウェアを追加することで、エンドポイントの正常性データと主要な依存関係を効率的に処理できます。 正常性エンドポイント監視パターンを実装するには、次の推奨事項に従います。
正常性チェックを実装します。 ASP.NET Core の正常性チェック ミドルウェアを使用して、正常性チェック エンドポイントを提供します。
依存関係を検証します。 正常性チェックによって、データベース、ストレージ、メッセージング システムなどの主要な依存関係の可用性が検証されることを確認します。 Microsoft 以外のパッケージ AspNetCore.Diagnostics.HealthChecks では、多くの一般的なアプリの依存関係に対する正常性チェックの依存関係チェックを実装できます。
たとえば、参照実装では、ASP.NET Core 正常性チェック ミドルウェアを使用して、正常性チェック エンドポイントを公開します。
AddHealthChecks()
オブジェクトでbuilder.Services
メソッドを使用します。 このコードでは、AddAzureBlobStorage()
パッケージの一部であるAddAzureServiceBusQueue()
メソッドとAspNetCore.Diagnostics.HealthChecks
メソッドを使用して、キーの依存関係、Azure Blob Storage、Service Bus キューの可用性を検証します。 Container Apps を使用すると、 正常性プローブ を構成して、アプリが正常であるか、リサイクルが必要かを測定できます。// Add health checks, including health checks for Azure services // that are used by this service. // The Blob Storage and Service Bus health checks are provided by // AspNetCore.Diagnostics.HealthChecks // (a popular open source project) rather than by Microsoft. // https://github.com/Xabaril/AspNetCore.Diagnostics.HealthChecks builder.Services.AddHealthChecks() .AddAzureBlobStorage(options => { // AddAzureBlobStorage will use the BlobServiceClient registered in DI. // We just need to specify the container name. options.ContainerName = builder.Configuration.GetRequiredConfigurationValue("App:StorageAccount:Container"); }) .AddAzureServiceBusQueue( builder.Configuration.GetRequiredConfigurationValue("App:ServiceBus:Host"), builder.Configuration.GetRequiredConfigurationValue("App:ServiceBus:RenderRequestQueueName"), azureCredentials); // Further app configuration omitted for brevity. app.MapHealthChecks("/health");
Azure リソースを構成します。 アプリの正常性チェック URL を使用して、ライブ性と準備状態を確認するように Azure リソースを構成します。 たとえば、参照実装では、Bicep を使用して正常性チェック URL を構成し、Azure リソースのライブネスとレディネスを確認します。 ライブネス プローブは、最初の遅延が 2 秒後に 10 秒ごとに
/health
エンドポイントにヒットします。probes: [ { type: 'liveness' httpGet: { path: '/health' port: 8080 } initialDelaySeconds: 2 periodSeconds: 10 } ]
再試行パターンを実装する
再試行パターンを使用すると、アプリケーションは一時的な障害から復旧できます。 再試行パターンは、信頼性の高い Web アプリ パターンの中核であるため、多くの Web アプリに使用されています。 再試行パターンを、Web アプリから抽出した分離されたサービスによって発行されたメッセージング システムと要求に要求に適用します。 再試行パターンを実装するには、次の推奨事項に従います。
再試行オプションを構成します。 メッセージ キューと統合する場合は、適切な再試行設定でキューとの対話を担当するクライアントを構成してください。 再試行の最大数、再試行間の遅延、最大遅延などのパラメーターを指定します。
エクスポネンシャル バックオフを使用します。 再試行のための指数バックオフ戦略を実装します。 この戦略では、各再試行の間の時間を指数関数的に増やす必要があります。これにより、高い障害率の期間中にシステムの負荷を軽減できます。
SDK の再試行機能を使用します。 Service Bus や Blob Storage などの特殊な SDK を持つサービスの場合は、組み込みの再試行メカニズムを使用します。 組み込みの再試行メカニズムは、サービスの一般的なユース ケースに合わせて最適化されており、必要な構成を減らすことで再試行をより効果的に処理できます。 たとえば、参照実装では、Service Bus SDK の組み込みの再試行機能 (
ServiceBusClient
とServiceBusRetryOptions
) が使用されます。ServiceBusRetryOptions
オブジェクトは、MessageBusOptions
から設定をフェッチして、MaxRetries
、Delay
、MaxDelay
、TryTimeout
などの再試行設定を構成します。// ServiceBusClient is thread-safe and can be reused for the lifetime // of the application. services.AddSingleton(sp => { var options = sp.GetRequiredService<IOptions<MessageBusOptions>>().Value; var clientOptions = new ServiceBusClientOptions { RetryOptions = new ServiceBusRetryOptions { Mode = ServiceBusRetryMode.Exponential, MaxRetries = options.MaxRetries, Delay = TimeSpan.FromSeconds(options.BaseDelaySecondsBetweenRetries), MaxDelay = TimeSpan.FromSeconds(options.MaxDelaySeconds), TryTimeout = TimeSpan.FromSeconds(options.TryTimeoutSeconds) } }; return new ServiceBusClient(options.Host, azureCredential ?? new DefaultAzureCredential(), clientOptions); });
HTTP クライアント用の標準の回復性ライブラリを採用します。 HTTP 通信の場合は、Polly や
Microsoft.Extensions.Http.Resilience
などの標準の回復性ライブラリを統合します。 これらのライブラリは、外部 Web サービスとの通信を管理するために不可欠な包括的な再試行メカニズムを提供します。メッセージのロックを処理します。 メッセージ ベースのシステムの場合は、使用可能な場合は "ピーク ロック" モードの使用など、データ損失のない再試行をサポートするメッセージ処理戦略を実装します。 失敗したメッセージが効果的に再試行され、失敗が繰り返し発生した後は配信不能キューに移動されるようにします。
分散トレースを実装する
アプリケーションのサービス指向が高まり、コンポーネントの分離が進むと、サービス間の実行フローを監視することが重要になります。 最新の Web アプリ パターンでは、Application Insights と Azure Monitor を使用して、分散トレースをサポートする OpenTelemetry API を介してアプリケーションの正常性とパフォーマンスを可視化します。
分散トレースは、複数のサービスを走査するユーザー要求を追跡します。 要求が受信されると、トレース識別子でタグ付けされます。これは、HTTP ヘッダーを介して他のコンポーネントに渡され、依存関係の呼び出し中に Service Bus プロパティに渡されます。 トレースとログには、特定のコンポーネントとその親アクティビティに対応するトレース識別子とアクティビティ識別子 (またはスパン識別子) の両方が含まれます。 Application Insights などの監視ツールでは、この情報を使用して、分散アプリケーションの監視に不可欠なさまざまなサービスにわたるアクティビティとログのツリーを表示します。
OpenTelemetry ライブラリをインストールします。 インストルメンテーション ライブラリを使用して、一般的なコンポーネントからのトレースとメトリックを有効にします。 必要に応じて、
System.Diagnostics.ActivitySource
およびSystem.Diagnostics.Activity
を使用してカスタム インストルメンテーションを追加します。 エクスポーター ライブラリを使用して OpenTelemetry 診断をリッスンし、永続的なストアに記録します。 既存のエクスポーターを使用するか、System.Diagnostics.ActivityListener
を使用して独自のエクスポーターを作成します。OpenTelemetry を設定します。 OpenTelemetry の Azure Monitor ディストリビューション (
Azure.Monitor.OpenTelemetry.AspNetCore
) を使用します。 診断を Application Insights にエクスポートし、.NET ランタイムと ASP.NET Core からの一般的なメトリック、トレース、ログ、例外の組み込みインストルメンテーションが含まれていることを確認します。 SQL、Redis、Azure SDK クライアント用の他の OpenTelemetry インストルメンテーション パッケージを含めます。監視と分析を実行します。 トレースを構成したら、ログ、トレース、メトリック、例外がキャプチャされ、Application Insights に送信されることを確認します。 トレース、アクティビティ、および親アクティビティの識別子が含まれていることを確認します。 これらの識別子を使用すると、Application Insights は HTTP と Service Bus の境界を越えてエンドツーエンドのトレース可視性を提供できます。 このセットアップを使用して、サービス全体のアプリケーションのアクティビティを監視および分析します。
モダン Web アプリのサンプルでは、OpenTelemetry の Azure Monitor ディストリビューション (Azure.Monitor.OpenTelemetry.AspNetCore
) を使用しています。 SQL、Redis、Azure SDK クライアントには、より多くのインストルメンテーション パッケージが使用されます。 OpenTelemetry は、Modern Web App サンプル チケット レンダリング サービスで次のように構成されます。
builder.Logging.AddOpenTelemetry(o =>
{
o.IncludeFormattedMessage = true;
o.IncludeScopes = true;
});
builder.Services.AddOpenTelemetry()
.UseAzureMonitor(o => o.ConnectionString = appInsightsConnectionString)
.WithMetrics(metrics =>
{
metrics.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddRuntimeInstrumentation();
})
.WithTracing(tracing =>
{
tracing.AddAspNetCoreInstrumentation()
.AddHttpClientInstrumentation()
.AddSource("Azure.*");
});
builder.Logging.AddOpenTelemetry
メソッドは、OpenTelemetry を介してすべてのログ記録をルーティングし、アプリケーション全体で一貫したトレースとログ記録を確保します。 OpenTelemetry サービスは builder.Services.AddOpenTelemetry
に登録されているため、アプリケーションは診断を収集してエクスポートするように設定され、その後、 UseAzureMonitor
経由で Application Insights に送信されます。 さらに、Service Bus や HTTP クライアントなどのコンポーネントのクライアント インストルメンテーションは、 WithMetrics
と WithTracing
によって構成されます。これにより、既存のクライアントの使用状況を変更することなく、メトリックとトレースの自動収集が可能になります。 構成の更新のみが必要です。
構成ガイダンス
以降のセクションでは、構成の更新の実装に関するガイダンスを提供します。 各セクションは、Well-Architected フレームワークの 1 つ以上の柱と一致します。
構成 | 信頼性 (RE) | セキュリティ (SE) | コスト最適化 (CO) | オペレーショナル エクセレンス (OE) | パフォーマンス効率 (PE) | 適切に設計されたフレームワークの原則のサポート |
---|---|---|---|---|---|---|
認証と承認を構成する | ✔ | ✔ |
SE:05 OE:10 |
|||
独立したオートスケールを実装する | ✔ | ✔ | ✔ |
RE:06 CO:12 PE:05 |
||
サービスのデプロイをコンテナー化する | ✔ | ✔ |
CO:13 PE:09 PE:03 |
認証と承認を構成する
Web アプリに追加するすべての新しい Azure サービス (ワークロード ID) で認証と承認を構成するには、次の推奨事項に従います。
新しいサービスごとにマネージド ID を使用します。 個々の独立したサービスに固有の ID を設定し、サービス間認証にマネージド ID を使用する必要があります。 マネージド ID を使用すると、コードで資格情報を管理する必要がなくなり、資格情報の漏洩のリスクが軽減されます。 これらは、コードや構成ファイルに接続文字列などの機密情報を配置しないようにするのに役立ちます。
新しい各サービスに最小限の特権を付与します。 新しいサービス ID ごとに必要なアクセス許可のみを割り当てます。 たとえば、ID がコンテナー レジストリにのみプッシュする必要がある場合は、プルアクセス許可を付与しないでください。 これらのアクセス許可を定期的に確認し、必要に応じて調整します。 デプロイやアプリケーションなど、ロールごとに異なる ID を使用します。 これにより、1 つの ID が侵害された場合の潜在的な損害が制限されます。
コードとしてのインフラストラクチャ (IaC) を導入します。 Bicep または同様の IaC ツールを使用して、クラウド リソースを定義および管理します。 IaC を使用すると、デプロイにおけるセキュリティ構成の一貫した適用が保証され、インフラストラクチャのセットアップをバージョン管理できます。
ユーザー (ユーザー ID) の認証と承認を構成するには、次の推奨事項に従います。
ユーザーに最小限の特権を付与します。 サービスと同様に、タスクを実行するために必要なアクセス許可のみがユーザーに付与されていることを確認します。 これらのアクセス許可を定期的に確認して調整します。
定期的なセキュリティ監査を実施します。 セキュリティ設定を定期的に見直し、監査します。 構成の誤りや不要なアクセス許可を探し、直ちに修正します。
参照実装では、IaC を使用して、追加されたサービスにマネージド ID を割り当て、各 ID に特定のロールを割り当てます。 デプロイ (containerRegistryPushRoleId
)、アプリケーション所有者 (containerRegistryPushRoleId
)、Container Apps アプリケーション (containerRegistryPullRoleId
) のロールとアクセス許可を定義します。 コードの例を次に示します。
roleAssignments: \[
{
principalId: deploymentSettings.principalId
principalType: deploymentSettings.principalType
roleDefinitionIdOrName: containerRegistryPushRoleId
}
{
principalId: ownerManagedIdentity.outputs.principal_id
principalType: 'ServicePrincipal'
roleDefinitionIdOrName: containerRegistryPushRoleId
}
{
principalId: appManagedIdentity.outputs.principal_id
principalType: 'ServicePrincipal'
roleDefinitionIdOrName: containerRegistryPullRoleId
}
\]
参照実装では、デプロイ時に新しい Container Apps ID としてマネージド ID が割り当てられます。
module renderingServiceContainerApp 'br/public:avm/res/app/container-app:0.1.0' = {
name: 'application-rendering-service-container-app'
scope: resourceGroup()
params: {
// Other parameters omitted for brevity.
managedIdentities: {
userAssignedResourceIds: [
managedIdentity.id
]
}
}
}
独立したオートスケールを構成する
モダン Web アプリ パターンは、モノリシック アーキテクチャの分割が開始し、サービスの分離を導入します。 Web アプリ アーキテクチャを分離すると、分離されたサービスを個別にスケーリングできます。 Web アプリ全体ではなく、独立した Web アプリ サービスをサポートするように Azure サービスをスケーリングすることで、需要を満たしながらスケーリング コストが最適化されます。 コンテナーをオートスケールするには、次の推奨事項に従います。
ステートレス サービスを使用します。 サービスがステートレスであることを確認します。 .NET アプリケーションにインプロセス セッション状態が含まれている場合は、Redis などの分散キャッシュまたは SQL Server などのデータベースに外部化します。
オートスケール ルールを構成します。 サービスに対して最もコスト効率の高い制御を提供するオートスケール構成を使用します。 コンテナー化されたサービスの場合、Kubernetes Event-Driven Autoscaler (KEDA) などのイベント ベースのスケーリングでは、多くの場合、イベント メトリックに基づいてスケーリングできる細かい制御が提供されます。 Container Apps と AKS では KEDA がサポートされます。 App Service など、KEDA をサポートしていないサービスの場合は、プラットフォームが提供する自動スケール機能を使用します。 多くの場合、これらの機能には、メトリックベースのルールまたは HTTP トラフィックに基づくスケーリングが含まれます。
最小レプリカを構成します。 コールド スタートを防ぐには、少なくとも 1 つのレプリカを維持するようにオートスケール設定を構成します。 コールド スタートは、サービスを停止状態から初期化すると発生します。多くの場合、遅延応答が作成されます。 コストを最小限に抑えることが優先され、コールド スタートの遅延を許容できる場合は、自動スケールを構成するときに最小レプリカ数を 0 に設定します。
クールダウン期間を構成します。 適切なクールダウン期間を適用して、スケーリング イベント間の遅延を導入します。 目標は、一時的な負荷の急増によってトリガーされる 過剰なスケーリング アクティビティを防ぐこと です。
キューベースのスケーリングを構成します。 アプリケーションで Service Bus などのメッセージ キューを使用する場合は、要求メッセージを含むキューの長さに基づいてスケーリングするように自動スケール設定を構成します。 スケーラーは、キュー内の N 個のメッセージごとにサービスの 1 つのレプリカを維持することを目的としています (切り上げられます)。
たとえば、参照実装では、 Service Bus KEDA スケーラー を使用して、キューの長さに基づいてコンテナー アプリをスケーリングします。
service-bus-queue-length-rule
は、指定された Service Bus キューの長さに基づいてサービスをスケーリングします。
messageCount
パラメーターが 10 に設定されているため、スケーラーはキュー内の 10 個のメッセージごとに 1 つのサービス レプリカを保持します。
scaleMaxReplicas
パラメーターと scaleMinReplicas
パラメーターは、サービスのレプリカの最大数と最小数を設定します。 Service Bus キューの接続文字列を含む queue-connection-string
シークレットは、Azure Key Vault から取得されます。 このシークレットは、Service Bus に対してスケーラーを認証するのに使用されます。
scaleRules: [
{
name: 'service-bus-queue-length-rule'
custom: {
type: 'azure-servicebus'
metadata: {
messageCount: '10'
namespace: renderRequestServiceBusNamespace
queueName: renderRequestServiceBusQueueName
}
auth: [
{
secretRef: 'render-request-queue-connection-string'
triggerParameter: 'connection'
}
]
}
}
]
scaleMaxReplicas: 5
scaleMinReplicas: 0
サービスのデプロイをコンテナー化する
コンテナー化されたデプロイでは、アプリに必要なすべての依存関係が軽量イメージにカプセル化され、幅広いホストに確実にデプロイできます。 デプロイをコンテナー化するには、次の推奨事項に従います。
ドメインの境界を特定します。 最初に、モノリシック アプリケーション内のドメイン境界を特定します。 これを行うと、アプリケーションのどの部分を個別のサービスに抽出できるかを判断するのに役立ちます。
Docker イメージを作成します。 .NET サービスの Docker イメージを作成するときは、 基本イメージを使用します。 これらのイメージには、.NET の実行に必要なパッケージの最小セットのみが含まれており、パッケージ サイズと攻撃対象領域の両方を最小限に抑えます。
マルチステージの Dockerfile を使用します。 マルチステージの Dockerfile を実装して、ランタイム コンテナー イメージからビルド時アセットを分離します。 この種類のファイルを使用すると、運用イメージを小さく安全に保つことができます。
非ルート ユーザーとして実行します。 .NET コンテナーを (ユーザー名または UID $APP_UID を使用して) 非ルート ユーザーとして実行し、最小限の特権の原則に合わせます。 これにより、侵害されたコンテナーの潜在的な影響が制限されます。
ポート 8080 でリッスンします。 非ルート ユーザーとしてコンテナーを実行する場合は、ポート 8080 でリッスンするようにアプリケーションを構成します。 これは、非ルート ユーザーの一般的な規則です。
依存関係をカプセル化します。 アプリのすべての依存関係が Docker コンテナー イメージにカプセル化されていることを確認します。 カプセル化を使用すると、アプリをさまざまなホストに確実にデプロイできます。
適切な基本イメージを選択します。 選択する基本イメージは、デプロイ環境によって異なります。 たとえば、Container Apps にデプロイする場合は、Linux Docker イメージを使用する必要があります。
たとえば、参照実装では、マルチステージのビルド プロセスを使用しています。 初期ステージでは、完全な SDK イメージ (mcr.microsoft.com/dotnet/sdk:8.0-jammy
) を使用して、アプリケーションをコンパイルしてビルドします。 最終的なランタイム イメージは、SDK とビルド成果物を除外した chiseled
基本イメージから作成されます。 サービスは非ルート ユーザー (USER $APP_UID
) として実行され、ポート 8080 を公開します。 アプリケーションが動作するために必要な依存関係は、プロジェクト ファイルをコピーしてパッケージを復元するコマンドによって証明されるように、Docker イメージに含まれています。 Linux ベースのイメージ (mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled
) を使用すると、デプロイに Linux コンテナーが必要な Container Apps との互換性が確保されます。
# Build in a separate stage to avoid copying the SDK into the final image.
FROM mcr.microsoft.com/dotnet/sdk:8.0-jammy AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
# Restore packages.
COPY ["Relecloud.TicketRenderer/Relecloud.TicketRenderer.csproj", "Relecloud.TicketRenderer/"]
COPY ["Relecloud.Messaging/Relecloud.Messaging.csproj", "Relecloud.Messaging/"]
COPY ["Relecloud.Models/Relecloud.Models.csproj", "Relecloud.Models/"]
RUN dotnet restore "./Relecloud.TicketRenderer/Relecloud.TicketRenderer.csproj"
# Build and publish.
COPY . .
WORKDIR "/src/Relecloud.TicketRenderer"
RUN dotnet publish "./Relecloud.TicketRenderer.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# Chiseled images contain only the minimal set of packages needed for .NET 8.0.
FROM mcr.microsoft.com/dotnet/aspnet:8.0-jammy-chiseled AS final
WORKDIR /app
EXPOSE 8080
# Copy the published app from the build stage.
COPY --from=build /app/publish .
# Run as nonroot user.
USER $APP_UID
ENTRYPOINT ["dotnet", "./Relecloud.TicketRenderer.dll"]
参照実装をデプロイする
.NET 向けモダン Web アプリ パターンの参照実装をデプロイします。 リポジトリに、開発と実稼働の両方のデプロイに関する手順が用意されています。 実装をデプロイした後、設計パターンをシミュレートして観察できます。
次の図は、参照実装のアーキテクチャを示しています。
このアーキテクチャの Visio ファイルをダウンロードします。