Tanzu Service MeshのGlobal NamespaceをCLIで作成する

Tanzu Service Mesh(以下TSM)にはCI/CDやGitOps用途を想定してCLIが提供されている。
TSM専用のCLIはTSMにログイン後、`Admin`->`CLI`からダウンロード出来るが、[tanzuコマンドの最新版](https://docs.vmware.com/en/VMware-Tanzu-CLI/1.0/tanzu-cli/index.html)ではtanzu pluginとしてTSMのプラグインが用意されており、tanzu CLIからTSMが操作出来るようになっている。
今回はこれを使ってTSMのGloban Namespace(GNS)をCLIから作成してみる。

## 事前準備
### インストール
[Tanzu CLI](https://docs.vmware.com/en/VMware-Tanzu-CLI/index.html#compatibility-with-vmware-tanzu-products-1)のプラグインと対応バージョンの表を見ると分かるが、古いtanzuコマンドではTSMのpluginは対応していない。
今回はtanzuコマンドを導入するところから始める。
Tanzu CLIの[GitHubページ](https://github.com/vmware-tanzu/tanzu-cli/releases)からバイナリをダウンロードしてインストールする。
```sh
wget -P /tmp/ https://github.com/vmware-tanzu/tanzu-cli/releases/download/v1.0.0/tanzu-cli-linux-amd64.tar.gz
tar xvzfp /tmp/tanzu-cli-linux-amd64.tar.gz -C /tmp/
install /tmp/tanzu-cli-linux_amd64 ~/bin/tanzu
```
パスが通っていることを確認する。
```sh
$ tanzu version
version: v1.0.0
buildDate: 2023-08-08
sha: 006d0429
```
[公式の手順](https://docs.vmware.com/en/VMware-Tanzu-Service-Mesh/services/using-tanzu-service-mesh-guide/GUID-43D50C92-C382-4329-BCEA-E77EA8670AE3.html#GUID-43D50C92-C382-4329-BCEA-E77EA8670AE3__id-04f6bd31-6d35-4ed8-85c7-ed8144f81bee)に従いTSMのPluginをインストールする。
```sh
tanzu plugin install service-mesh
```
実行すると使用許諾に合意するか、的なメッセージが出るのでYesを選択する。
```sh
Do you agree to the VMware General Terms?
  [Use arrows to move, type to filter]
> Yes
  No
```
次にCEIP(利用情報をVMwareに渡すかどうか)について聞かれるので、こちらは好みで回答する。
```sh
Do you agree to participate in the Customer Experience Improvement Program?
> Yes
  No
```
問題なければ以下のようなメッセージが出てインストールは完了する。
```sh
[i] Reading plugin inventory for "projects.registry.vmware.com/tanzu_cli/plugins/plugin-inventory:latest", this will take a few seconds.
[i] Installing plugin 'service-mesh:v1.0.1' with target 'global'
[ok] successfully installed 'service-mesh' plugin
```
### TSMへのログイン
次にCLIからTSMにログインする。
最初にログイン先のホストとログインのためのTokenを設定する。
```sh
export TSM_HOST=prod-2-1.nsxservicemesh.vmware.com
export TSM_TOKEN=f_EJNfU94FxYxxxx
```
ホストはブラウザでTSMにアクセスした際のドメイン名を指定する。
TokenはVMware Cloud ServiceのTokenとなる。Tokenの取得方法は過去に書いた[こちら](https://qiita.com/ipppppei/items/14d2302a89b263a97266#cli%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%A8%E5%88%9D%E6%9C%9F%E8%A8%AD%E5%AE%9A)の記事に載せている。
環境変数設定後、ログインする。
```sh
tanzu sm login -s $TSM_HOST -t $TSM_TOKEN
```
コマンド実行後、以下のようなメッセージが表示される。
```sh
[i] The tanzu cli essential plugins have not been installed and are being installed now. The install may take a few seconds.


Response Error: <nil>
Response Code: 200 OK
Response Body: {"id_token":"eyJhbGxxxxxx
```
なお、`-s`で指定したサーバが適当でも200が返るので、問題ないかどうかはここでは分からない。Tokenが誤っている場合はここで`400 Bad Request`が返る。
適当にコマンドを叩いてみる。
```yaml
$ tanzu sm get spec globalnamespaces
apiVersion: gns.tsm.vmware.com/v1
kind: GlobalNamespace
metadata:
  labels:
    projectId: string
  name: string
  :（省略）
```
上記のように意図通りに動けばOK。

## GNSの作成
GNS用Manifestの作成方法としては、[`tanzu sm get spec`で雛形を生成して修正して作成する方法](https://docs.vmware.com/en/VMware-Tanzu-Service-Mesh/services/using-tanzu-service-mesh-guide/GUID-E7FDDE99-3E53-4AE7-B3D3-3CDB3F060607.html#procedure-2)と[既存のGNSの情報を引っこ抜いてYAMLに整形する方法](https://docs.vmware.com/en/VMware-Tanzu-Service-Mesh/services/using-tanzu-service-mesh-guide/GUID-560B8792-F1E7-4313-AD6C-63F2A220C81A.html#procedure-2)がある。
後者の場合も雛形が必要になるので、両方のやり方がカバーできる後者で試してみる。
コマンドは`tanzu sm get <サブコマンド>`の形式となっており、GNSの場合は`tanzu sm get globalnamespace`でGNSの一覧を、`tanzu sm get globalnamespace <GNS名>`で既存のGNSの詳細情報を取得することが出来る。
試してみる。まずは一覧取得から。
```sh
$ tanzu sm get globalnamespace
autoscaling-test
demo
gns-test-advanced
ipchecker-gns
red
```
問題ないようだ。次にGNS`demo`の詳細を取得する。
```yaml
$ tanzu sm get globalnamespace demo
api_discovery_enabled: true
ca: default
ca_type: PreExistingCA
color: '#9460B8'
description: ""
display_name: demo
domain_name: tsm.demo
match_conditions:
- cluster:
    match: imurata-wdc-tsm
    type: EXACT
  namespace:
    match: demo
    type: EXACT
- cluster:
    match: imurata-wdc-tsm2
    type: EXACT
  namespace:
    match: demo
    type: EXACT
- cluster:
    match: imurata-wdc-huge-wc
    type: EXACT
  namespace:
    match: demo
    type: EXACT
mtls_enforced: false
name: demo
use_shared_gateway: true
version: "2.0"
```
既存のGNSの`demo`では3クラスタに対してNamespace`demo`でクラスタ跨ぎの通信を設定していたため、こちらも問題なく取得できている。
この表示項目はTSMのGNS用Manifestでの`spec`にあたる。
これを使って新しいGNSを作成する。
一旦ファイルに吐き出す。
```sh
$ tanzu sm get globalnamespace demo > gns-demo.yaml
```
`spec`の形式に変換する。
```sh
sed -i "s/^\(.*\)/  \1/g" gns-demo.yaml
sed -i "1i spec:" gns-demo.yaml
```
GNS用Manifestの形式にするため、apiVersionやmetadata等を追加する。
GNS用Manifestの形式（雛形）は以下のコマンドで確認できる。
```sh
tanzu sm get spec globalnamespace
```
先程の`spec`形式にしたファイルとGNS用Manifestの雛形を組み合わせてManifestを作成する。
```sh
{ tanzu sm get spec globalnamespace | sed '/spec:/,$d'; cat gns-demo.yaml; } > gns-demo-new.yaml
```
なお、このままの状態だと既存のGNSと丸かぶりなのでapplyしてもエラーとなる。
```sh
$ tanzu sm apply -f gns-demo-new.yaml
Error: GlobalNamespace/string creation failed, HTTP Response Status: 400
```
エラーの原因を確認する場合は`--debug`をつける。
```sh
$ tanzu sm apply -f gns-demo-new.yaml --debug
：（省略）
time="2023-08-23T02:21:05Z" level=debug msg="400 Bad Request"
time="2023-08-23T02:21:05Z" level=debug msg="{\"code\":400,\"message\":\"got error handling request :400 undefined: Config Error. Domain conflicts with existing GNS: demo\\n\\\"Config Error. Domain conflicts with existing GNS: demo\\\"\",\"name\":\"Error\"}\n"
Error: GlobalNamespace/string creation failed, HTTP Response Status: 400
```
Manifestを以下のように変更する。
- `metadata.labels`を削除
- `metadata.name`を重複しないもの(ここではdemonew)に設定
- `spec.display_name`、`spec.domain_name`、`spec.name`も重複しないもの(ここではdemonew)に設定
- 対象Namespace(spec.match_conditions[x].namespace)を重複しないもの(ここではdemonew)に設定


applyする。
```sh
$ tanzu sm apply -f gns-demo-new.yaml
GlobalNamespace/demo-new created
```
ApplyするとすぐにUI上に反映される。
![1692758694099.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/21acc337-aaf5-6fb6-0ea3-890a8d00a45b.png)
GNSの設定を見ても、Manifestの内容が反映されていることが分かる。
![1692758734376.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/fde9244e-9286-9c21-2815-9c46279bc20d.png)

これにより、CLIによりGNSが作成できることが確認できた。
次に、設定を変更してClusterを削除してみる。
以下のようにManifestを変更した。
```diff
diff -u gns-demo-new.yaml gns-demo-edit.yaml
--- gns-demo-new.yaml	2023-08-23 02:42:17.326440716 +0000
+++ gns-demo-edit.yaml	2023-08-23 02:46:48.730962698 +0000
@@ -23,12 +23,6 @@
     namespace:
       match: demonew
       type: EXACT
-  - cluster:
-      match: imurata-wdc-huge-wc
-      type: EXACT
-    namespace:
-      match: demonew
-      type: EXACT
   mtls_enforced: false
   name: demonew
   use_shared_gateway: true
```
applyしなおす。
```sh
$ tanzu sm apply -f gns-demo-edit.yaml
GlobalNamespace/demonew created
```
CLIの実行結果では`created`になっていたが、既存のリソースが変更されて、以下のように削除したClusterがGNSの対象外になったことが分かる。
![1692758875198.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/834d8c39-6de9-df80-d261-048088e8679c.png)


## まとめ
tanzu CLIを利用して、TSMのGNSを作成したり修正したりすることが確認できた。
これにより、新規クラスタ作成時にGNSにクラスタを追加したりするのをCI/CDから実行する見通しを得ることが出来た。
運用自動化を考える場合、APIを使っても良いが、まずは簡単なCLIを使って進めるのが良さそうだ。

## 備考：注意点
以下検証中に引っかかった点となる。

### `tanzu sm`コマンドでエラーが返る
`tanzu sm login`後、以下のようなエラーになることがある。
```sh
$ tanzu sm get spec globalnamespaces
{"message":"this instance is not allowed for this user","instanceUrl":"https://prod-2-1.nsxservicemesh.vmware.com"}
```
この場合は`tanzu sm login -s`で指定したサーバが正しいかを確認する。サーバが正しい場合でも原因不明だが発生したことがあり、その際はTokenを再発行して再度loginすることで回避出来た。

### GNSのManifestの`metadata.labels`に`projectId`を指定するとGNSのHealth Statusが`Unknown`になる

~~UI上のGNS名が`projectIdのvalue`.`GNS名`になり、Statusが`Unknown`となってGNSの中が見れなくなる。
おそらくGNSの命名規則違反に引っかかっていると思われる。`metadata.labels`に`projectId`を指定しないこと。~~

'23/10/22追記：
ここの値は任意ではなくて、UIのProject Configurationから確認できるProject Nameを指定する必要がある。
指定しなければ`default`が設定されるものと思われる。Project Nameを変更していなければ通常は設定する必要はない。


### UIから`Edit Global Namespace`を選択しても編集できない
Manifestでドメインを指定する際に、`-`など許可されていない文字列を入れると、applyは成功するが編集出来ない状態になる。
指定する文字列はUI側の制約に合わせた方がよい。