ブログ | NGINX

マイクロサービス アプリを構成するためのベスト プラクティス

NGINX-F5 水平黒タイプ RGB の一部
ハビエル・エヴァンス サムネイル
ハビエル・エヴァンス
2023年3月2日公開

12 要素アプリとして知られるガイドラインは、10 年以上前に初めて公開されました。 それ以来、ほぼすべての義務付けられたプラクティスが、Web アプリの作成と展開の事実上の標準方法になりました。 また、アプリの構成や展開方法が変化しても、これらのプラクティスは適用可能ですが、アプリの開発と展開のためのマイクロサービスパターンにこれらのプラクティスがどのように適用されるかを理解するには、追加のニュアンスが必要になる場合もあります。

このブログでは、要因 3 「環境に構成を保存する」に焦点を当てています。

  • 構成とは、デプロイメント環境 (Twelve-Factor App ではdeploysと呼びます) 間で異なるすべての要素です。
  • 構成はアプリのコードから厳密に分離する必要があります。そうしないと、デプロイ間でどのように変化するのでしょうか?
  • 構成データは環境変数に保存されます。

マイクロサービスに移行しても、これらのガイドラインを遵守することはできますが、必ずしも 12 要素アプリの文字通りの解釈に正確に一致するとは限りません。構成データを環境変数として提供するなどの一部のガイドラインは、問題なく引き継がれます。 その他の一般的なマイクロサービス プラクティスは、12 要素アプリの基本原則を尊重しながらも、その拡張版のようなものです。 この記事では、Factor 3 の観点から、マイクロサービスの構成管理の 3 つの中核概念について説明します。

マイクロサービスの主な用語と概念

マイクロサービスに Factor 3 を適応させる議論に入る前に、いくつかの重要な用語と概念を理解しておくと役立ちます。

  • モノリシック アプリ アーキテクチャ- アプリの機能をコンポーネント モジュールに分割し、すべてのモジュールを単一のコードベースに含める従来のアーキテクチャ モデル。
  • マイクロサービス アプリ アーキテクチャ– それぞれが適切に範囲指定された一連の操作 (認証、通知、支払い処理など) を実行する複数の小さなコンポーネントから大規模で複雑なアプリを構築するアーキテクチャ モデル。 「マイクロサービス」は、小さなコンポーネント自体の名前でもあります。 実際には、一部の「マイクロサービス」はかなり大きくなる可能性があります。
  • サービス– システム内の単一のアプリケーションまたはマイクロサービスの総称。
  • システム– このブログの文脈では、組織が提供する完全な機能を作成するために組み合わされるマイクロサービスとサポート インフラストラクチャの完全なセットです。
  • アーティファクト– テストおよびビルド パイプラインによって作成されたオブジェクト。 アプリのコードを含む Docker イメージなど、さまざまな形式をとることができます。
  • デプロイメント– ステージング、統合、本番などの環境で実行されるアーティファクトの実行中の「インスタンス」。

マイクロサービスとモノリス

モノリシック アプリケーションでは、組織内のすべてのチームが同じアプリケーションと周囲のインフラストラクチャで作業します。 モノリシック アプリは一般的に、理論上はマイクロサービスよりもシンプルに見えますが、組織がマイクロサービスへの移行を決定する一般的な理由がいくつかあります。

  • チームの自律性- モノリス内の機能とサブシステムの所有権を定義するのは難しい場合があります。 組織が成長し成熟するにつれて、アプリの機能に対する責任はより多くのチームに分散されるようになります。 機能の一部を所有するチームがモノリス内のすべての関連サブシステムを所有するわけではないため、チーム間に依存関係が生じます。
  • 「爆発半径」の縮小– 大規模なアプリケーションを 1 つのユニットとして開発および展開すると、1 つのサブシステムでエラーが発生すると、アプリケーション全体の機能が低下する可能性があります。
  • 機能を個別にスケーリングする– モノリシック アプリ内の 1 つのモジュールだけに高い負荷がかかっている場合でも、システム障害やシステム低下を回避するために、組織はアプリ全体のインスタンスを多数展開する必要があります。

もちろん、マイクロサービスには、複雑さの増大、可観測性の低下、新しいセキュリティ モデルの必要性など、独自の課題が伴いますが、多くの組織、特に大規模または急成長中の組織は、顧客に提供するエクスペリエンスの信頼性が高く安定した基盤を構築するために、チームにさらなる自律性と柔軟性を与えるためには、これらの課題を乗り越える価値があると判断しています。

マイクロサービスアーキテクチャに必要な変更

モノリシック アプリをマイクロサービスにリファクタリングする場合、サービスは次の条件を満たす必要があります。

  • 予測可能な方法で構成の変更を受け入れる
  • 予測可能な方法でシステム全体に知らせる
  • 十分に文書化されている

