VMwareが提供しているTanzu Service Mesh(以下TSM)はIstioベースのService Meshを提供するが、大きな違いとしてはSaaSを介したGlobal Namespaceと呼ばれる機能があることが1つ言える。
これはクラスタ間のNamespaceやクラスタ内の複数のNamespaceの壁を取っ払い、同じNamespace内にいるかのようにPod間通信を実現するものだ。
今回はこれを試してみる。
なお、付録に初回の構築方法などもまとめている。どう試したらいいかが分からない人はそちらから参照するとよい。
※'23/10/17追記:本記事の中でクラスタのNamespace名をクラスタ間で合わせる話を書いているが、クラスタ間でNamespace名を合わせないといけないという制約がなくなっている模様。現時点では異なるNamespace名でも通信可能となっている。
Global Namespaceの作成
ここではクラスタを横断したNamespaceとしてのGlobal Namespace(以下GNS)を作成する。
左サイドバーのNEW WORKFLOW
をクリックし、 New Global Namespace
をクリックする。
1. General Details
では以下を入力する。
-
GNS Name
: GNSの名前。TSM固有のものとなる。 -
Description
: GNSの説明。 -
Color
: GNSの色の指定(どこで使われているかが不明。。。) -
Domain
: GNS内で利用するドメイン。クラスタを跨いだ通信をする際に利用する。
2. Namespace Mapping
では以下を入力する。
-
Cluster Name
: GNSに紐付けるクラスタを選択する -
Namespace
: GNSに紐付けるNamespaceを選択する。
複数のクラスタを選択することになると思うので、+ADD MAPPING RULE
をクリックして、紐付けるクラスタ・Namespaceを追加する。
Namespaceについては、各クラスタごとに同じ名前のServiceがいる場合、Namespace名も揃えておかないと通信に失敗する。(同一クラスタ内の場合は問題なく利用できる)
また、1つのNamespaceは1つのGNSにのみ紐付けることが出来、複数のGNSに紐付けることは出来ない。
3. External Services (Optional)
ではGNSに紐付ける外部のサービスを設定する。以下は設定する場合の画面だが、ここでは簡単な検証を目的としているのでNo External Services
を選択してスキップする。
なお、External Servicesの説明はこちらに公式の説明があるが、外部のサービス(Lambda、DB等)にGNS経由でアクセスすることが出来るもので、サポートしているプロトコルとしてはHTTP、HTTPS、TCP、TLSとなっている。
4. Public Services (Optional)
ではService Meshに対して外部から接続するためのGSLBなどの情報を入力する。以下は設定する場合の画面だが、ここでは簡単な検証を目的としているのでNo Public Services
を選択してスキップする。
なお、Public Servicesの説明はこちらに公式の説明がある。
5. Configuration Summary
で確認画面になるので、設定に問題なければFINISH
を押してGNSを作成する。
作成が完了すると、対象のNamespaceにkind: Service
のオブジェクトが存在する場合はkind: VirtualService
のオブジェクトが作成される。
$ kubectl get vs -n tsm-test1
NAME GATEWAYS HOSTS AGE
nsxsm.gns.gns-test-advanced.nginx ["nginx.hoge.tanzu","nginx.tsm-test1.svc.cluster.local","243.228.38.11"] 44m
動作確認(クラスタ跨ぎでの同一Namespace名間アクセス)
クラスタ1の方でサンプルのアプリを立ち上げる。
kubectl create deploy nginx --image nginx -r 1 -n tsm-test1
kubectl expose deploy nginx --port 80 -n tsm-test1
クラスタ2の方でアクセス用Podを立ち上げる。
kubectl run centos --image centos -n tsm-test1 -- sleep 365d
クラスタ2のPodからクラスタ1のServiceにアクセスする。
アクセス先は<サービス名>.<GNS作成時に設定したドメイン名>
となる。
アクセス先はkubectl get vs
でも確認できる。
$ kubectl exec -it centos -n tsm-test1 -- curl nginx.hoge.tanzu
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
:(省略)
External-IPなどを利用せずにクラスタを跨いでサービスにアクセスすることが出来た。
なお、Istioサイドカーが入っていない場合、名前解決に失敗する。
この動きも確認する。
Istioの仕様によると、Labelにsidecar.istio.io/inject: "false"
を指定するとIstioサイドカーが挿入されないので、これを設定したアクセス用Podを作成する。
kubectl delete pod centos -n tsm-test1
kubectl run centos --image centos -n tsm-test1 -l sidecar.istio.io/inject="false" -- sleep 365d
アクセスしてみる。
$ kubectl exec -it centos -n tsm-test1 -- curl nginx.hoge.tanzu
curl: (6) Could not resolve host: nginx.hoge.tanzu
command terminated with exit code 6
先程と異なり、アクセスできないことが確認できた。
また、ここで第3のクラスタをGNSに追加し、nginxを同じようにtsm-test1にデプロイする。
kubectl create deploy nginx --image nginx -r 1 -n tsm-test1
kubectl expose deploy nginx --port 80 -n tsm-test1
この状態でServiceEntry
オブジェクトを確認する。ServiceEntry
は実際にそのホストにアクセスした時のアクセス先の情報を含んでいる。
それぞれのクラスタでkubectl get se -o yaml nsxsm.gns.gns-test-advanced.nginx
を実行すると結果は以下のようになった。
クラスタ1:
endpoints:
- address: nginx.tsm-test1
ports:
http1: 80
weight: 4294967293
- address: istio-egressgateway.istio-system
ports:
http1: 443
weight: 1
クラスタ2:
endpoints:
- address: istio-egressgateway.istio-system
ports:
http1: 443
weight: 1
クラスタ3:
endpoints:
- address: nginx.tsm-test1
ports:
http1: 80
weight: 4294967293
- address: istio-egressgateway.istio-system
ports:
http1: 443
weight: 1
クラスタ1、3は宛先が2つあるものの、weightが異なっており、自クラスタの宛先を優先的に使うように設定される。
クラスタ2は自クラスタにnginxのServiceを持っていないため、istio-egressgateway経由で別クラスタにアクセスしに行くことが分かる。
動作確認(同一クラスタ内別Namespace名間アクセス)
同一のクラスタ内で別Namespaceへのアクセスも、GNSを作成することでNamespaceを意識せずにアクセスすることが出来る。
クラスタ内のNamespace tsm-test2
とtsm-test3
をGNS red
で繋いだ時の挙動を確認する。(GNSの作成方法については省略する)
アクセスすると自身のPodIPを表示するサンプルアプリをそれぞれのNamespaceで起動し、公開する。
kubectl run ipchecker --image imuratavmware/ipchecker -n tsm-test2
kubectl expose pod ipchecker --port 80 -n tsm-test2
kubectl run ipchecker --image imuratavmware/ipchecker -n tsm-test3
kubectl expose pod ipchecker --port 80 -n tsm-test3
VirtualService
としては以下となる。
$ kubectl get vs -A
tsm-test2 nsxsm.gns.red.ipchecker ["ipchecker.tanzu.tkg","ipchecker.tsm-test2.svc.cluster.local","244.177.154.138"] 3m44s
tsm-test3 nsxsm.gns.red.ipchecker ["ipchecker.tanzu.tkg","ipchecker.tsm-test3.svc.cluster.local","244.177.154.138"] 3m43s
作成したPodのIPは以下となっている。
$ kubectl get pod -n tsm-test2 -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
centos 2/2 Running 0 4h26m 100.96.2.234 imurata-wdc-tsm2-md-0-6jww9-5d554f5cc7-kf5kv <none> <none>
ipchecker 2/2 Running 0 13m 100.96.1.130 imurata-wdc-tsm2-md-0-6jww9-5d554f5cc7-99fqb <none> <none>
$ kubectl get pod -n tsm-test3 -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
ipchecker 2/2 Running 0 3m56s 100.96.2.243 imurata-wdc-tsm2-md-0-6jww9-5d554f5cc7-kf5kv <none> <none>
この状態ではipchecker.tanzu.tkg
がそれぞれのNamespaceに存在しているが、この状態でアクセスすると以下のような結果となった。
$ kubectl exec -it centos -- curl ipchecker.tanzu.tkg
Client IP: 127.0.0.6
Server IP: 100.96.2.243
$ kubectl exec -it centos -- curl ipchecker.tanzu.tkg
Client IP: 127.0.0.6
Server IP: 100.96.1.130
これにより、同一クラスタ内別Namespace名間アクセスでは以下が確認できた。
- Namespaceを意識せずにサービスにアクセス出来る
- 同名のサービスがNamespaceを跨いで存在する場合、分散される
なお、ServiceEntry
オブジェクト内では、それぞれのNamespaceに対するアクセスの重さが均一に定義される。
$ kubectl get se nsxsm.gns.red.ipchecker
NAME HOSTS LOCATION RESOLUTION AGE
nsxsm.gns.red.ipchecker ["ipchecker.tanzu.tkg"] MESH_INTERNAL DNS 23h
$ kubectl get se nsxsm.gns.red.ipchecker -o yaml
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
:(省略)
endpoints:
- address: ipchecker.tsm-test2
ports:
http1: 80
weight: 2147483647
- address: ipchecker.tsm-test3
ports:
http1: 80
weight: 2147483647
まとめ
GNSを使うと、<サービス名>.<GNSで定義したドメイン名>
でアクセスできるため、Namespaceを意識せずにアプリケーション間通信を行うことが出来る。
また、同名のサービスがいれば分散するため、クラスタ間で同名のサービスを用意しておけば、冗長性を持たせることも出来る。
マイクロサービスでシステムを構築する場合にはかなり使えそうだと思われる。
付録:Tanzu Service Meshのセットアップ
要件
システム要件
こちらによるとWorkloadクラスタの要件は以下となる。
コンポーネント | 要件 |
---|---|
Node | 3台、4CPU、6GBメモリ |
Daemonset | 各ノードで稼働するDaemonset用に0.25CPUと650MBメモリ |
Ephemeral ストレージ | クラスタ全体で24GB、各ノードごとに1GB |
Pod | クラスタ全体で30個、各ノードごとに3個 |
サポートしているKubernetesはこちら。
各ディストリビューションの最新バージョンが含まれていないように見える点に注意。
ネットワーク要件
- クラスタ間で通信させる場合はクラスタ間でネットワークのreachabilityが必要
- クラスタでCNI、RBACが利用可能
- クラスタ上のTSMコンポーネントからSaaSおよびECRに接続出来る必要あり。
-
Type: LoadBalancer
が使える状態になっている
※NodePortはサポートされない
クラスタの登録(Onboarding)
TSMでクラスタを管理するために、TSMにクラスタを登録してAgentをクラスタ内にインストールすることをOnboardingと呼ぶ。
公式ドキュメントはこのあたり。
TSMにアクセスし、左サイドバーのNEW WORKFLOW
をクリックし、Onboard New Cluster
をクリックする。
すると以下のような入力フォームが現れる。
ここでの設定項目は以下となる。
-
Enter cluster name
: クラスタ名を入力する -
Cluster Labels
: TSM内で識別するためのラベルを割り当てる -
Cluster admin owned
: チェックするとTSM側でNamespaceを管理しなくなる
最初にTSM上でクラスタを識別するための名前を入力する。これはTKG上のクラスタ名と同じである必要はない。
ハイフン以外の記号や大文字は利用できないので注意。
Cluster Labels
に関しては、プロキシを利用してNSX ALBに繋ぐ場合は設定が必要だが、今回は利用しないためスキップする。
Cluster admin owned
はチェックするとTSM側でIstioサイドカーを挿入するNamespaceを制御しなくなる。
ユーザー側でNamespaceにistio-injection=enabled
というラベルを付与した場合にのみ、そのNamespaceで作成したPodに対し、Istioサイドカーが挿入される。
Integrations
に関しては、mTLSなどで利用する証明書を統合する場合に変更する。今回はTSMの自己署名証明書を利用するので変更しない。
GENERATE SECURITY TOKEN
をクリックすると、yamlのapplyを促す画面に遷移する。
表示されているコマンドをコピーし、クラスタで実行する。
1番目のコマンドを実行すると、以下のようにTSMのためのカスタムリソースやロール等のリソースがデプロイされる。
namespace/vmware-system-tsm created
customresourcedefinition.apiextensions.k8s.io/aspclusters.allspark.vmware.com created
customresourcedefinition.apiextensions.k8s.io/clusters.client.cluster.tsm.tanzu.vmware.com created
customresourcedefinition.apiextensions.k8s.io/tsmclusters.tsm.vmware.com created
customresourcedefinition.apiextensions.k8s.io/clusterhealths.client.cluster.tsm.tanzu.vmware.com created
configmap/tsm-agent-operator created
serviceaccount/tsm-agent-operator-deployer created
clusterrole.rbac.authorization.k8s.io/tsm-agent-operator-cluster-role created
role.rbac.authorization.k8s.io/vmware-system-tsm-namespace-admin-role created
clusterrolebinding.rbac.authorization.k8s.io/tsm-agent-operator-crb created
rolebinding.rbac.authorization.k8s.io/tsm-agent-operator-rb created
deployment.apps/tsm-agent-operator created
job.batch/update-scc-job created
2番目のコマンドを実行するとTSMと接続するためのTokenがSecretとしてクラスタに作成される。
なお、ここのSecretの作成前についうっかりTMCからIntegrationをしてたりすると既にSecretがあって作成に失敗する。
TMCからのIntegrationとはバッティングしないようにすること。
暫く待つと、以下のように画面が推移して、TSMをインストールする画面が出てくる。
TSMの対象のNamespaceを聞いているので、除外するNamespaceがあればそれを指定する。
今回は全Namespaceを対象としてデプロイする。
INSTALL TANZU SERVICE MESH
をクリックすると、クラスタに以下のようにジョブが走ってクラスタとの接続が行われる。
$ kubectl get job -n vmware-system-tmc
NAME COMPLETIONS DURATION AGE
agentupdater-workload-27952283 0/1 9s 9s
ちなみにvSphere with TanzuだとPSPの設定が適切ではないと以下のような感じでエラーが出てReplicaSetの作成が終わらない。
$ kubectl get event -n vmware-system-tsm
LAST SEEN TYPE REASON OBJECT MESSAGE
2m42s Warning FailedCreate job/check-client-crds Error creating: pods "check-client-crds--1-" is forbidden: PodSecurityPolicy: unable to admit pod: []
4m5s Warning FailedCreate replicaset/policy-d58dbf556 Error creating: pods "policy-d58dbf556-" is forbidden: PodSecurityPolicy: unable to admit pod: []
4m5s Warning FailedCreate replicaset/telemetry-86c497947d Error creating: pods "telemetry-86c497947d-" is forbidden: PodSecurityPolicy: unable to admit pod: []
4m45s Normal Synced tsmcluster/tsm-client-cluster TsmCluster synced successfully
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
allspark-ws-proxy-6cf5859b4c 1 1 1 26h
config-service-74587c9cfd 1 1 1 26h
k8s-cluster-manager-79df485f94 1 1 1 26h
policy-d58dbf556 1 0 0 26h
telemetry-86c497947d 1 0 0 26h
tsm-agent-operator-d866f497b 1 1 1 26h
適切にPSPを設定するか、面倒なら以下のように一律で権限付与してあげる必要がある。
kubectl create clusterrolebinding default-tkg-admin-privileged-binding --clusterrole=psp:vmware-system-privileged --group=system:authenticated