Tanzu Tanzu for Kubernetes Operations アプリケーションのモダナイゼーション

Tanzu Service Mesh を触ってみよう (Part-1)

VMware Tanzu Service Mesh (以下 TSM )は VMware が提供するサービスメッシュプラットフォームであり、アプリケーションのエンドツーエンドの通信全体の検出、ID、ポリシー制御、トラフィック管理の機能を提供し、サービスレベル目標やデータ保護およびプライバシーに関する規制要件への準拠を可能にします。

TSM アーキテクチャ

Tanzu Service Mesh アーキテクチャ

TSM は SaaS で提供される、VMware Cloud Services のコンソールからアクセスできるグローバルコントローラーと、クラスタ側にインストールされる TSM エージェントなどのコンポーネントで構成されます。また、クラスタ側では OSS の Istio や Istio に含まれる Envoy proxy を活用していますが、Istio と比較して、アプリケーションの優れた可視性、コンポーネントのライフサイクル管理、可用性の向上、API セキュリティといったような便利な機能を TSM は提供しています。また、当然ながら商用サポートを提供していますし、Istio モードとしてIstio API を直接利用することもできますので、TSM の Istio ライフサイクル管理などの一部機能を利用しつつ、これから Istio を使い始めようと考えている方にとってもメリットを提供します。また、Tanzu for Kubernetes Operations の中に含まれる Tanzu Service Mesh Advanced は、その中でも Global Namespace によるマルチクラスタのサービスメッシュや通信の可視化、mTLS による通信の保護を提供します。

TKO Blog シリーズ「Tanzu Service Mesh 入門(概要編)」ではTSM の概要を紹介しましたが、本記事では、TSM の使い方と基本的な機能である Global Namespace を中心に解説します。

クラスタの接続

TSM の機能を利用するためには、Kubernetes クラスタを TSM に接続する必要があります。接続がサポートされているクラスタに関しては下記ドキュメントをご参照ください。

https://docs.vmware.com/en/VMware-Tanzu-Service-Mesh/services/tanzu-service-mesh-environment-requirements-and-supported-platforms/GUID-D0B939BE-474E-4075-9A65-3D72B5B9F237.html

実際にクラスタを接続してみましょう。

画面左上のNEW WORKFLOW をクリックして、Onboard New Cluster を選択します。

クラスタ名を任意に入力し、GENERATE SECURITY TOKEN をクリックすると、2 つの kubectl コマンドが出力されます。この kubectl コマンドをクラスタに対し適用することで、TSM で必要な Istio のエージェントやシークレットが作成されます。vmware-system-tsm 名前空間内の Pod が問題なくRunnning またはCompleted の状態であればOKです。

最後に、INSTALL TANZU SERVICE MESH をクリックすれば接続は完了です。ここで、Install on all Namespaces を選択すると、管理系の名前空間を除くすべての名前空間にistio-injection=enabled のラベルが付き、Pod 作成時にIstio のサイドカーが自動的にデプロイされるようになります。このラベル付けを除外したい名前空間をここで選択することができますが、接続後も変更できるため、ここではすべての名前空間に適用しています。

これで接続は完了ですが、まだアプリケーションをデプロイしていないため、HOME → Cluster Overview で接続したクラスタは確認できるものの、No Service Traffic と表示され、あまり面白くありません。そこでテスト用のアプリケーションであるbookinfo をデプロイしてみましょう。デプロイ方法についてはIstio の公式ドキュメントを参照してください。

bookinfo アプリケーションのアーキテクチャ

下記コマンドを実行します。

git clone https://github.com/istio/istio 
cd istio/ 
kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml

kubectl get pod で問題なくPod が立ち上がっていることを確認します。

[root@localhost istio]# kubectl get pod
NAME READY STATUS RESTARTS AGE
details-v1-5f676b4ff4-jsvbk 2/2 Running 0 27m
productpage-v1-6b457f989b-rnchv 2/2 Running 0 27m
ratings-v1-786ddcddf4-s5cw6 2/2 Running 0 27m
reviews-v1-78b44768d8-vw5t8 2/2 Running 0 27m
reviews-v2-68944bbd8-qnbwx 2/2 Running 0 27m
reviews-v3-769d45d664-cqz7d 2/2 Running 0 27m

外部から接続するために、Gateway / VirtualService リソースを作成します。Gateway リソースは、その名の通りクラスタの外部/内部の境界で作用するゲートウェイで、トラフィックの許可のために必要な入口/出口を定義します。既にistio-system 名前空間にはistio-ingressgateway Service がデプロイされていますが、Gateway リソースを追加でデプロイすることで、作成したアプリケーションに対してトラフィックを許可することができます。また、VirtualService リソースは、どのKubernetes Service に対してどのようにトラフィックを流すかを定義するリソースです。これら2 つを追加でデプロイすることで、bookinfo アプリケーションにアクセスすることができるようになります。

kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml

さっそくアクセスしてみましょう。アクセスのためのIP アドレスやポート番号は下記コマンドで確認できます(環境によって値は異なります)。URL はhttp://${GATEWAY_URL}/productpage のような形になります(下記の例ですと URL はhttp://10.198.104.46/productpage です)。