モノリシック アプリの場合、プロセスにおけるわずかな矛盾や共有された前提への依存は重要ではありません。 しかし、多数の個別のマイクロサービスがある場合、それらの不整合や仮定によって多くの問題や混乱が生じる可能性があります。 マイクロサービスで行う必要のある変更の多くは技術的な必要性によるものですが、驚くほど多くの変更は、チームの内部での作業や他のチームとのやり取りに関係しています。

マイクロサービス アーキテクチャによる注目すべき組織的変化は次のとおりです。

  • 同じコードベースで共同作業するのではなく、チームは完全に分離され、各チームが 1 つ以上のサービスに対して全面的に責任を負います。 マイクロサービスの最も一般的な実装では、チームも「クロスファンクショナル」に再編成されます。つまり、他のチームへの依存を最小限に抑えながら、チームの目標を達成するために必要なすべての能力を備えたメンバーがチームに所属することになります。
  • プラットフォーム チーム (システム全体の健全性を担当) は、単一のアプリケーションを処理するのではなく、異なるチームが所有する複数のサービスを調整する必要があります。
  • ツール チームは、システムの安定性を維持しながら、さまざまなサービス所有者チームが目標を迅速に達成できるように、ツールとガイダンスを継続的に提供できる必要があります。

モノリシック アプリとマイクロサービス アプリの開発チームの構成を比較した図

サービス構成を明確に定義する

マイクロサービス アーキテクチャで要素 3 を拡張する必要がある領域の 1 つは、サービスの構成を含むサービスに関する特定の重要な情報を明確に定義し、他のサービスとの共有コンテキストを最小限に抑える必要があることです。 要因 3 はこれを直接的には扱いませんが、アプリケーションの機能に貢献する多数の個別のマイクロサービスでは特に重要です。

マイクロサービス アーキテクチャのサービス所有者として、チームはシステム全体で特定の役割を果たすサービスを所有します。 あなたのサービスとやり取りするサービスを持つ他のチームは、コードやドキュメントを読んだり貢献したりするために、あなたのサービスのリポジトリにアクセスする必要があります。

さらに、ソフトウェア開発の分野では、開発者の入社や退職だけでなく、社内の再編によってもチームのメンバー構成が頻繁に変わるのが残念な現実です。 また、特定のサービスに対する責任もチーム間で移管されることが多々あります。

これらの現実を考慮すると、コードベースとドキュメントは極めて明確で一貫性がある必要があり、これは次の方法で実現されます。

  • 各設定オプションの目的を明確に定義する
  • 設定値の期待される形式を明確に定義する
  • アプリケーションが構成値をどのように提供するかを明確に定義する
  • この情報を限られた数のファイルに記録する

多くのアプリケーション フレームワークは、必要な構成を定義する手段を提供します。 たとえば、Node.js アプリケーション用のconvict NPM パッケージは、単一のファイルに保存された完全な構成「スキーマ」を使用します。 これは、Node.js アプリの実行に必要なすべての構成の信頼できるソースとして機能します。

堅牢で簡単に検出できるスキーマにより、チームのメンバーと他のユーザーが自信を持ってサービスとやり取りできるようになります。

サービスに構成を提供する方法

アプリケーションに必要な構成値を明確に定義したら、デプロイされたマイクロサービス アプリケーションが構成を取得する 2 つの主要なソース間の重要な違いを尊重する必要もあります。

  • 構成設定を明示的に定義し、アプリケーションのソースコードに付随するデプロイメントスクリプト
  • 展開時に照会される外部ソース

デプロイメント スクリプトは、マイクロサービス アーキテクチャにおける一般的なコード編成パターンです。 これらは、12 要素アプリの最初の公開以来新しく追加されたものであるため、必然的に 12 要素アプリの拡張版となります。

パターン: アプリケーションの次に展開とインフラストラクチャの構成

近年では、アプリケーション コードと同じリポジトリに、 infrastructure (またはその名前のバリエーション) というフォルダーを置くことが一般的になっています。 通常、次のものが含まれます。

  • インフラストラクチャ・アズ・コード( Terraformが一般的な例)は、データベースなどのサービスが依存するインフラストラクチャを記述します。
  • Helm チャートKubernetes マニフェストなどのコンテナ オーケストレーション システムの構成
  • アプリケーションの展開に関連するその他のファイル

一見すると、これは、構成はコードから厳密に分離するという Factor 3 の規定に違反しているように見えるかもしれません。

実際、アプリケーションのに配置すると、インフラストラクチャ フォルダーはルールを実際に尊重すると同時に、マイクロサービス環境で作業するチームにとって重要な貴重なプロセス改善が可能になります。

