このドキュメントは、マイクロサービスの設計、構築、デプロイに関する 4 つのパートからなるシリーズのパート 3 です。このシリーズでは、マイクロサービス アーキテクチャのさまざまな要素について説明します。このシリーズには、マイクロサービス アーキテクチャ パターンの利点と欠点、およびその適用方法に関する情報が含まれています。
- マイクロサービスの概要
- モノリスをマイクロサービスにリファクタリングする
- マイクロサービス設定におけるサービス間通信(このドキュメント)
- マイクロサービス アプリケーションの分散トレース
このシリーズは、モノリシック アプリケーションをマイクロサービス アプリケーションにリファクタリングするための移行を設計して実装するアプリケーション デベロッパーとアーキテクトを対象としています。
このドキュメントでは、マイクロサービスの同期 API と比較した非同期メッセージのトレードオフについて説明します。このドキュメントでは、モノリシック アプリケーションの分解について説明し、元のアプリケーションの同期リクエストを、新しいマイクロサービス ベースの設定内で非同期フローに変換する方法を示しています。この変換には、サービス間での分散トランザクションの実装も含まれます。
サンプル アプリケーション
このドキュメントでは、Online Boutique という既製の e コマース アプリケーションを使用します。このアプリケーションは、アイテムの閲覧、カートへの商品の追加、購入手続きなど、基本的な e コマースフローを実装しています。このアプリケーションはまた、ユーザーの選択に基づいて、おすすめと広告を表示します。
サービスの論理的な分離
このドキュメントでは、支払いサービスをアプリケーションの他の部分から分離します。元の Online Boutique アプリケーションのすべてのフローは、同期しています。リファクタリングされたアプリケーションでは、支払いプロセスは非同期フローに変換されます。したがって、購入リクエストを受信すると、直ちに処理するのではなく、「リクエストを受領しました」という確認メッセージをユーザーに表示します。バックグラウンドでは、支払いサービスに対する非同期リクエストがトリガーされ、支払いが処理されます。
支払いデータとロジックを新しいサービスに移動する前に、支払いデータとロジックをモノリスから分離します。支払いデータとロジックをモノリスで分離すると、支払いサービスの境界(ビジネス ロジックまたはデータ)が誤って取得されても、同じコードベースでコードをリファクタリングする方が簡単です。
このドキュメントに含まれるモノリシック アプリケーションのコンポーネントは、すでにモジュール化されているため、互いに分離しています。アプリケーションにより緊密な相互依存関係がある場合、ビジネス ロジックを分離して、クラスとモジュールを個別に作成する必要があります。また、データベースの依存関係をそれぞれのテーブルに分離して、個別のリポジトリ クラスを作成する必要もあります。データベースの依存関係を分離すると、スプリット テーブルの間に外部キー関係が存在する場合があります。ただし、サービスをモノリスから完全に分離した後は、これらの依存関係は存在しなくなり、サービスは事前定義された API コントラクトまたは RPC コントラクトを通じて排他的に通信します。
分散トランザクションと部分的な障害
サービスを分離してモノリスから切り離すと、元のモノリシック システムのローカル トランザクションが複数のサービスに分散されます。モノリス実装では、次の図に示す順序で購入手続きを行います。
図 1:モノリス実装の購入手続きシーケンス。
図 1 では、アプリケーションが注文書を受け取ると、購入手続きコントローラは支払いサービスおよび注文サービスを呼び出し、それぞれ支払いを処理して注文を保存します。いずれかのステップが失敗した場合、データベース トランザクションをロールバックできます。注文リクエストが注文テーブルに正常に保存されているものの、支払いに失敗するというシナリオで考えてみましょう。このシナリオでは、トランザクション全体がロールバックされ、エントリは注文テーブルから削除されます。
支払いを独自のサービスに分離すると、変更されたチェックアウト フローは次の図のようになります。
図 2.支払後の購入手続きシーケンスは、独自のサービスに分離されます。
図 2 では、トランザクションが複数のサービスとそれに対応するデータベースにまたがるため、分散トランザクションになります。注文リクエストを受信すると、購入手続きコントローラはローカル データベースに注文の詳細を保存し、他のサービスを呼び出して注文を完了します。支払いサービスなどのこうしたサービスでは、独自のローカル データベースを使用して注文の詳細を保存できます。
モノリシック アプリケーションでは、データベース システムによってローカル トランザクションがアトミックであることが保証されます。ただし、デフォルトでは、サービスごとに個別のデータベースを持つマイクロサービス ベースのシステムには、さまざまなデータベースにまたがるグローバル トランザクション コーディネーターがありません。トランザクションは一元的に調整されないため、支払い処理が失敗した場合、注文サービスに commit された変更はロールバックされません。したがって、システムは一貫性のない状態になります。
分散トランザクションの処理には、次のパターンがよく使用されます。
- 2 フェーズ commit プロトコル(2PC): コンセンサス プロトコル ファミリーの一部である 2PC は分散トランザクションの commit を調整し、アトミック性、整合性、独立性、耐久性(ACID)の保証を維持します。プロトコルは準備フェーズと commit フェーズに分割されます。トランザクションは、すべての参加者が投票した場合にのみ commit されます。参加者が合意に達しない場合、トランザクション全体がロールバックされます。
- Saga: Saga パターンは、分散トランザクションを構成する各マイクロサービス内で動作しているローカル トランザクションで構成されます。イベントは、すべての成功した、または失敗したオペレーションの終了時にトリガーされます。分散トランザクションに関連するすべてのマイクロサービスが、これらのイベントをサブスクライブします。次のマイクロサービスが成功イベントを受け取ると、マイクロサービスはオペレーションを実行します。障害が発生すると、前のマイクロサービスは、変更を元に戻すための代替アクションを完了します。Saga は、すべてのステップが完了したときに、すべてのオペレーションが成功するか、代替アクションですべての作業を元に戻すことを保証することで、システムの一貫したビューを提供します。
長期のトランザクションには Saga の使用をおすすめします。マイクロサービス ベースのアプリケーションでは、サービス間の呼び出しとサードパーティ システムとの通信が想定されます。そのため、結果整合性を設計することをおすすめします。回復可能なエラーを再試行し、最終的に復元不可能なエラーを修復する代替イベントを公開します。
Saga を実装するにはさまざまな方法があります。たとえば、Apache Airflow や Apache Camel などのタスクエンジンおよびワークフロー エンジンを使用できます。Kafka、RabbitMQ、ActiveMQ に基づくシステムを使用して、独自のイベント ハンドラを作成することもできます。
Online Boutique アプリケーションは、購入手続きサービスを使用して、支払い、配送、メール通知サービスをオーケストレートします。購入手続きサービスは、ビジネスと注文のワークフローも処理します。独自のワークフロー エンジンを構築する代わりに、Zeebe などのサードパーティ コンポーネントを使用することもできます。Zeebe は UI ベースのモデラーを提供します。アプリケーションの要件に基づき、マイクロサービス オーケストレーターについて選択肢を慎重に評価することをおすすめします。この選択は、マイクロサービスの実行とスケーリングにおいて非常に重要です。
リファクタリングされたアプリケーション
リファクタリングされたアプリケーションで分散トランザクションを有効にするために、購入手続きサービスが支払い、配送、メールサービス間の通信を処理します。一般的なビジネス プロセスモデルと表記法(BPMN)ワークフローでは、次のフローを使用します。
図 3.一般的なマイクロサービスで分散トランザクションを保証する際に役立つ注文ワークフロー。
上の図は次のワークフローを示しています。
- フロントエンド サービスは注文リクエストを受け取り、次の処理を行います。
- 注文商品をカートサービスに送信します。その後、カートサービスが注文の詳細(Redis)を保存します。
- 購入手続きページにリダイレクトされます。購入手続きサービスがカートサービスから注文を取得し、注文ステータスを
Pending
に設定して、お客様に支払いを求めます。 - ユーザーが支払いを行ったことを確認します。確認が完了すると、購入手続きサービスはメールサービスに対し、確認メールを生成してお客様に送信するよう指示します。
- その後、購入手続きサービスがリクエストを処理します。
- 支払いリクエストが成功すると、支払いサービスは注文ステータスを
Complete
に更新します。 - 支払いリクエストが失敗した場合、支払いサービスは代替トランザクションを開始します。
- 支払いリクエストがキャンセルされます。
- 注文手続きサービスが、注文ステータスを
Failed
に変更します。
- 支払いサービスを利用できない場合、リクエストは N 秒後にタイムアウトし、購入手続きサービスが代替トランザクションを開始します。
- 注文手続きサービスが、注文ステータスを
Failed
に変更します。
- 支払いリクエストが成功すると、支払いサービスは注文ステータスを
目標
- モノリシックな Online Boutique アプリケーションを Google Kubernetes Engine(GKE)にデプロイする。
- モノリシックな購入手続きフローを検証する。
- リファクタリングされたモノリシック アプリケーションのマイクロサービス バージョンをデプロイする。
- 新しい購入手続きのフローが機能することを確認する。
- 障害が発生した場合に分散トランザクションと代替アクションが機能することを確認する。
費用
このドキュメントでは、Google Cloud の次の課金対象のコンポーネントを使用します。
料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。
このドキュメントの完了後に作成したリソースを削除すれば、今後課金は発生しません。詳細については、クリーンアップをご覧ください。
始める前に
-
In the Google Cloud console, on the project selector page, select or create a Google Cloud project.
-
Make sure that billing is enabled for your Google Cloud project.
-
In the Google Cloud console, activate Cloud Shell.
At the bottom of the Google Cloud console, a Cloud Shell session starts and displays a command-line prompt. Cloud Shell is a shell environment with the Google Cloud CLI already installed and with values already set for your current project. It can take a few seconds for the session to initialize.
Compute Engine、Google Kubernetes Engine、Cloud SQL、Artifact Analysis、Container Registry の API を有効にします。
gcloud services enable \ compute.googleapis.com \ sql-component.googleapis.com \ servicenetworking.googleapis.com\ container.googleapis.com \ containeranalysis.googleapis.com \ containerregistry.googleapis.com \ sqladmin.googleapis.com
次の環境変数をエクスポートします。
export PROJECT=$(gcloud config get-value project) export CLUSTER=$PROJECT-gke export REGION="us-central1"
e コマース モノリスをデプロイする
このセクションでは、モノリシックな Online Boutique アプリケーションを GKE クラスタにデプロイします。このアプリケーションは、リレーショナル データベースとして Cloud SQL を使用します。次の図は、モノリシック アプリケーション アーキテクチャを示しています。
図 4.クライアントは、GKE クラスタ内のアプリケーションに接続し、アプリケーションは Cloud SQL データベースに接続します。
アプリケーションをデプロイするには、次の手順を行います。
GitHub リポジトリのクローンを作成します。
git clone https://github.com/GoogleCloudPlatform/monolith-to-microservices-example
Terraform 変数マニフェスト ファイル内の
PROJECT_ID
プレースホルダを置き換えます。cd monolith-to-microservices-example/setup && \ sed -i -e "s/\[PROJECT_ID\]/$PROJECT/g" terraform.tfvars
Terraform スクリプトを実行してインフラストラクチャの設定を完了し、インフラストラクチャをデプロイします。Terraform の詳細については、Google Cloud での Terraform のスタートガイドをご覧ください。
terraform init && terraform apply -auto-approve
Terraform スクリプトは以下を作成します。
PROJECT_ID-vpc
という名前の VPC ネットワークPROJECT_ID-gke
という名前の GKE クラスタPROJECT_ID-mysql
という名前の Cloud SQL インスタンス- アプリケーションで使用する
ecommerce
という名前のデータベース - パスワードが
password
に設定されたユーザーroot
- アプリケーションで使用する
Terraform スクリプトを変更して、パスワードを自動生成できます。この設定では、単純な例を使用しますが、本番環境では使用しないでください。
インフラストラクチャのプロビジョニングには、最長で 10 分ほどかかる場合があります。スクリプトが成功すると、出力は次のようになります。
... Apply complete! Resources: 8 added, 0 changed, 0 destroyed. Outputs: kubernetes_cluster_name = PROJECT_ID-gke sql_database_name = PROJECT_ID-mysql vpc_name = PROJECT_ID-vpc
クラスタに接続し、
monolith
という名前の名前空間を作成します。GKE クラスタ内の独自の Namespace にアプリケーションをデプロイします。gcloud container clusters get-credentials $CLUSTER \ --region $REGION \ --project $PROJECT && \ kubectl create ns monolith
GKE で動作するアプリケーションは、Kubernetes Secret を使用して Cloud SQL データベースにアクセスします。データベースのユーザー認証情報を使用する Secret を作成します。
kubectl create secret generic dbsecret \ --from-literal=username=root \ --from-literal=password=password -n monolith
Docker イメージを構築し、Container Registry にアップロードします。
cd ~/monolith gcloud builds submit --tag gcr.io/$PROJECT_ID/ecomm
deploy.yaml
ファイル内の参照を、新しく作成した Docker イメージに更新します。cd ~/monolith sed -i -e "s/\[PROJECT_ID\]/$PROJECT/g" deploy.yaml
デプロイ マニフェスト ファイル内のプレースホルダを置き換え、アプリケーションをデプロイします。
cd .. && \ DB_IP=$(gcloud sql instances describe $PROJECT-mysql | grep "ipAddress:" | tail -1 | awk -F ":" '{print $NF}') sed -i -e "s/\[DB_IP\]/$DB_IP/g" monolith/deploy.yaml kubectl apply -f monolith/deploy.yaml
デプロイのステータスを確認します。
kubectl rollout status deployment/ecomm -n monolith
出力は次のようになります。
Waiting for deployment "ecomm" rollout to finish: 0 of 1 updated replicas are available... deployment "ecomm" successfully rolled out
デプロイされたアプリケーションの IP アドレスを取得します。
kubectl get svc ecomm -n monolith \ -o jsonpath="{.status.loadBalancer.ingress[*].ip}" -w
ロードバランサの IP アドレスが公開されるまで待機します。コマンドを終了するには、
Ctrl+C
を押します。ロードバランサの IP アドレスをメモしてから、URLhttp://IP_ADDRESS
にあるアプリケーションにアクセスします。ロードバランサが正常な状態になり、トラフィックの通過が開始されるまでに時間がかかることがあります。
モノリスの購入手続きを検証する
このセクションでは、テスト注文を作成して購入手続きを検証します。
- 前のセクションでメモした URL(
http://IP_ADDRESS
)に移動します。 - 表示されたアプリケーションのホームページで、任意の商品を選択し、[カートに追加] をクリックします。
- テスト購入を作成するには、[注文する] をクリックします。
- 購入手続きが成功すると、注文確認ウィンドウが表示され、注文確認 ID が表示されます。
注文の詳細を表示するには、データベースに接続します。
gcloud sql connect $PROJECT-mysql --user=root
サポートされているその他の方法を使用してデータベースに接続することもできます。プロンプトが表示されたら、パスワードとして「
password
」と入力します。保存された注文の詳細を表示するには、次のコマンドを実行します。
select cart_id from ecommerce.cart;
出力は次のようになります。
+--------------------------------------+ | cart_id | +--------------------------------------+ | 7cb9ab11-d268-477f-bf4d-4913d64c5b27 | +--------------------------------------+
マイクロサービス ベースの e コマース アプリケーションをデプロイする
このセクションでは、リファクタリングされたアプリケーションをデプロイします。このドキュメントでは、フロントエンド サービスと支払いサービスの分離についてのみ説明します。このシリーズの次のドキュメント、マイクロサービス アプリケーションの分散トレースでは、モノリスから分離できるレコメンデーション サービスや広告サービスなど、その他のサービスについて説明します。次の図に示すように、購入手続きサービスはフロントエンド サービスと支払いサービスの間で分散トランザクションを処理し、GKE クラスタに Kubernetes Service としてデプロイされます。
図 5.購入手続きサービスは、カートサービス、支払いサービス、メールサービス間のトランザクションをオーケストレートします。
マイクロサービスをデプロイする
このセクションでは、以前にプロビジョニングしたインフラストラクチャを使用して、独自の名前空間 microservice
にマイクロサービスをデプロイします。
以下の前提条件があるか確認します。
- Google Cloud プロジェクト
gcloud
、git
、kubectl
を含むシェル環境
Cloud Shell でマイクロサービス リポジトリのクローンを作成します。
git clone https://github.com/GoogleCloudPlatform/microservices-demo cd microservices-demo/
Google Cloud プロジェクトとリージョンを設定し、GKE API が有効になっていることを確認します。
export PROJECT_ID=PROJECT_ID export REGION=us-central1 gcloud services enable container.googleapis.com \ --project=${PROJECT_ID}
PROJECT_ID
は、Google Cloud プロジェクトの ID に置き換えます。GKE クラスタを作成し、その認証情報を取得します。
gcloud container clusters create-auto online-boutique \ --project=${PROJECT_ID} --region=${REGION}
クラスタの作成には数分かかる場合があります。
クラスタにマイクロサービスをデプロイします。
kubectl apply -f ./release/kubernetes-manifests.yaml
Pod の準備が整うまで待ちます。
kubectl get pods
数分後、Pod が
Running
状態になります。フロントエンドの外部 IP アドレスを使用して、ブラウザでウェブ フロントエンドにアクセスします。
kubectl get service frontend-external | awk '{print $4}'
ウェブブラウザで
http://EXTERNAL_IP
にアクセスして、Online Boutique のインスタンスへのアクセスを開始します。
新しい購入手続きを検証する
- 購入手続きフローを確認するには、前のセクションのモノリス購入手続きを検証するの説明に従って商品を選択し、注文します。
- 注文の購入手続きが完了しても、確認ウィンドウに確認 ID は表示されません。代わりに、確認ウィンドウには、詳細確認のためにメールを確認するように表示されます。
注文を受け取ったこと、支払いサービスで支払いが処理されたこと、注文の詳細が更新されたことを確認するには、次のコマンドを実行します。
kubectl logs -f deploy/checkoutservice --tail=100
出力は次のようになります。
[...] {"message":"[PlaceOrder] user_id=\"98828e7a-b2b3-47ce-a663-c2b1019774a3\" user_currency=\"CAD\"","severity":"info","timestamp":"2023-08-10T04:19:20.498893921Z"} {"message":"payment went through (transaction_id: f0b4a592-026f-4b4a-9892-ce86d2711aed)","severity":"info","timestamp":"2023-08-10T04:19:20.528338189Z"} {"message":"order confirmation email sent to \"someone@example.com\"","severity":"info","timestamp":"2023-08-10T04:19:20.540275988Z"}
ログを終了するには、
Ctrl+C
を押します。支払いが正常に行われたことを確認します。
kubectl logs -f deploy/paymentservice -n --tail=100
出力は次のようになります。
[...] {"severity":"info","time":1691641282208,"pid":1,"hostname":"paymentservice-65cc7795f6-r5m8r","name":"paymentservice-charge","message":"Transaction processed: visa ending 0454 Amount: CAD119.30128260"} {"severity":"info","time":1691641300051,"pid":1,"hostname":"paymentservice-65cc7795f6-r5m8r","name":"paymentservice-server","message":"PaymentService#Charge invoked with request {\"amount\":{\"currency_code\":\"USD\",\"units\":\"137\",\"nanos\":850000000},\"credit_card\":{\"credit_card_number\":\"4432-8015-6152-0454\",\"credit_card_cvv\":672,\"credit_card_expiration_year\":2039,\"credit_card_expiration_month\":1}}"}
ログを終了するには、
Ctrl+C
を押します。注文確認メールが送信されていることを確認します。
kubectl logs -f deploy/emailservice -n --tail=100
出力は次のようになります。
[...] {"timestamp": 1691642217.5026057, "severity": "INFO", "name": "emailservice-server", "message": "A request to send order confirmation email to kalani@examplepetstore.com has been received."}
各マイクロサービスのログメッセージは、購入手続き、支払い、メールサービスに分散されたトランザクションが正常に完了したことを示します。
分散トランザクションで代替アクションを検証する
このセクションでは、お客様が注文を行い、支払いサービスが停止した場合のシナリオをシミュレートします。
サービスが利用できないことをシミュレートするには、支払いのデプロイとサービスを削除します。
kubectl delete deploy paymentservice && \ kubectl delete svc paymentservice
アプリケーションに再度アクセスして、購入手続きのフローを完了します。この例では、支払いサービスが応答しないと、リクエストがタイムアウトし、報酬アクションがトリガーされます。
UI フロントエンドで、[注文を確定] ボタンをクリックします。出力は次のようになります。
HTTP Status: 500 Internal Server Error rpc error: code = Internal desc = failed to charge card: could not charge the card: rpc error: code = Unavailable desc = connection error: desc = "transport: Error while dialing: dial tcp: lookup paymentservice on 34.118.224.10:53: no such host" failed to complete the order main.(*frontendServer).placeOrderHandler /src/handlers.go:360
フロントエンド サービスのログを確認します。
kubectl logs -f deploy/frontend --tail=100
出力は次のようになります。
[...] {"error":"failed to complete the order: rpc error: code = Internal desc = failed to charge card: could not charge the card: rpc error: code = Unavailable desc = connection error: desc = \"transport: Error while dialing: dial tcp: lookup paymentservice on 34.118.224.10:53: no such host\"","http.req.id":"0a4cb058-ee9b-470a-9bb1-3a965636022e","http.req.method":"POST","http.req.path":"/cart/checkout","message":"request error","session":"96c94881-a435-4490-9801-c788dc400cc1","severity":"error","timestamp":"2023-08-11T18:25:47.127294259Z"}
購入手続きサービスのログを確認します。
kubectl logs -f deploy/frontend --tail=100
出力は次のようになります。
[...] {"message":"[PlaceOrder] user_id=\"96c94881-a435-4490-9801-c788dc400cc1\" user_currency=\"USD\"","severity":"info","timestamp":"2023-08-11T18:25:46.947901041Z"} {"message":"[PlaceOrder] user_id=\"96c94881-a435-4490-9801-c788dc400cc1\" user_currency=\"USD\"","severity":"info","timestamp":"2023-08-11T19:54:21.796343643Z"}
通知を送信するためのメールサービスの呼び出しは、これ以降行われません。
payment went through (transaction_id: 06f0083f-fa47-4d91-8258-6d61edfab1ca)
のようなトランザクション ログはありません。メールサービスのログを確認します。
kubectl logs -f deploy/emailservice --tail=100
メールサービスで失敗したトランザクションに対してログエントリが作成されていないことに注意してください。
オーケストレーターとしてサービスの呼び出しが失敗した場合、購入手続きサービスはエラー ステータスを返して購入手続きプロセスを終了します。
クリーンアップ
このシリーズの次のドキュメント、マイクロサービス アプリケーションの分散トレースの手順を行う場合は、プロジェクトとリソースを削除せずに、再利用できます。
プロジェクトの削除
- In the Google Cloud console, go to the Manage resources page.
- In the project list, select the project that you want to delete, and then click Delete.
- In the dialog, type the project ID, and then click Shut down to delete the project.
リソースを削除する
このドキュメントで使用した Google Cloud プロジェクトを残しておく場合は、個々のリソースを削除します。
Cloud Shell で、次のコマンドを実行します。
cd setup && terraform destroy -auto-approve
Google Cloud CLI を使用してマイクロサービス クラスタを削除するには、次のコマンドを実行します。
gcloud container clusters delete online-boutique \ --location $REGION
次のステップ
- マイクロサービス アーキテクチャの詳細を確認する。
- このシリーズの最初のドキュメントを読み、マイクロサービス、そのメリット、課題、ユースケースについて学習する。
- このシリーズの 2 番目のドキュメントを読み、マイクロサービスを分解するためのアプリケーション リファクタリング戦略について学習する。
- このシリーズの最後のドキュメントを読み、マイクロサービス間でのリクエストの分散トレースについて学習する。