[root@localhost istio]# kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
10.198.104.46
[root@localhost istio]# kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].port}'
80

何度か更新ボタンを押すと、本のレビューの星の色と、右下のReviews served by: の部分、つまりアクセスしている本のレビュー情報を取得・表示するためのPod の名前が変わっていることに気づきますが、これはIstio がトラフィックをラウンドロビンで振り分けているからです。

アクセス先のPod が異なるためPod 名やレビューの星の色が変化

さて、ここまではIstio の話でしたが、TSM から、HOME→Cluster Overview より、デプロイしたクラスタを選択すると、bookinfo アプリの接続の様子がグラフィカルに表示されています(うまく表示されない場合は、画面更新を繰り返しトラフィックを発生させてみてください)。

さて、これでTSM としては問題なくインストールが完了していることが分かりました。ここからは、TSM ならではの機能を中心に解説します。なお、次に進む前にbookinfo アプリは削除してください。

Global Namespace によるマルチクラウドのサービスメッシュ

TSM の中核的な機能が、Global Namespace (GNS) と呼ばれる、複数のクラスタに対する名前空間の抽象化です。この機能を利用するにあたり、まずはKubernetes クラスタを2 つ以上用意します。今回はAWS のEKS とAzure 上にデプロイしたTanzu Kubernetes Grid を使います(インストール要件を満たしているクラスタであればOKです)。

さて、さっそく2 つのクラスタをTSM に接続します。手順については既に解説したため省略します。

接続が完了したら、テスト用のアプリケーションを作成します。ここでは、acme fitness demo というマイクロサービスで構成された通販サイトを使用しますが、このアプリケーションをコンポーネントを分けながら2 つのクラスタにまたがってデプロイすることに注意してください。Kubernetes は各クラウドの差異を吸収する抽象化レイヤと考えることができますが、GNS によりKubernetes クラスタをまたがってアプリケーションをデプロイできるため、GNS はKubernetes クラスタ群に対する追加の抽象化レイヤと考えることができます。

さっそくデプロイしてみます。ここでは、Azure 上のTanzu Kubernetes Grid をクラスタ1 、EKS をクラスタ2 とし、クラスタ1 とクラスタ2にまたがってアプリケーションをデプロイします。catalog サービスのみがクラスタ2、それ以外はクラスタ1 にデプロイされます。

2 つのクラスタでデプロイされるacme fitness demo アプリケーション

まずはクラスタ1 で下記コマンドを実行します。アプリケーションの一部コンポーネントのみ(acme_fitness_cluster1.yaml)をデプロイしていることに注意してください。

git clone -b dkalani-dev3 https://github.com/vmwarecloudadvocacy/acme_fitness_demo.git
cd acme_fitness_demo
kubectl apply -f kubernetes-manifests/secrets.yaml
kubectl apply -f istio-manifests/gateway.yaml
kubectl apply -f kubernetes-manifests/acme_fitness_cluster1.yaml

次に、クラスタ2 で下記コマンドを実行します。

git clone -b dkalani-dev3 https://github.com/vmwarecloudadvocacy/acme_fitness_demo.git
cd acme_fitness_demo
kubectl apply -f kubernetes-manifests/secrets.yaml
kubectl apply -f istio-manifests/gateway.yaml
kubectl apply -f kubernetes-manifests/acme_fitness_cluster2.yaml

問題なくPod が立ち上がっていることを確認したら、GNS の作成をします。NEW WORKFLOW → New Global Namespace よりGNS 作成ウィザードに進みます。GNS 名は任意でOK です。ドメイン名はacme.example.com にしています。このドメイン名がサービス間の接続時の名前解決に使われます(詳細は後述)。

ここでは抽象化する名前空間を選択します。クラスタ1 とクラスタ2 でacme_fitness_demo アプリが稼働する名前空間(ここではdefault)を選択しています。

ここはそのまま次へを選択してください。こちらのメニューの詳細に関しては下記ドキュメントを参照してください。

https://docs.vmware.com/en/VMware-Tanzu-Service-Mesh/services/tanzu-service-mesh-enterprise/GUID-C7442928-047A-4832-84E8-703E566375EF.html

4 のPulic Services とGSLB の設定はデフォルトのまま次へ進み、GNS の作成を完了します。

GNS の作成が完了し、ホーム画面から作成したGNS を選択すると、GNS に含まれるサービスのマッピングが確認できます。

この状態で、作成したアプリケーションにアクセスしてみましょう。アクセス先IP アドレスはbookinfo と同様、クラスタ1 にてkubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}’ などで調べることができます。アクセスできたら、トップのCATALOG ボタンをクリックし、商品のカタログ画像が表示されないことを確認します。

この状態で再度TSM のUI を確認してみると、いくつかのコンポーネントの通信が可視化されています。発生させた通信によっては画像と若干異なる場合もありますが、ポイントは、この時点ではまだShopping サービスとCatalog サービスの通信が発生していない点です。ゆえに、先ほどは商品のカタログ画像を取得することができていません。