このパターンの利点は次のとおりです。

  • サービスを所有するチームは、サービスの展開と、サービス固有のインフラストラクチャ (データベースなど) の展開も所有します。
  • 所有チームは、これらの要素に対する変更が開発プロセス (コードレビュー、CI) を通過することを確認できます。
  • チームは、外部のチームに作業を依頼することなく、サービスとサポート インフラストラクチャの展開方法を簡単に変更できます。

このパターンによってもたらされる利点は、個々のチームの自律性を強化すると同時に、展開および構成プロセスにさらなる厳密さが確実に適用されることに注目してください。

どのタイプの構成をどこに適用しますか?

実際には、インフラストラクチャ フォルダーに保存されているデプロイメント スクリプトを使用して、スクリプト自体に明示的に定義された構成と、デプロイ時に外部ソースから取得される構成の両方を管理します。そのためには、サービスのデプロイメント スクリプトを次のようにします。

  1. 特定の設定値を直接定義する
  2. デプロイメント スクリプトを実行するプロセスが外部ソースで必要な構成値を検索できる場所を定義します。

サービスの特定のデプロイメントに固有で、チームによって完全に制御される構成値は、インフラストラクチャ フォルダー内のファイルで直接指定できます。 例としては、アプリによって開始されたデータベース クエリの実行が許可される時間の長さの制限などが挙げられます。 この値は、デプロイメント ファイルを変更し、アプリケーションを再デプロイすることによって変更できます。

このスキームの利点の 1 つは、このような構成の変更は必ずコード レビューと自動テストを経由するため、誤った構成値によって停止が発生する可能性が低くなることです。 コードレビューを通過する値の変更と、特定の時点での構成キーの値は、ソース管理ツールの履歴で検出できます。

アプリケーションの実行に必要だがチームの制御下にない値は、アプリケーションがデプロイされる環境によって提供される必要があります。 例としては、サービスが依存する別のマイクロサービスに接続するホスト名とポートがあります。

そのサービスはチームによって所有されていないため、ポート番号などの値について想定することはできません。 このような値はいつでも変更される可能性があり、変更が手動で行われるか、何らかの自動プロセスによって行われるかに関係なく、変更されたときに何らかの中央構成ストレージに登録する必要があります。 その後、それらに依存するアプリケーションによってクエリを実行できるようになります。

これらのガイドラインは、マイクロサービス構成の 2 つのベスト プラクティスにまとめることができます。

マイクロサービス構成では、次のことは行わないでください。 ハードコードされた値または相互に合意された値に依存する

サービスが対話するサービスの場所など、デプロイメント スクリプトに特定の値をハードコードするのが最も簡単に思えるかもしれません。 実際には、そのようなタイプの構成をハードコーディングすることは、特にサービスの場所が頻繁に変更されるような現代の環境では危険です。 2 番目のサービスを所有していない場合は特に危険です。

スクリプト内でサービス場所を最新の状態に保つには自分自身の努力に頼ることができる、あるいはさらに悪いことに、場所が変わったときに所有チームが通知してくれると信頼できると考えるかもしれません。 ストレスの多い時期には注意力が散漫になることが多く、人間の厳しさに頼ると、システムが予告なしに機能しなくなるリスクが生じます。

マイクロサービス構成では次のことを行います。 サービスに「データベースはどこにありますか?」と尋ねさせます。

位置情報がハードコードされているかどうかに関係なく、アプリケーションは重要なインフラストラクチャが特定の場所にあることに依存してはなりません。 代わりに、新しくデプロイされたサービスは、システム内の共通ソースに「データベースはどこにありますか?」などの質問をし、その外部リソースの現在の場所に関する正確な回答を受け取る必要があります。 すべてのサービスがデプロイ時にシステムに登録されると、物事がはるかに簡単になります。

サービスを構成として利用可能にする

システムが「データベースはどこにあるのか?」や「依存している「サービス X」はどこにあるのか?」という質問に答える必要があるのと同様に、サービスは、他のサービスが、そのサービスがどのようにデプロイされているかを知らなくても簡単に見つけて通信できるように、システムに公開される必要があります。

マイクロサービス アーキテクチャにおける重要な構成方法は、サービス検出です。サービス検出とは、新しいサービス情報を登録し、他のサービスによってアクセスされたときにその情報を動的に更新することです。 マイクロサービスにサービス ディスカバリが必要な理由を説明した後、NGINX Open Source と Consul を使用してそれを実現する方法の例を見てみましょう。

サービスの複数のインスタンス (デプロイメント) を同時に実行することは一般的な方法です。 これにより、追加のトラフィックを処理できるだけでなく、新しいデプロイメントを起動してダウンタイムなしでサービスを更新することもできます。 NGINX などのツールはリバース プロキシおよびロード バランサとして機能し、受信トラフィックを処理して最も適切なインスタンスにルーティングします。 これは良いパターンです。サービスに依存するサービスは NGINX にのみリクエストを送信し、デプロイメントについて何も知る必要がないためです。

