Kumaでクラスタ跨ぎの通信を試す

先日、KongのAPI Gatewayを触っていた時に、Kongが開発しているService Meshの[Kuma](https://kuma.io/)があることを知った。
機能を見るとTanzu Service Mesh(以下TSM)のGlobal Namespaceのように全体を俯瞰するControlPlaneを置いてクラスタ間通信を制御するようでちょっと気になったので、触ってみた時のメモ。

## Kumaとは
KumaはKongが中心となって開発されているService MeshのOSSで、CNCFのSandbox Projectにもなっている。
[2022/5のCNCFの調査](https://www.cncf.io/wp-content/uploads/2022/05/CNCF_Service_Mesh_MicroSurvey_Final.pdf)で「利用中、もしくは予定しているService Mesh製品は何か？」というアンケートではKumaの名前がなかったので、Service Mesh製品としてはマイナーな部類に入る。
アーキテクチャとしては以下のように複数のKubernetes/VMクラスタを跨いだマルチゾーン展開が出来、全体を見るControlPlaneが存在するようで、これがTanzu Service Meshにちょっと似ている。
![1704246211063.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/d96be9e7-f9e3-728b-6484-d452f34a1b7d.png)
（[Overview of Kuma](https://kuma.io/docs/2.5.x/introduction/overview-of-kuma/)より引用）

なおKumaではKubernetesにMeshを導入するKubernetes Modeと、非Kubernetes環境でもMeshを実現するUniversal Modeが用意されており、どちらの環境でもMeshが利用できるようになっている。
また、各ゾーン内のMeshはControlPlaneとDataPlaneで構成されていて、DataplaneにはEnvoyが利用されている。
![1704247140770.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/5e818586-3fc9-ac15-2378-a6ec9fabaf75.png)
（[Architecture](https://kuma.io/docs/2.5.x/introduction/architecture/)より引用）

一般的なService MeshのSidecarパターンと同じと考えるとよさそう。

## 検証

### 事前準備
以下を用意して検証した。
- K8sクラスタ3台（1台はGlobal ControlPlane用、2台はZone用）

ちなみに最初はVMも含めた構成でUniversal Modeも検証しようとしたのだが、検証を進めた結果、どうもDataPlaneごとにVMが必要っぽいように見えて用意するのが大変なので断念した。
### インストール
[Deploy a multi-zone global control plane](https://kuma.io/docs/2.5.x/production/cp-deployment/multi-zone/)を参考にインストールを進めていく。
やりたいのは以下の構成となる。
```mermaid
stateDiagram-v2
    val1: K8s Zone1
    val2: K8s Zone2
    val3: K8s Global
    val1 --> val3
    val2 --> val3
```


#### Global ControlPlaneの構築
まずKubernetes上にGlobal ControlPlaneを構築する。
インストール方法は独自のCLIである[kumactl](https://kuma.io/docs/2.5.x/production/install-kumactl/)を使った方法と、Helmによるインストール方法が用意されているが、ここでは慣れたHelmで進める。
インストール時、`controlPlane.mode`で`global`を指定することで、Global ControlPlaneとしてKumaをデプロイ出来る。
Global ControlPlaneのUIに頻繁にアクセスするため、ここではServiceを`type: LoadBalancer`で公開してデプロイした。
```sh
helm repo add kuma https://kumahq.github.io/charts
helm upgrade -i --create-namespace --namespace kuma-system \
  --set "controlPlane.mode=global" \
  --set "controlPlane.service.type=LoadBalancer" \
  kuma kuma/kuma
```
インストールが成功すると、ControlPlaneが展開される。
```sh
$ kubectl get pod -n kuma-system
NAME                                     READY   STATUS    RESTARTS   AGE
pod/kuma-control-plane-679574bbb-zk98x   1/1     Running   0          12s
```
ModeをGlobalにした場合にStandalone Modeと異なるのは、Serviceに`kuma-global-zone-sync`が作成され、External-IPでIPが公開される点である。
このIPを別のクラスタ上にKuma ControlPlaneを展開する際に`controlPlane.kdsGlobalAddress=<IP:5686>`で指定して、Global ControlPlaneと繋いでいく。
なお、通常DataPlaneはPodかNamespaceに`kuma.io/sidecar-injection: enabled`というラベルを付与するとサイドカーとして自動的に付加されるが、Global ControlPlaneで展開したクラスタでは`kuma.io/sidecar-injection: enabled`をつけてもDataPlaneは展開されず、ControlPlane専用で動くようになっている。

マルチゾーン(クラスタ間通信)を利用するにはmTLSが必須なため、Global ControlPlaneのデプロイ後、以下を実行してdefaultのMesh設定を変更してmTLSを有効化する。
```yaml
cat << EOF | kubectl apply -f -
apiVersion: kuma.io/v1alpha1
kind: Mesh
metadata:
  name: default
spec:
  routing:
    zoneEgress: true
  mtls:
    enabledBackend: ca-1
    backends:
      - name: ca-1
        type: builtin
        dpCert:
          rotation:
            expiration: 1d
        conf:
          caCert:
            RSAbits: 2048
            expiration: 10y
EOF
```

#### K8s ZoneのControlPlaneの構築
Global ControlPlaneが提供する`kuma-global-zone-sync`が公開しているIPを指定して、HelmでKubernetesクラスタに展開する。
```sh
GLOBAL_IP="10.220.35.73"
ZONE_NAME="tkgs-cluster-2"
helm install --create-namespace --namespace kuma-system \
  --set "controlPlane.mode=zone" \
  --set "controlPlane.zone=$ZONE_NAME" \
  --set "ingress.enabled=true" \
  --set "controlPlane.kdsGlobalAddress=grpcs://${GLOBAL_IP}:5685" \
  --set "controlPlane.tls.kdsZoneClient.skipVerify=true" \
  kuma kuma/kuma
```
Ingressを有効にすることで、`type: LoadBalancer`で`kuma-ingress`というServiceが作成され、外部から接続可能となる。
```sh
$ kubectl get svc -n kuma-system
NAME                 TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)                                                AGE
kuma-control-plane   ClusterIP      10.97.3.51       <none>         5680/TCP,5681/TCP,5682/TCP,443/TCP,5676/TCP,5678/TCP   2d6h
kuma-ingress         LoadBalancer   10.103.209.149   10.220.35.74   10001:30597/TCP                                        2d6h
```

なお、ModeをZoneにしているため、こちらではNamespaceかPodのラベルに`kuma.io/sidecar-injection: enabled`があればDataPlaneがサイドカーとしてInjectされるので試してみる。
以下のサンプルのManifestを動かしてみる。
```yaml
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
        kuma.io/sidecar-injection: enabled
    spec:
      containers:
      - image: nginx
        name: nginx
EOF
```
確認する。
```sh
$ kubectl get pod
NAME                     READY   STATUS        RESTARTS   AGE
nginx-67c9776b8b-bvqhs   2/2     Running       0          13s
```
コンテナ数が2となっており、サイドカー入りのPodが作成されることが確認できた。

次にもう1つのZone用クラスタにコンテキストを切り替え、同じように展開する。
なお、こちらでは動作確認用にEgressを有効にし、こちらのクラスタから先程のクラスタへの通信が行えるようにする。
```sh
ZONE_NAME="tkgs-cluster-3"
helm upgrade -i --create-namespace --namespace kuma-system \
  --set "controlPlane.mode=zone" \
  --set "controlPlane.zone=$ZONE_NAME" \
  --set "ingress.enabled=true" \
  --set "egress.enabled=true"　\
  --set "controlPlane.kdsGlobalAddress=grpcs://${GLOBAL_IP}:5685" \
  --set "controlPlane.tls.kdsZoneClient.skipVerify=true" \
  kuma kuma/kuma
```

展開後、Global ControlPlaneのService`kuma-control-plane`の5681ポートにブラウザで接続すると、それぞれのクラスタが確認できる。
![1704441962254.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/9bdb6467-3596-2be0-6631-39ec937558cc.png)

### 動作確認
マルチゾーンでの通信確認のため、最初のK8sのZoneで立ち上げたnginxに対し、2番目のZoneから通信する。
最初のZoneのnginxのServiceを作成する。
```sh
kubectl expose deploy nginx --port 80
```
次に2番めのZoneでアクセス元となるPodを起動する。mTLSで通信しないと接続できないため、アクセス元の方もサイドカーをInjectionするようにラベルに`kuma.io/sidecar-injection: enabled`を設定して起動する。
```yaml
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: centos
  name: centos
spec:
  replicas: 1
  selector:
    matchLabels:
      app: centos
  template:
    metadata:
      labels:
        app: centos
        kuma.io/sidecar-injection: enabled
    spec:
      containers:
      - command:
        - sleep
        - infinity
        image: centos
        name: centos
EOF
```
Podが起動したら、早速アクセスする。
アクセス先については`kind: ServiceInsight`というリソースを確認することで確認できる。
```yaml
$ kubectl get serviceinsight -o yaml all-services-default
apiVersion: kuma.io/v1alpha1
kind: ServiceInsight
mesh: default
：（省略）
    nginx_default_svc_80:
      dataplanes:
        online: 1
      issuedBackends:
        ca-1: 1
      status: online
```
ここだと`nginx_default_svc_80`がアクセス先となる。
これに`.mesh`をつけて、対応ポートを指定すればアクセスできる。
```html
$ kubectl exec -it centos-6d7594d4c9-445rl  -- curl nginx_default_svc_80.mesh:80
<!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>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
```
クラスタを跨いでアクセスすることが出来た。
なお、アクセス先はUIからも確認できる。
![1704445571697.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/5e532028-7e2b-b6aa-523a-44dcc7ff2df2.png)

なお、メトリクスに関しては別途[Prometheusなどをインストールする必要がある](https://kuma.io/docs/2.5.x/explore/observability/)。
kumactlを使うことでKuma向けに設定されたPrometheus、Jaeger、Loki、Grafanaが簡単にインストール出来るようになっている。
```sh
kumactl install observability | kubectl apply -f -
```
ただ、インストールするだけではメトリクスが見えず、[この辺](https://kuma.io/docs/2.5.x/policies/traffic-metrics/)などの設定を入れてメトリクスを公開する必要がある模様。
今回は時間の都合上断念した。


## 感想
簡単にクラスタ跨ぎのService Meshが作成でき、かつUIが最初から提供され、各クラスタの状態が確認できるのは便利だった。
一方で、TSMに慣れてるとそれでも導入が少し面倒と感じたり、UIもKialiみたいなネットワークトポロジーを表示する機能はなく、ダッシュボードも別途用意する必要がある点ではOOTBなTSMが流石だなと感じた。
Istioの面倒さとTSMの簡単さの中間にあるService Meshというところだろうか。
OSSとしては十分良かったので、お金を掛けず楽してService Meshを使いたい人は選択肢としてあっていいと思う。