次に、クラスタ1 にて、kubectl edit deployment shopping コマンドでshopping サービスを提供するdeployment のマニフェストを変更します。具体的には、下記スクリーンショットの通り、CATALOG_HOST (カタログサービスのアクセス先URL)をcatalog.acme.example.com に変更します。「catalog + GNS 作成時に最初に定義したドメイン名(acme.example.com)」です。

この状態で、再度アプリケーションにアクセスしてみましょう。今度は、商品のカタログ画像が表示されるはずです。

画面を更新したり、商品をカートに入れたりとアプリケーションへのアクセスを繰り返すと、アプリケーションを構成する各コンポーネントの通信がTSM のUI に表示されます。

このように、GNS を作成することで、アプリケーションをクラスタをまたいで定義できるようになったことがわかりました。Kubernetes クラスタという境界をあまり気にすることなく、より柔軟なアーキテクチャをもつアプリケーションを、GNS を通して作成することができます。

GNS の仕組み

さて、ここまでで、GNS によってクラスタ間の通信を簡単に実現できることが分かりました。ところで、実際のGNS の仕組みはどのようになっているのでしょうか?

通常、アプリケーションが単一クラスタに閉じる世界において、アプリケーションを構成する各Pod は基本的にService オブジェクトを通して通信します。Pod はIP アドレスが変わる前提、かつ同等のPod が複数デプロイされることもあるので、Service オブジェクトが通信相手の名前解決や負荷分散を提供するからです。

ここでSercice オブジェクトの名前解決は、Kubernetes クラスタの内部DNS (CoreDNS)を通して実行されます。my-svc.my-namespace.svc.cluster.local (<Service オブジェクト名>.<名前空間名>.svc.<クラスタのドメイン名>)といったようなDNS A レコードがService に与えられ、クラスタ内のPod はそれらService オブジェクトの名前を通して他のPod にアクセスします。このあたりの詳細はKubernetes のドキュメントを参照してください。

ここで重要なのは、クラスタ内の名前解決はそのクラスタに閉じる、ということです。先のDNS レコードを見てください。あるクラスタは当然他のクラスタのSevice 名や名前空間名を理解しませんし、Pod やService リソースのIP アドレスの振られ方もクラスタによって様々です。したがって、このDNS レコードではクラスタ外のKubernetes Service オブジェクトやPod に対してアクセスできません。では、どうして今回はマルチクラスタにおいて、catalog.acme.example.com という名前をクラスタ1 が正しく解決し、クラスタ2 のカタログサービスへと通信できたのでしょうか?

この裏側の実装は、Istio のServiceEntry を使っています。GNS を作成すると、自動的にSerciceEntry オブジェクトが作成されます。

このオブジェクトの目的はクラスタの外の名前解決を提供することにあります。SerciceEntry オブジェクトには、GNS 作成時に定義したドメイン名と任意のIP アドレスが紐づけられ、あるPod がその名前に対して名前解決をするときに、その任意のIP アドレスに解決されます。このIP アドレスへのアクセスはサイドカーとしてアプリケーションPod に注入されたistio-proxy に対してプロキシされ、さらにEgressGateway に対してプロキシされます。

もう少し詳細を見てみましょう。先のServiceEntry オブジェクトの中で、ホスト名としてcatalog.acme.example.com と定義されているnsxsm.gns.acme-fitness-psajp.catalog をkubectl describe します。

この意味は何でしょうか?ServiceEntry のドキュメントを確認すると分かりますが、 Hosts で定義されているcatalog.acme.example.com はAddresses で定義されている仮想IP アドレスに名前解決されます。次に、このIP アドレス(ここでは248.231.249.21)にアクセスすると、このServiceEntry オブジェクトとの紐づけにより、Endpoints のAddress (ここではistio-egressgateway.istio-system)にトラフィックが渡されます。istio-egressgateway.istio-system とは、Istio のEgressGateway オブジェクトであり、メッシュからの出口点を定義します。これにより、トラフィックの出口をEgressGateway という単一のエンドポイントを通して厳格に管理できるようになります。VMware Aria Operations for Applications (旧Tanzu Observability)において、メトリックの収集の際にWavefront Proxies と呼ばれるプロキシの利用が推奨されているのと同じような理由です。そして、EgressGateway から、クラスタ2 のIngressGateway に対してトラフィックを渡すことで、クラスタ間の通信が実現されています。

クラスタ間にまたがるアプリケーションの通信のフロー

先のIstio ServiceEntry のドキュメントを確認いただきたいのですが、このような外部サービスへのアクセスを手動で実装しようとすると中々複雑です。GNS の場合、このあたりの複雑さをあまり意識せず、GUI から簡単に実装できることがメリットです。

まとめ

本記事では、Tanzu Service Mesh の基本的な機能であるGlobal Namespace を中心に、その技術概要を紹介しました。しかしながらTanzu Service Mesh の強みはこれだけではなく、Global Namespace を中心に提供される可用性やセキュリティを高めるための様々な機能があります。次回のTanzu Service Mesh を触ってみよう (Part-2) では、今回紹介しきれなかった様々な機能を紹介する予定ですので、どうぞお楽しみに!