たとえば、リバース プロキシとして機能する NGINX の背後で実行されているmessengerというサービスのインスタンスが 1 つあるとします。

NGINX によってリバース プロキシされている「メッセンジャー」マイクロサービスの単一インスタンスの図

あなたのアプリが人気になったらどうしますか? これは良いニュースと考えられますが、その後、トラフィックの増加により、メッセンジャーインスタンスが CPU を大量に消費し、リクエストの処理に時間がかかっている一方で、データベースは正常に動作しているように見えることに気付きます。 これは、メッセンジャーサービスの別のインスタンスを展開することで問題を解決できる可能性があることを示しています。

メッセンジャーサービスの 2 番目のインスタンスをデプロイすると、NGINX はどのようにしてそれがライブであることを認識し、トラフィックの送信を開始するのでしょうか。 NGINX 構成に新しいインスタンスを手動で追加することも 1 つの方法ですが、サービスのスケールアップとスケールダウンが増えると、すぐに管理不能になります。

一般的な解決策は、 Consulのような高可用性サービス レジストリを使用してシステム内のサービスを追跡することです。 新しいサービス インスタンスは、デプロイされると Consul に登録されます。 Consul は、定期的にヘルスチェックを送信してインスタンスのステータスを監視します。 インスタンスがヘルスチェックに失敗すると、利用可能なサービスのリストから削除されます。

NGINX によってリバース プロキシされ、サービス検出に Consul を使用している「メッセンジャー」マイクロサービスの 2 つのインスタンスの図

NGINX は、さまざまな方法を使用して Consul などのレジストリを照会し、それに応じてルーティングを調整できます。 リバース プロキシまたはロード バランサーとして機能する場合、NGINX はトラフィックを「アップストリーム」サーバーにルーティングすることを思い出してください。 次の単純な構成を検討してください。


# 「messenger_service」というアップストリーム グループを定義します
upstream messenger_service {
server 172.18.0.7:4000;
server 172.18.0.8:4000;
}

server {
listen 80;

location /api {
# '/api' で始まるパスを持つ HTTP トラフィックを上記の 'upstream' ブロックにプロキシします。 デフォルトの負荷分散アルゴリズムである
# ラウンドロビンは、ブロック内の 2 つのサーバー間でリクエストを交互に送信します。
proxy_pass http://messenger_service;
proxy_set_header X-Forwarded-For $remote_addr;
}
}


デフォルトでは、NGINX はトラフィックをルーティングするために、各メッセンジャーインスタンスの正確な IP アドレスとポートを認識する必要があります。 この場合、172.18.0.7 と 172.18.0.8 の両方のポート 4000 になります。

ここで Consul とConsul テンプレートが登場します。 Consul テンプレートは NGINX と同じコンテナー内で実行され、サービス レジストリを管理する Consul クライアントと通信します。

レジストリ情報が変更されると、Consul テンプレートは正しい IP アドレスとポートを含む NGINX 構成ファイルの新しいバージョンを生成し、それを NGINX 構成ディレクトリに書き込み、NGINX に構成を再読み込みするように指示します。 NGINX が設定を再読み込みするときにダウンタイムは発生せず、再読み込みが完了するとすぐに新しいインスタンスがトラフィックの受信を開始します。

このような状況では、NGINX などのリバース プロキシを使用すると、他のサービスがアクセスする場所としてシステムに登録するための単一のタッチ ポイントが存在します。 チームは、他のサービスがサービス全体にアクセスできなくなることを心配することなく、個々のサービス インスタンスを柔軟に管理できます。

NGINXとマイクロサービスに実際に触れてみよう 3月

確かに、マイクロサービスは、サービスの技術的観点でも、他のチームとの関係の組織的観点でも、複雑さを増大させます。 マイクロサービス アーキテクチャの利点を享受するには、モノリス向けに設計されたプラクティスを批判的に再検討し、まったく異なる環境に適用した場合にも同じ利点が得られることを確認することが重要です。 このブログでは、12 要素アプリの要素 3 がマイクロサービス コンテキストでも依然として価値を提供しながら、具体的な適用方法を少し変更することでメリットが得られることを説明しました。

マイクロサービス アーキテクチャへの Twelve-Factor App の適用について詳しくは、「Microservices March 2023」(ブログで近日公開) のユニット 1 をご覧ください。 無料で登録する このトピックに関するウェビナーとハンズオンラボにアクセスします。


「このブログ投稿には、入手できなくなった製品やサポートされなくなった製品が参照されている場合があります。 利用可能な F5 NGINX 製品およびソリューションに関する最新情報については、 NGINX 製品ファミリーをご覧ください。 NGINX は現在 F5 の一部です。 以前の NGINX.com リンクはすべて、F5.com の同様の NGINX コンテンツにリダイレクトされます。"