この記事では、モダン 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 つのリージョンには、1 つのリージョンに接続するハブ仮想ネットワークがある以外は同じサービスが必要です。 ハブアンドスポーク ネットワーク トポロジを導入して、ネットワーク ファイアウォールなどのリソースを一元化および共有します。 ハブ仮想ネットワーク経由でコンテナー リポジトリにアクセスします。 仮想マシンがある場合は、ハブ仮想ネットワークに Bastion ホストを追加して仮想マシンを安全に管理します (図 2 を参照)。
図 2. 2 つ目のリージョンを設定し、ハブアンドスポーク ネットワーク トポロジを採用したモダン Web アプリ パターン アーキテクチャ。
アーキテクチャを分離する
モダン Web アプリ パターンを実装するには、既存の Web アプリ アーキテクチャを分離する必要があります。 アーキテクチャを分離するには、モノリシック アプリケーションを、それぞれ特定の機能を担当する、より小規模で独立したサービスに分割する必要があります。 このプロセスでは、現在の Web アプリの評価、アーキテクチャの変更、最後にコンテナー プラットフォームへの Web アプリ コードの抽出が必要です。 目標は、分離されることで最もメリットのあるアプリケーション サービスを体系的に特定して抽出することです。 アーキテクチャを分離するには、次の推奨事項に従います。
サービスの境界を特定する ドメイン駆動型の設計原則を適用して、モノリシック アプリケーション内の境界付けられたコンテキストを識別します。 境界付けられた各コンテキストは論理境界を表しており、別個のサービスとする候補と考えられます。 個別のビジネス機能を表す、依存関係が少ないサービスは、分離の候補として適しています。
サービスの利点を評価します。 独立したスケーリングにより最大のメリットが得られるサービスに焦点を当てます。 たとえば、LOB アプリケーションの電子メール サービス プロバイダーなどの外部依存関係では、障害からの分離が必要になる場合があります。 頻繁に更新または変更が行われるサービスを検討してください。 これらのサービスを切り離すことで、独立したデプロイが可能になり、アプリケーションの他の部分に影響を与えるリスクが軽減されます。
技術的な実現可能性を評価します。 現在のアーキテクチャを調べて、分離プロセスに影響する可能性がある技術的な制約と依存関係を特定します。 サービス間でデータを管理および共有する方法を計画します。 分離されたサービスでそれぞれ独自のデータを管理し、サービスの境界を越えた直接データベース アクセスを最小限に抑える必要があります。
Azure サービスをデプロイします。 抽出する予定の Web アプリ サービスをサポートするために必要な Azure サービスを選択してデプロイします。 ガイダンスについては、次の「適切な Azure サービスを選択する」セクションを参照してください。
Web アプリ サービスを分離します。 システムの他の部分とやり取りするために、新しく抽出された Web アプリ サービスの明確なインターフェイスと API を定義します。 一貫性と整合性を確保しながら、各サービスが独自のデータを管理できるようにするデータ管理戦略を設計します。 この抽出プロセス中に使用する特定の実装戦略と設計パターンについては、「コードのガイダンス」セクションを参照してください。
分離されたサービス用に独立したストレージを使用します。 分離された各サービスには、バージョン管理とデプロイを容易にするために、独自のデータ ストアが必要です。 たとえば、参照実装では、電子メール サービスを Web アプリから分離し、サービスがデータベースにアクセスする必要がなくなります。 代わりに、サービスは Azure Service Bus メッセージを介して電子メール配信の状態を Web アプリに伝え、Web アプリはそのデータベースにメモを保存します。
分離されたサービスごとに個別のデプロイ パイプラインを実装します。 個別のデプロイ パイプラインを使用することで、各サービスを独自のペースで更新できます。 社内のチームや組織がそれぞれ異なるサービスを所有している場合、個別のデプロイ パイプラインを使用することで、各チームが独自のデプロイを制御できます。 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 における Container Registry の概要」のガイダンスが、決定に役立ちます。
コードのガイダンス
独立したサービスを正常に分離して抽出するには、ストラングラー フィグ パターン、キューベースの負荷平準化パターン、競合コンシューマー パターン、正常性エンドポイント監視パターン、再試行パターンの 4 種類の設計パターンで Web アプリ コードを更新する必要があります。
ストラングラー フィグ パターン: ストラングラー フィグ パターンは、分離されたサービスにモノリシック アプリケーションから段階的に機能を移行します。 このパターンをメイン Web アプリに実装し、エンドポイントに基づいてトラフィックを転送することで、独立したサービスに徐々に機能を移行します。
キューベースの負荷平準化パターン: キュー ベースの負荷平準化パターンは、キューをバッファーとして使用して、プロデューサーとコンシューマーの間のメッセージのフローを管理します。 分離されたサービスのプロデューサー部分にこのパターンを実装し、キューを使用してメッセージ フローを非同期的に管理します。
競合コンシューマー パターン: 競合コンシューマー パターンでは、分離されたサービスの複数のインスタンスが同じメッセージ キューから個別に読み取り、競い合ってメッセージを処理します。 分離されたサービスにこのパターンを実装し、複数のインスタンスにタスクを分散させます。
正常性エンドポイント監視パターン: 正常性エンドポイント監視パターンは、Web アプリのさまざまな部分の状態と正常性を監視するためのエンドポイントを公開します。 (4a) メイン Web アプリにこのパターンを実装します。 (4b) エンドポイントの正常性を追跡するために、分離されたサービスにも実装します。
再試行パターン: 再試行パターンは、断続的に失敗する可能性のある操作を再試行することによって、一時的なエラーを処理します。 (5a) メッセージ キューやプライベート エンドポイントの呼び出しなど、メイン Web アプリ内の他の Azure サービスへのすべての発信呼び出しにこのパターンを実装します。 (5b) プライベート エンドポイントの呼び出しにおける一時的な障害を処理するために、分離されたサービスにもこのパターンを実装します。
各設計パターンには、ウェルアーキテクト フレームワークの 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 |
ストラングラー フィグ パターンを実装する
Strangler fig パターンを使用して、モノリシック コードベースから新しい独立したサービスに機能を段階的に移行します。 既存のモノリシック コード ベースから新しいサービスを抽出し、Web アプリの重要な部分を徐々にモダン化します。 ストラングラー フィグ パターンを実装するには、次の推奨事項に従います。
ルーティング層を設定します。 モノリシック Web アプリのコード ベースで、エンドポイントに基づいてトラフィックを転送するルーティング層を実装します。 必要に応じてカスタム ルーティング ロジックを使用し、トラフィックを転送するための特定のビジネス ルールを処理します。 たとえば、モノリシック アプリに
/users
エンドポイントがあり、その機能を分離されたサービスに移動した場合、ルーティング層はすべての要求を新しいサービスに/users
します。機能のロールアウトの管理.分離されたサービスを段階的にロールアウトするために機能フラグ段階的なロールアウトを行います。 既存のモノリシック アプリ ルーティングでは、分離されたサービスが受信する要求の数を制御する必要があります。 ごく一部の要求から始めて、安定性とパフォーマンスを確認できたら、時間の経過と共に使用量を増やします。
たとえば、参照実装では、スタンドアロン サービスに電子メール配信機能を抽出します。これは、Contoso サポート ガイドを含む電子メールを送信する要求の大部分を処理するために段階的に導入できます。 新しいサービスがその信頼性とパフォーマンスを証明するにつれて、最終的には一連の電子メールの責任をモノリスから引き継ぎ、移行を完了できます。
ファサード サービスを使用します (必要な場合)。 ファサード サービスは、1 つの要求で複数のサービスとやり取りする必要がある場合や、基盤となるシステムの複雑さをクライアントから認識されないようにする場合に便利です。 ただし、分離されたサービスに公開 API がない場合は、ファサード サービスは必要ない可能性があります。
モノリシック Web アプリのコード ベースで、要求を適切なバックエンド (モノリスまたはマイクロサービス) にルーティングするファサード サービスを実装します。 新しい分離されたサービスで、ファサードを介してアクセスしたときに新しいサービスが個別に要求を処理できることを確認します。
キューベースの負荷平準化パターンを実装する
分離されたサービスのプロデューサー部分にQueue ベースの負荷平準化パターンを実装して、即時応答を必要としないタスクを非同期的に処理します。 このパターンでは、キューを使用してワークロードの分散を管理することで、システムの全体的な応答性とスケーラビリティが向上します。 これにより、分離されたサービスは一貫した速度で要求を処理できます。 このパターンを効果的に実装するには、次の推奨事項に従います。
非ブロッキング メッセージ キューを使用します。 分離されたサービスがキュー内のメッセージを処理するのを待機している間に、キューにメッセージを送信するプロセスが他のプロセスをブロックしないようにします。 分離されたサービス操作の結果がプロセスに必要な場合は、キューに登録された操作の完了を待機している間に状況を処理する別の方法を実装します。 たとえば、Spring Boot では、
StreamBridge
クラスを使用して、呼び出し元のスレッドをブロックせずに非同期的にキューにメッセージを発行できます (コード例参照)。private final StreamBridge streamBridge; public SupportGuideQueueSender(StreamBridge streamBridge) { this.streamBridge = streamBridge; } // Asynchronously publish a message without blocking the calling thread @Override public void send(String to, String guideUrl, Long requestId) { EmailRequest emailRequest = EmailRequest.newBuilder() .setRequestId(requestId) .setEmailAddress(to) .setUrlToManual(guideUrl) .build(); log.info("EmailRequest: {}", emailRequest); var message = emailRequest.toByteArray(); streamBridge.send(EMAIL_REQUEST_QUEUE, message); log.info("Message sent to the queue"); }
この Java の例では、
StreamBridge
を使用して非同期的にメッセージを送信します。 この方法により、分離されたサービスがキューに登録された要求を管理可能な速度で処理する間、メイン アプリケーションの応答性が維持され、他のタスクを同時に処理できるようになります。メッセージの再試行と削除を実装します。 キューに登録されたメッセージのうち、正常に処理できないものの処理を再試行するメカニズムを実装します。 エラーが解決しない場合は、それらのメッセージをキューから削除する必要があります。 たとえば、Service Bus には再試行機能と配信不能キュー機能が組み込まれています。
べき等メッセージ処理を構成します。 キューからのメッセージを処理するロジックは、メッセージが複数回処理される可能性がある場合に対応するため、べき等である必要があります。 Spring Boot では、
@StreamListener
または@KafkaListener
を一意のメッセージ識別子と共に使用して、重複する処理を防ぐことができます。 または、Spring Cloud Stream を使用して機能的なアプローチで動作するようにビジネス プロセスを整理することもできます。ここで、consume
メソッドは、繰り返し実行されたときに同じ結果を生成する方法で定義されます。 メッセージの使用方法の動作を管理する設定の詳細な一覧については Service Bus Spring Cloud Stream を参照してください。エクスペリエンスの変更を管理します。 非同期処理を使用すると、タスクがすぐに完了しない可能性があります。 適切な予想を立て、混乱を避けるために、タスクがまだ処理中である場合をユーザーが認識ができるようにする必要があります。 タスクが進行中であることを示すには、視覚的な手掛かりまたはメッセージを使用します。 メールやプッシュ通知など、タスクの完了時に通知を受け取るオプションをユーザーに提供します。
競合コンシューマー パターンを実装する
分離されたサービスに Competing Consumers パターン を実装して、メッセージ キューからの受信タスクを管理します。 このパターンでは、分離されたサービスの複数のインスタンスにタスクを分散する必要があります。 これらのサービスはキューからのメッセージを処理し、負荷分散を強化し、同時要求を処理するためのシステムの容量を増やします。 競合コンシューマー パターンは、次の場合に有効です。
- メッセージ処理のシーケンスが重要ではない。
- キューが、形式に誤りがあるメッセージの影響を受けない。
- 処理操作はべき等である (最初の適用以降、結果を変更することなく、複数回適用できる)。
競合コンシューマー パターンを実装するには、次の推奨事項に従います。
同時実行メッセージを処理します。 キューからメッセージを受信する場合は、システム設計に合わせてコンカレンシーを構成することで、システムが予測どおりにスケーリングされるようにします。 ロード テストの結果は、処理する同時メッセージの適切な数を決定するのに役立ち、1 つからシステムのパフォーマンスを測定できます。
プリフェッチを無効にします メッセージのプリフェッチを無効にして、コンシューマーは準備ができたときにのみメッセージをフェッチします。
信頼できるメッセージ処理モードを使用します。 処理に失敗したメッセージを自動的に再試行する PeekLock (またはそれと同等のもの) など、信頼性の高い処理モードを使用します。 このモードでは、削除優先の方法よりも信頼性が向上します。 あるワーカーがメッセージの処理に失敗した場合、そのメッセージが複数回処理されるとしても、別のワーカーがエラーなしでそのメッセージを処理できる必要があります。
エラー処理を実装します。 形式に誤りがあるメッセージまたは処理できないメッセージを、別個の配信不能キューにルーティングします。 この設計により、繰り返し処理を防ぐことができます。 たとえば、メッセージ処理中に例外をキャッチし、問題のあるメッセージを別のキューに移動できます。 Service Bus の場合、メッセージは、指定した数の配信試行の後、またはアプリケーションによる明示的な拒否の後に、配信不能キューに移動されます。
順序が正しくないメッセージを処理します。 誤った順序で到着したメッセージを処理するようにコンシューマーを設計します。 並列処理のコンシューマーが複数あるということは、誤った順序でメッセージが処理される可能性があることを意味します。
キューの長さに基づいてスケーリングします。 キューからのメッセージを使用するサービスは、キューの長さに基づいてオートスケールする必要があります。 スケールベースのオートスケールにより、受信メッセージのスパイクを効率的に処理できます。
メッセージ応答キューを使用します。 システムでメッセージ後処理の通知が必要な場合は、専用の応答キューまたは応答キューを設定します。 この設定により、操作メッセージングが通知プロセスから切り離されます。
ステートレス サービスを使用します。 ステートレス サービスを使用してキューからの要求を処理することを検討してください。 これにより、簡単なスケーリングと効率的なリソースの使用が可能になります。
ログを構成します。 メッセージ処理ワークフロー内にログ記録と特定の例外処理を統合します。 シリアル化エラーをキャプチャし、これらの問題のあるメッセージを配信不能メカニズムに転送することに重点を置きます。 これらのログは、トラブルシューティングに役立つ貴重な分析情報を提供します。
たとえば、参照実装では、Container Apps で実行されているステートレス サービスの競合コンシューマー パターンを使用して、Service Bus キューからの電子メール配信要求を処理します。
プロセッサはメッセージ処理の詳細をログに記録し、トラブルシューティングと監視に役立ちます。 逆シリアル化エラーをキャプチャし、プロセスのデバッグ時に必要な分析情報を提供します。 サービスはコンテナー レベルでスケーリングされるため、キューの長さに基づいてメッセージスパイクを効率的に処理できます (次のコードを参照)。
@Configuration
public class EmailProcessor {
private static final Logger log = LoggerFactory.getLogger(EmailProcessor.class);
@Bean
Function<byte[], byte[]> consume() {
return message -> {
log.info("New message received");
try {
EmailRequest emailRequest = EmailRequest.parseFrom(message);
log.info("EmailRequest: {}", emailRequest);
EmailResponse emailResponse = EmailResponse.newBuilder()
.setEmailAddress(emailRequest.getEmailAddress())
.setUrlToManual(emailRequest.getUrlToManual())
.setRequestId(emailRequest.getRequestId())
.setMessage("Email sent to " + emailRequest.getEmailAddress() + " with URL to manual " + emailRequest.getUrlToManual())
.setStatus(Status.SUCCESS)
.build();
return emailResponse.toByteArray();
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException("Error parsing email request message", e);
}
};
}
}
正常性エンドポイントの監視パターンを実装する
メイン アプリ コードおよび分離されたサービス コードに正常性エンドポイント監視パターンを実装すると、アプリケーション エンドポイントの正常性を追跡できます。 AKS や Container Apps などのオーケストレーターは、これらのエンドポイントをポーリングしてサービスの正常性を確認し、異常なインスタンスを再起動できます。 Spring Boot には、Spring Boot Actuator を使用した正常性チェックの組み込みサポートが用意されており、データベース、メッセージ ブローカー、ストレージ システムなどの主要な依存関係の正常性チェック エンドポイントを公開できます。 正常性エンドポイント監視パターンを実装するには、次の推奨事項に従います。
正常性チェックを実装します。 Spring Boot Actuator を使用して、正常性チェック エンドポイントを提供します。 Spring Boot Actuator は、組み込みの正常性インジケーターと、さまざまな依存関係のカスタム チェックを含むエンドポイント
/actuator/health
を公開します。 正常性エンドポイントを有効にするには、pom.xml
またはbuild.gradle
ファイルにspring-boot-starter-actuator
依存関係を追加します。<!-- Add Spring Boot Actuator dependency --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
参照実装に示すように、
application.properties
で正常性エンドポイントを構成します。txt management.endpoints.web.exposure.include=metrics,health,info,retry,retryevents
依存関係を検証します。 Spring Boot Actuator には、データベース、メッセージ ブローカー (RabbitMQ または Kafka)、ストレージ サービスなどのさまざまな依存関係の正常性インジケーターが含まれています。 Azure Blob Storage や Service Bus などの Azure サービスの可用性を検証するには、これらのサービスの正常性インジケーターを提供する Azure Spring Apps や Micrometer 統合などのコミュニティ プラグインを使用します。 カスタム チェックが必要な場合は、カスタム
HealthIndicator
Bean を作成して実装できます。import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.stereotype.Component; @Component public class CustomAzureServiceBusHealthIndicator implements HealthIndicator { @Override public Health health() { // Implement your health check logic here (e.g., ping Service Bus) boolean isServiceBusHealthy = checkServiceBusHealth(); return isServiceBusHealthy ? Health.up().build() : Health.down().build(); } private boolean checkServiceBusHealth() { // Implement health check logic (pinging or connecting to the service) return true; // Placeholder, implement actual logic } }
Azure リソースを構成します。 アプリの正常性チェック URL を使用して、ライブ性と準備状態を確認するように Azure リソースを構成します。 たとえば、Terraform を使用して正常性チェック URL を使用して、Container Apps にデプロイされたアプリのライブ性と準備状況を確認できます。 詳細については、「 Health probes in Container Apps」を参照してください。
再試行パターンを実装する
再試行パターンを使用すると、アプリケーションは一時的な障害から復旧できます。 再試行パターンは、信頼性の高い Web アプリ パターンの中核であるため、多くの Web アプリに使用されています。 メッセージング システムに対する要求と、Web アプリから抽出した分離されたサービスによって発行された要求に、再試行パターンを適用します。 再試行パターンを実装するには、次の推奨事項に従います。
再試行オプションを構成します。 メッセージ キューと統合する場合は、適切な再試行設定でキューとのやり取りに責任を負うクライアントを構成してください。 再試行の最大回数、再試行の間の遅延、最大遅延などのパラメーターを指定します。
エクスポネンシャル バックオフを使用します。 再試行のための指数バックオフ戦略を実装します。 これは、個々の再試行の間隔を指数関数的に長くすると、障害発生率が高い期間中にシステムの負荷を軽減するのに役立つことを意味します。
SDK の再試行機能を使用します。 Service Bus や Blob Storage などの特殊な SDK を使用するサービスの場合は、組み込みの再試行メカニズムを使用します。 組み込みの再試行メカニズムは、サービスの一般的なユース ケースに合わせて最適化されており、ユーザー側で必要となる構成を減らすことで、より効果的に再試行を処理できます。
HTTP クライアント用の標準の回復性ライブラリを採用します。 HTTP クライアントの場合は、Spring の RestTemplate または WebClient と共に Resilience4* を使用して、HTTP 通信での再試行を処理できます。 Spring の RestTemplate を Resilience4j の再試行ロジックでラップして、一時的な HTTP エラーを効果的に処理できます。
メッセージのロックを処理します。 メッセージベースのシステムの場合は、ピークロック モード (使用可能な場合) の使用など、データ損失のない再試行をサポートするメッセージ処理戦略を実装します。 失敗したメッセージが効果的に再試行され、失敗が繰り返し発生した後は配信不能キューに移動されるようにします。
構成ガイダンス
以降のセクションでは、構成の更新の実装に関するガイダンスを提供します。 各セクションは、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 または Terraform などの同様の IaC ツールを使用して、クラウド リソースを定義および管理します。 IaC を使用すると、デプロイにおけるセキュリティ構成の一貫した適用が保証され、インフラストラクチャのセットアップをバージョン管理できます。
ユーザー (ユーザー ID) の認証と承認を構成するには、次の推奨事項に従います。
ユーザーに最小限の特権を付与します。 サービスと同様に、タスクを実行するために必要なアクセス許可のみをユーザーに付与します。 これらのアクセス許可を定期的に確認して調整します。
定期的なセキュリティ監査を実施します。 セキュリティ設定を定期的に見直し、監査します。 構成の誤りや不要なアクセス許可を探し、直ちに修正します。
参照実装では、IaC を使用して、追加されたサービスにマネージド ID を割り当て、各 ID に特定のロールを割り当てます。 Container Registry のプッシュとプルのロールを定義することで、デプロイのロールとアクセス許可を定義します (次のコードを参照)。
resource "azurerm_role_assignment" "container_app_acr_pull" {
principal_id = var.aca_identity_principal_id
role_definition_name = "AcrPull"
scope = azurerm_container_registry.acr.id
}
resource "azurerm_user_assigned_identity" "container_registry_user_assigned_identity" {
name = "ContainerRegistryUserAssignedIdentity"
resource_group_name = var.resource_group
location = var.location
}
resource "azurerm_role_assignment" "container_registry_user_assigned_identity_acr_pull" {
scope = azurerm_container_registry.acr.id
role_definition_name = "AcrPull"
principal_id = azurerm_user_assigned_identity.container_registry_user_assigned_identity.principal_id
}
# For demo purposes, allow current user access to the container registry
# Note: when running as a service principal, this is also needed
resource "azurerm_role_assignment" "acr_contributor_user_role_assignement" {
scope = azurerm_container_registry.acr.id
role_definition_name = "Contributor"
principal_id = data.azuread_client_config.current.object_id
}
独立したオートスケールを構成する
モダン Web アプリ パターンは、モノリシック アーキテクチャの分割が開始し、サービスの分離を導入します。 Web アプリ アーキテクチャを分離すると、分離されたサービスを個別にスケーリングできます。 Web アプリ全体ではなく、独立した Web アプリ サービスをサポートするように Azure サービスをスケーリングすることで、需要を満たしながらスケーリング コストが最適化されます。 コンテナーをオートスケールするには、次の推奨事項に従います。
ステートレス サービスを使用します。 サービスがステートレスであることを確認します。 Web アプリにインプロセス セッション状態が含まれている場合は、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 キューの長さに基づいてコンテナー アプリを自動的にスケーリングします。 service-bus-queue-length-rule
という名前のスケーリング 規則は、指定された Service Bus キュー内のメッセージ数に応じてサービス レプリカの数を調整します。 messageCount
パラメーターは 10 に設定されています。つまり、スケーラーはキュー内の 10 個のメッセージごとに 1 つのレプリカを追加します。 最大レプリカ (max_replicas
) は 10 に設定され、最小レプリカはオーバーライドされない限り暗黙的に 0 に設定されます。これにより、キューにメッセージがない場合にサービスをゼロにスケールダウンできます。 Service Bus キューの接続文字列は、Service Bus に対するスケーラーの認証に使用される azure-servicebus-connection-string
という名前のシークレットとして安全に Azure に格納されます。
max_replicas = 10
min_replicas = 1
custom_scale_rule {
name = "service-bus-queue-length-rule"
custom_rule_type = "azure-servicebus"
metadata = {
messageCount = 10
namespace = var.servicebus_namespace
queueName = var.email_request_queue_name
}
authentication {
secret_name = "azure-servicebus-connection-string"
trigger_parameter = "connection"
}
}
サービスのデプロイをコンテナー化する
コンテナー化とは、アプリが機能するためのすべての依存関係が、幅広いホストに確実にデプロイできる軽量イメージとしてカプセル化されることを意味します。 デプロイをコンテナー化するには、次の推奨事項に従います。
ドメインの境界を特定します。 まず、モノリシック アプリケーション内のドメイン境界を特定します。 これは、アプリケーションのどの部分を個別のサービスに抽出できるかを判断するのに役立ちます。
Docker イメージを作成します。 Java サービス用の Docker イメージを作成する場合は、公式の OpenJDK 基本イメージを使用します。 これらのイメージには、Java の実行に必要なパッケージの最小セットのみが含まれています。これにより、パッケージ サイズと攻撃対象領域の両方が最小限に抑えられます。
マルチステージの Dockerfile を使用します。 マルチステージ Dockerfile を使用して、ランタイム コンテナー イメージからビルド時アセットを分離します。 これは、実稼働イメージを小さく安全に保つのに役立ちます。 構成済みのビルド サーバーを使用し、jar ファイルをコンテナー イメージにコピーすることもできます。
非ルート ユーザーとして実行します。 (ユーザー名または UID、$APP_UID を使用して) 非ルート ユーザーとして Java コンテナーを実行し、最小限の特権の原則に合わせます。 これにより、侵害されたコンテナーによる潜在的な影響が制限されます。
ポート 8080 でリッスンします。 非ルート ユーザーとして実行するときはポート 8080 でリッスンするようにアプリケーションを構成します。 これは、非ルート ユーザーの一般的な規則です。
依存関係をカプセル化します。 アプリが機能するためのすべての依存関係が Docker コンテナー イメージにカプセル化されるようにします。 カプセル化により、アプリをさまざまなホストに確実にデプロイできます。
適切な基本イメージを選択します。 選択する基本イメージは、デプロイ環境によって異なります。 たとえば、Container Apps にデプロイする場合は、Linux Docker イメージを使用する必要があります。
このリファレンス実装では、Java アプリケーションをコンテナー化するための Docker ビルド プロセスを示します。 この Dockerfile では、必要な Java ランタイム環境を提供する OpenJDK 基本イメージ (mcr.microsoft.com/openjdk/jdk:17-ubuntu
) を使用したシングルステージ ビルドが使用されます。
Dockerfile には、次の手順が含まれています。
- ボリューム宣言: 一時ボリューム (
/tmp
) が定義されており、コンテナーのメイン ファイルシステムとは別の一時ファイル ストレージを使用できます。 - 成果物のコピー: アプリケーションの JAR ファイル (
email-processor.jar
) が、監視用の Application Insights エージェント (applicationinsights-agent.jar
) と共にコンテナーにコピーされます。 - エントリポイントの設定: コンテナーは、実行時にアプリケーションを監視するために
java -javaagent
を使用して、Application Insights エージェントを有効にしてアプリケーションを実行するように構成されます。
この Dockerfile は、Linux ベースのコンテナーをサポートする Container Apps などのデプロイ環境に適したランタイム依存関係のみを含めることで、イメージを無駄にし続けます。
# Use OpenJDK 17 base image on Ubuntu as the foundation
FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu
# Define a volume to allow temporary files to be stored separately from the container's main file system
VOLUME /tmp
# Copy the packaged JAR file into the container
COPY target/email-processor.jar app.jar
# Copy the Application Insights agent for monitoring
COPY target/agent/applicationinsights-agent.jar applicationinsights-agent.jar
# Set the entry point to run the application with the Application Insights agent
ENTRYPOINT ["java", "-javaagent:applicationinsights-agent.jar", "-jar", "/app.jar"]
参照実装をデプロイする
Modern Web App Pattern for Java の参照実装をデプロイ。 リポジトリに、開発と実稼働の両方のデプロイに関する手順が用意されています。 デプロイ後、設計パターンをシミュレートして観察できます。
図 3. 参照実装のアーキテクチャ。このアーキテクチャの Visio ファイルをダウンロードできます。