この記事では、モダン Web アプリ パターンを実装する方法について説明します。 モダン Web アプリ パターンは、クラウド内の Web アプリをモダン化し、サービス指向アーキテクチャを導入する方法を定義したものです。 モダン Web アプリ パターンは、Azure Well-Architected Framework の原則に沿った規範的なアーキテクチャ、コード、および構成のガイダンスを提供し実行可能な Web アプリ パターンに基づいて構築。
モダン Web アプリ パターンを使用する理由
モダン Web アプリ パターンは、需要の高い Web アプリ領域を最適化するのに役立ちます。 これらの領域を分離し、独立したスケーリングを可能にすることでコスト最適化を図るための、詳細なガイダンスを提供します。 このアプローチでは、重要なコンポーネントに専用リソースを割り当てることで、全体的なパフォーマンスを向上させることができます。 個別のサービスを分離すると、アプリの一部の速度低下により他の部分に影響が生じることを防ぎ、信頼性を向上させることができます。 分離により、個々のアプリ コンポーネントを個別にバージョン管理することもできます。
モダン Web アプリ パターンの実装方法
この記事には、モダン Web アプリ パターンを実装するための、アーキテクチャ、コード、構成のガイダンスが含まれています。 以下のリンクを使用すると、必要なガイダンスに移動できます。
- アーキテクチャ ガイダンス: Web アプリ コンポーネントをモジュール化し、適切なサービスとしてのプラットフォーム (PaaS) ソリューションを選択する方法について説明します。
- コードのガイダンス: 分離されたコンポーネントを最適化するための、ストラングラー フィグ、キューベースの負荷平準化、競合コンシューマー、正常性エンドポイント監視の 4 つの設計パターンを実装します。
- 構成のガイダンス: 分離されたコンポーネントの認証、承認、自動スケーリング、コンテナー化を構成します。
ヒント
モダン Web パターンの 参照実装 (サンプル アプリ) があります。 これは、モダン Web アプリの実装の最終状態を表します。 この記事で説明する、コード、アーキテクチャ、構成に対するすべての更新プログラムを取り入れた実稼働グレードの Web アプリです。 この参照実装をデプロイすることで、モダン Web アプリ パターンを実装するためのガイドとして使用できます。
アーキテクチャのガイダンス
モダン Web アプリ パターンは、信頼性の高い Web アプリ パターンに基づいています。 実装するには、いくつかの追加のアーキテクチャ コンポーネントが必要です。 メッセージ キュー、コンテナー プラットフォーム、分離されたサービス データ ストア、コンテナー レジストリが必要です (図 1 を参照)。
図 1. モダン Web アプリ パターンの重要なアーキテクチャ要素。
より高いサービス レベル目標 (SLO) では、Web アプリ アーキテクチャに 2 つ目のリージョンを追加できます。 2 つ目のリージョンでは、アクティブ/アクティブまたはアクティブ/パッシブの構成をサポートするために、2 つ目のリージョンにトラフィックをルーティングするようにロード バランサーを構成する必要があります。 ハブアンドスポーク ネットワーク トポロジを使用して、ネットワーク ファイアウォールなどのリソースを一元化および共有します。 ハブ仮想ネットワーク経由でコンテナー リポジトリにアクセスします。 仮想マシンがある場合は、ハブ仮想ネットワークに Bastion ホストを追加して仮想マシンを安全に管理します (図 2 を参照)。
図 2. 2 つ目のリージョンを設定し、ハブアンドスポーク ネットワーク トポロジを採用したモダン Web アプリ パターン アーキテクチャ。
アーキテクチャを分離する
モダン 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、Service Bus の 3 つのメッセージング サービスがあります。 既定の選択肢として Service Bus から開始し、Service Bus がニーズを満たしていない場合は、他の 2 つのオプションを使用します。
Service ユース ケース Service Bus エンタープライズ アプリケーションで価値の高いメッセージを信頼性が高く、順序付けして、場合によってはトランザクション配信を行う場合は、Service Bus を選択します。 Event Grid 多数の個別のイベントを効率的に処理する必要がある場合は、Event Grid を選択します。 Event Grid は、短い待機時間のパブリッシュ/サブスクライブ モデルで多数の小さな独立したイベント (リソース状態の変更など) をサブスクライバーにルーティングする必要があるイベント ドリブン アプリケーションに対してスケーラブルです。 Event Hubs テレメトリ、ログ、リアルタイム分析など、大量の高スループットのデータ インジェストのために Event Hubs を選択します。 Event Hubs は、一括データを継続的に取り込んで処理する必要があるストリーミング シナリオ向けに最適化されています。 コンテナー サービスを実装します。 コンテナー化するアプリケーション部分には、コンテナーをサポートするアプリケーション プラットフォームが必要です。 「Azure コンテナー サービスを選択する」のガイダンスが、決定に役立ちます。 Azure には、Azure Container Apps、Azure Kubernetes Service (AKS)、Azure アプリ Service の 3 つの主要なコンテナー サービスがあります。 既定の選択肢として Container Apps から開始し、Container Apps がニーズを満たしていない場合は、他の 2 つのオプションを使用します。
Service ユース ケース コンテナー アプリ イベント ドリブン アプリケーションでコンテナーを自動的にスケーリングおよび管理するサーバーレス プラットフォームが必要な場合は、Container Apps を選択します。 AKS Kubernetes の構成の詳細な制御や、スケーリング、ネットワーク、セキュリティの高度な機能が必要な場合は、AKS を選択します。 Web Apps for Containers 最も単純な PaaS エクスペリエンスを実現するために、App Service の Web App for Containers を選択します。 コンテナー リポジトリを実装します。 コンテナー ベースのコンピューティング サービスを使用する場合は、コンテナー イメージを格納するリポジトリが必要です。 Docker Hub などのパブリック コンテナー レジストリや、Azure Container Registry などのマネージド レジストリを使用できます。 Azure のコンテナー レジストリへの導入に関するガイダンス使用して、意思決定を支援します。
コードのガイダンス
独立したサービスを正常に分離して抽出するには、ストラングラー フィグ パターン、キューベースの負荷平準化パターン、競合コンシューマー パターン、正常性エンドポイント監視パターン、再試行パターンの 4 種類の設計パターンで Web アプリ コードを更新する必要があります。
ストラングラー フィグ パターン: ストラングラー フィグ パターンは、分離されたサービスにモノリシック アプリケーションから段階的に機能を移行します。 このパターンをメイン Web アプリに実装し、エンドポイントに基づいてトラフィックを転送することで、独立したサービスに徐々に機能を移行します。
キューベースの負荷平準化パターン: キュー ベースの負荷平準化パターンは、キューをバッファーとして使用して、プロデューサーとコンシューマーの間のメッセージのフローを管理します。 分離されたサービスのプロデューサー部分にこのパターンを実装し、キューを使用してメッセージ フローを非同期的に管理します。
競合コンシューマー パターン: 競合コンシューマー パターンでは、分離されたサービスの複数のインスタンスが同じメッセージ キューから個別に読み取り、競い合ってメッセージを処理します。 分離されたサービスにこのパターンを実装し、複数のインスタンスにタスクを分散させます。
正常性エンドポイント監視パターン: 正常性エンドポイント監視パターンは、Web アプリのさまざまな部分の状態と正常性を監視するためのエンドポイントを公開します。 (4a) メイン Web アプリにこのパターンを実装します。 (4b) エンドポイントの正常性を追跡するために、分離されたサービスにも実装します。
再試行パターン: 再試行パターンは、断続的に失敗する可能性のある操作を再試行することによって、一時的なエラーを処理します。 (5a) メッセージ キューやプライベート エンドポイントの呼び出しなど、メイン Web アプリ内の他の Azure サービスに対するすべての発信呼び出しに、このパターンを実装します。 (5b) プライベート エンドポイントの呼び出しにおける一時的な障害を処理するために、分離されたサービスにもこのパターンを実装します。
各設計パターンには、Well-Architected フレームワークの 1 つ以上の柱に沿った利点があります (次の表を参照してください)。
設計パターン | 実装の場所 | 信頼性 (RE) | セキュリティ (SE) | コスト最適化 (CO) | オペレーショナル エクセレンス (OE) | パフォーマンス効率 (PE) | 適切に設計されたフレームワークの原則のサポート |
---|---|---|---|---|---|---|---|
ストラングラー フィグ パターン | メイン Web アプリ | ✔ | ✔ | ✔ | RE:08 CO:07 CO:08 OE:06 OE:11 |
||
キューベースの負荷平準化パターン | 分離されたサービスのプロデューサー | ✔ | ✔ | ✔ | RE:06 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 |
ストラングラー フィグ パターンを実装する
ストラングラー フィグ パターンは、モノリシック コードベースから新しい独立したサービスに段階的に機能を移行する場合に使用します。 既存のモノリシック コード ベースから新しいサービスを抽出し、Web アプリの重要な部分を徐々にモダン化します。 ストラングラー フィグ パターンを実装するには、次の推奨事項に従います。
ルーティング層を設定します。 モノリシック Web アプリのコード ベースで、エンドポイントに基づいてトラフィックを転送するルーティング層を実装します。 必要に応じてカスタム ルーティング ロジックを使用し、トラフィックを転送するための特定のビジネス ルールを処理します。 たとえば、モノリシック アプリに
/users
エンドポイントがあり、その機能を分離されたサービスに移動した場合、ルーティング層はすべての要求を新しいサービスに/users
します。機能のロールアウトを管理します。 .NET Feature Management ライブラリを使用して機能フラグを実装し、段階的ロールアウトを使用して、分離されたサービスを段階的にロールアウトします。 既存のモノリシック アプリ ルーティングでは、分離されたサービスが受信する要求の数を制御する必要があります。 ごく一部の要求から始めて、安定性とパフォーマンスを確認できたら、時間の経過と共に使用量を増やします。 たとえば、参照実装では、チケット レンダリング機能をスタンドアロン サービスに抽出します。これは、チケット レンダリング要求の大部分を処理するために徐々に導入できます。 新しいサービスで信頼性とパフォーマンスが確認できたら、最終的にチケット レンダリング機能全体をモノリスから引き継ぎ、移行を完了することができます。
ファサード サービスを使用します (必要な場合)。 ファサード サービスは、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 には再試行機能と配信不能キュー機能が組み込まれています。
べき等メッセージ処理を構成します。 キューからのメッセージを処理するロジックは、メッセージが複数回処理される可能性がある場合に対応するため、べき等である必要があります。 たとえば、参照実装では、
AutoCompleteMessages = true
とReceiveMode = ServiceBusReceiveMode.PeekLock
でServiceBusClient.CreateProcessor
を使用して、メッセージが 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
オブジェクトで使用して、正常性チェック エンドポイントを公開しています。 このコードでは、AspNetCore.Diagnostics.HealthChecks
パッケージの一部であるAddAzureBlobStorage()
メソッドとAddAzureServiceBusQueue()
メソッドを使用して、キーの依存関係、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
エンドポイントをヒットする liveness probe。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
オブジェクトは、MaxRetries、Delay、MaxDelay、TryTimeout などの再試行設定を構成するための設定をMessageBusOptions
からフェッチします。// 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
}
\]
参照実装では、デプロイ時にマネージド ID を新しい Container Apps 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 イメージを作成する場合は、Chiseled 基本イメージを使用します。 これらのイメージには、.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 non-root user
USER $APP_UID
ENTRYPOINT ["dotnet", "./Relecloud.TicketRenderer.dll"]
参照実装をデプロイする
.NET 向けモダン Web アプリ パターンの参照実装をデプロイします。 リポジトリに、開発と実稼働の両方のデプロイに関する手順が用意されています。 デプロイ後、設計パターンをシミュレートして観察できます。
図 3. 参照実装のアーキテクチャ。このアーキテクチャの Visio ファイルをダウンロードできます。