NSX環境におけるTanzu Kubernetes GridのRoutable Pod

本記事は[vExperts Advent Calendar 2021](https://adventar.org/calendars/6689)の12月18日分の投稿です。

## はじめに

NSX-Tで構成されるvSphere環境のTanzu Kubernetes GridのワークロードクラスターではAntrea CNIを利用して、Podに対してルーティング可能なIPアドレスを構成し、NATを利用せずにPodにアクセスする(Routable Pod)ことが可能になります。Tanzu Kubernetes Grid環境、vSphere with Tanzu環境ともRoutable Podを利用することが可能です。以下のドキュメント設定方法が記載されています。

- Tanzu Kubernetes Grid
	- [Customize Tanzu Kubernetes Cluster Networking](https://docs.vmware.com/en/VMware-Tanzu-Kubernetes-Grid/1.4/vmware-tanzu-kubernetes-grid-14/GUID-tanzu-k8s-clusters-networking.html#deploy-pods-with-routable-nonat-ip-addresses-nsxt-4)
	- [Tanzu CLI Configuration File Variable Reference - NSX-T Pod Routing](https://docs.vmware.com/en/VMware-Tanzu-Kubernetes-Grid/1.4/vmware-tanzu-kubernetes-grid-14/GUID-tanzu-config-reference.html#nsxt-pod-routing)
- vSphere with Tanzu
	- [Create and Configure a vSphere Namespace](https://docs.vmware.com/en/VMware-vSphere/7.0/vmware-vsphere-with-tanzu/GUID-177C23C4-ED81-4ADD-89A2-61654C18201B.html#procedure-2)
	- [Configuring a Tanzu Kubernetes Cluster with a Routable Pod Network Using the v1alpha2 API](https://docs.vmware.com/en/VMware-vSphere/7.0/vmware-vsphere-with-tanzu/GUID-2BC5CC9D-7396-4700-A698-3C97A882AE23.html)


## Tanzu Kubernetes Gridで構成してみる

マニュアルの「Deploy Pods with Routable, No-NAT IP Addresses (NSX-T)」を参考にTanzu Kubernetes Grid環境のワークロードクラスターでRoutable Podを利用してみます。

### NSX-Tの要件

NSX-T側では予めワークロードクラスターを接続するネットワーク(NSX-Tのセグメント)を作成し、Tier1 Gateway配下にぶら下げておく必要があります。

- Tier0 Gatewayを既存ネットワークに対して接続
- Tanzu Kubernetes Clusterを接続するTier1 Gatewayの作成
	- Tier0 Gatewayに接続
	- ルートアドバタイズとして「すべてのスタティックルート」と「接続されている全てのセグメントおよびサービスポート」を有効化する
- Tanzu Kubernetes Gridを接続するセグメントの作成
	- ゲートウェイアドレスの指定
	- 上記で作成したTier1 Gatewayに接続
	- Tanzu Kubernernets Clusterのノードが利用するDHCPサーバーの構成


### ネットワーク構成

Routable Podを有効化すると、NSX-TのTier1 Gateway配下にワークロードクラスターが作成され、Tier1 GatewayにPod CIDR向けのStatic Routeが追加されます。T1 Gatewayに追加されたStatic Routeは上位のTier0 Gatewayにアドバタイズされ、外部のネットワークからPodのIPアドレスに対して直接通信することが可能になります。

![Kobito.rBvlU7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27668/613b3a45-a1bc-c877-9ebd-6f3d6e2eeea0.png "Kobito.rBvlU7.png")


### マネジメントクラスターの作成

今回はロードバランサーとしてNSX-ALBを利用するため、以下のように`AVI_`パラメータを指定してマネジメントクラスターを作成します。マネジメントクラスターはRoutable Podを利用しないため、`CLUSTER_CIDR`として`100.96.0.0/11`を指定しています。

```yaml
AVI_CA_DATA_B64: XXXXXXXX...
AVI_CLOUD_NAME: Default-Cloud
AVI_CONTROL_PLANE_HA_PROVIDER: "false"
AVI_CONTROLLER: 192.168.30.250
AVI_DATA_NETWORK: tanzu-overlay
AVI_DATA_NETWORK_CIDR: 172.16.128.0/21
AVI_ENABLE: "true"
AVI_LABELS: ""
AVI_PASSWORD: <encoded:XXXXXXXXXXXX>
AVI_SERVICE_ENGINE_GROUP: Default-Group
AVI_USERNAME: admin
CLUSTER_CIDR: 100.96.0.0/11
CLUSTER_NAME: mgmt
CLUSTER_PLAN: dev
ENABLE_CEIP_PARTICIPATION: "true"
ENABLE_MHC: "true"
IDENTITY_MANAGEMENT_TYPE: none
INFRASTRUCTURE_PROVIDER: vsphere
SERVICE_CIDR: 100.64.0.0/13
TKG_HTTP_PROXY_ENABLED: "false"
VSPHERE_CONTROL_PLANE_ENDPOINT: 172.16.135.240
VSPHERE_DATACENTER: /DC
VSPHERE_DATASTORE: /DC/datastore/NFS
VSPHERE_FOLDER: /DC/vm
VSPHERE_NETWORK: tanzu-overlay
VSPHERE_PASSWORD: <encoded:XXXXXXXXXXXX>
VSPHERE_RESOURCE_POOL: /DC/host/CL/Resources/Tanzu
VSPHERE_SERVER: 10.44.55.100
VSPHERE_SSH_AUTHORIZED_KEY: ssh-rsa AAAAB...
VSPHERE_INSECURE: true
VSPHERE_USERNAME: administrator@vsphere.local
```

以下のコマンドでマネジメントクラスターを作成します。

```bash
$ tanzu management-cluster create -f management.yaml
```

### ワークロードクラスターの作成

マネジメントクラスターの作成が完了したら、マネジメントクラスターを利用してRoutable Podが利用可能なワークロードクラスターを作成します。ワークロードクラスターの作成には以下の構成ファイルを利用します。

```yaml
CLUSTER_NAME: cl1
CLUSTER_PLAN: dev
INFRASTRUCTURE_PROVIDER: vsphere
ENABLE_CEIP_PARTICIPATION: true
ENABLE_AUDIT_LOGGING: true
SERVICE_CIDR: 100.64.0.0/13
AVI_CONTROL_PLANE_HA_PROVIDER: false
DEPLOY_TKG_ON_VSPHERE7: true
ENABLE_TKGS_ON_VSPHERE7: false
VSPHERE_CONTROL_PLANE_ENDPOINT: 172.16.135.250
VSPHERE_DATACENTER: /DC
VSPHERE_DATASTORE: /DC/datastore/NFS
VSPHERE_FOLDER: /DC/vm
VSPHERE_NETWORK: tanzu-overlay
VSPHERE_PASSWORD: <encoded:xxxxxxxxxxxx>
VSPHERE_RESOURCE_POOL: /DC/host/CL/Resources/Tanzu
VSPHERE_SERVER: 10.44.55.100
VSPHERE_SSH_AUTHORIZED_KEY: ssh-rsa AAAA...
VSPHERE_INSECURE: true
VSPHERE_USERNAME: administrator@vsphere.local
VSPHERE_STORAGE_POLICY_ID: "tkg"
CONTROLPLANE_SIZE: large
WORKER_SIZE: large
WORKER_MACHINE_COUNT: 2
OS_NAME: "photon"
ENABLE_MHC: true
MHC_UNKNOWN_STATUS_TIMEOUT: 5m
MHC_FALSE_STATUS_TIMEOUT: 12m
CLUSTER_CIDR: 172.16.136.0/21
NSXT_POD_ROUTING_ENABLED: "true"
NSXT_MANAGER_HOST: 10.44.55.101
NSXT_ROUTER_PATH: /infra/tier-1s/tanzu-cl1
NSXT_USERNAME: admin
NSXT_PASSWORD: NSXTPASSWORD
NSXT_ALLOW_UNVERIFIED_SSL: "true"
```

ポイントは、`NSXT_`パラメータの指定と`CLUSTER_CIDR`の指定です。`CLUSTER_CIDR`には`/24`以上のCIDRを指定します。作成するクラスターの各ノードには`CLUSTER_CIDR`から`/24`単位でpodCIDRが割り当てられます。作成するノード数に注意して`CLUSTER_CIDR`を指定する必要があります。

以下のコマンドでマネジメントクラスターを作成します。

```bash
$ tanzu cluster create cl1 -f clsuter01.yaml
```

### Podの作成と動作確認

作成されたクラスターに接続して確認するとノードのIPアドレスはDHCPから払い出されています。

```bash
$ tanzu cluster kubeconfig get cl1 --admin
$ kubectl config use-context cl1-admin@cl1
$ kubectl get node -o wide
NAME                        STATUS   ROLES                  AGE   VERSION            INTERNAL-IP      EXTERNAL-IP      OS-IMAGE                 KERNEL-VERSION   CONTAINER-RUNTIME
cl1-control-plane-nh9n7     Ready    control-plane,master   20m   v1.21.2+vmware.1   172.16.128.63    172.16.128.63    VMware Photon OS/Linux   4.19.198-1.ph3   containerd://1.4.6
cl1-md-0-5789bbcb76-hbnqz   Ready    <none>                 17m   v1.21.2+vmware.1   172.16.128.126   172.16.128.126   VMware Photon OS/Linux   4.19.198-1.ph3   containerd://1.4.6
cl1-md-0-5789bbcb76-vvrjv   Ready    <none>                 14m   v1.21.2+vmware.1   172.16.128.35    172.16.128.35    VMware Photon OS/Linux   4.19.198-1.ph3   containerd://1.4.6
```

各ノードのpodCIDRを確認すると、クラスター作成時に指定した`CLUSTER_CIDR: 172.16.136.0/21`から各ノードに対して`/24`単位で割り当てられています。


```bash
$ kubectl get node -o=jsonpath='{range .items[*]}{.spec.podCIDR}{"\n"}{end}'
172.16.136.0/24
172.16.137.0/24
172.16.138.0/24
```

T1 GatewayのStatic Routeを確認すると、各podCIDRのNextHopとしてワークロードクラスターのノードが追加されています。複数のワークロードクラスターを構成する場合、各ワークロードクラスターの`CLUSTER_CIDR`として重複しないアドレス範囲を指定する必要があります。以下の例はワークロードクラスターとしてcl1とcl2を作成し、それぞれの`CLUSTER_CIDR`として`172.16.136.0/21`と`172.16.144.0/21`を割り当てた場合の状態です。

![Kobito.CEujUn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27668/d1f94416-680b-1b7b-09c5-3ccad845d6cf.png "Kobito.CEujUn.png")

Podを作成し、確認すると各PodのIPアドレスはpodCIDRから払い出されています。

```bash
$ kubectl create deploy nginx --image=nginxdemos/hello:plain-text
$ kubectl scale deploy nginx --replicas=4
$ kubectl get pod -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP              NODE                        NOMINATED NODE   READINESS GATES
nginx-6bbf47444b-9p4lx   1/1     Running   0          24s   172.16.138.5    cl1-md-0-5789bbcb76-vvrjv   <none>           <none>
nginx-6bbf47444b-j2wsf   1/1     Running   0          20s   172.16.137.9    cl1-md-0-5789bbcb76-hbnqz   <none>           <none>
nginx-6bbf47444b-ws4z7   1/1     Running   0          20s   172.16.138.6    cl1-md-0-5789bbcb76-vvrjv   <none>           <none>
nginx-6bbf47444b-x5mkc   1/1     Running   0          20s   172.16.137.10   cl1-md-0-5789bbcb76-hbnqz   <none>           <none>
```

各PodのIPアドレスに直接curlを実行すると、Podに対してアクセスすることができました。

```
$ for i in $(kubectl get pod -o=jsonpath='{.items[*].status.podIP}'); do curl -s $i; done | grep 'Server address'
Server address: 172.16.139.5:80
Server address: 172.16.137.9:80
Server address: 172.16.139.6:80
Server address: 172.16.137.10:80
```

### Antrea CNIの設定確認

ワークロードクラスターのantreaの設定を確認してみます。kube-systemネームスペースにConfigMapとしてantrea-configが作成されています。内容を確認すると、`antrea-agent.conf`で`trafficEncapMode: noEncap`、`noSNAT: true`が指定されています。

```bash
$ kubectl get configmap -n kube-system antrea-config-ctb8mftc58
NAME                       DATA   AGE
antrea-config-ctb8mftc58   3      73m

$ kubectl get configmap antrea-config-ctb8mftc58 -oyaml
apiVersion: v1
data:
  antrea-agent.conf: |
    featureGates:
      AntreaProxy: true
      EndpointSlice: false
      Traceflow: true
      NodePortLocal: false
      AntreaPolicy: true
      FlowExporter: false
      NetworkPolicyStats: false
    trafficEncapMode: noEncap
    noSNAT: true
```

### LoadBalancerの利用

LoadBalancer Serviceを作成し、NSX-ALBにVirtual Serviceが構成されてアクセスできることを確認します。

```bash
$ kubectl expose deploy nginx --port=80 --type=LoadBalancer

$ kubectl get service
NAME         TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)        AGE
kubernetes   ClusterIP      100.64.0.1       <none>         443/TCP        5h15m
nginx        LoadBalancer   100.69.231.126   172.16.129.2   80:30858/TCP   15s

$ while true; do curl -s 172.16.129.2|grep address; sleep 1; done
Server address: 172.16.138.6:80
Server address: 172.16.137.9:80
Server address: 172.16.138.5:80
Server address: 172.16.137.9:80
Server address: 172.16.137.10:80
```

NSX-ALB Controllerを確認すると、Service用のVirtual Serivceの負荷分散対象はワークロードクラスターのNodePortが構成されていました。[AKO自体はRoutable Podを負荷分散対象として利用することが可能](https://avinetworks.com/docs/ako/1.5/ako-design-and-deployment/#avi-se-placement--pod-network-reachability)ですが、TKGのオプションとして設定する方法を見つけることができませんでした。

![Kobito.D7YdC6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27668/306b3536-30d6-5900-fbbc-54dfa1b63965.png "Kobito.D7YdC6.png")

### ノードの追加・削除

TKGではクラスターのノードを簡単にスケールアウト・スケールインすることが可能です。ノードの追加と削除の際に、T1 GatewayのStatic Routeが追加・削除される様子を確認してみました。Static Routeの追加削除は、各ワークロードクラスターの`vsphere-cloud-controller-manager`が行っており、以下のログが出力されていました。なお、クラスター削除時は各ノードのStatic Routeは削除されないため手動でStatic Routeエントリを削除する必要があります。

- ノード追加時

```
I1216 05:40:06.080046       1 route_controller.go:193] Creating route for node cl1-md-0-5789bbcb76-vvrjv 172.16.139.0/24 with hint 1455b87f-8fbe-4cfd-b43c-fdfb822acfc7, throttled 564ns
I1216 05:40:10.107407       1 route_controller.go:213] Created route for node cl1-md-0-5789bbcb76-vvrjv 172.16.139.0/24 with hint 1455b87f-8fbe-4cfd-b43c-fdfb822acfc7 after 4.027359885s
I1216 05:40:10.107480       1 route_controller.go:303] Patching node status cl1-md-0-5789bbcb76-vvrjv with true previous condition was:nil
```

hintとして表示されているIDはNSX上に作成されるStatic RouteリソースのIDのPrefixとして利用されるため、IDにより追加されたリソースを検索することが可能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27668/cb7c70a7-ca6c-9ec6-cc50-609511a54d0e.png)



- ノード削除時

ノードの削除は古いものが優先して削除されるようです。

```
I1216 05:44:46.046675       1 route_controller.go:246] Deleting route a57a555e-e4eb-49ad-af74-e11a7d7a8ad8_172.16.138.0_24 172.16.138.0/24
I1216 05:44:46.485056       1 route_controller.go:250] Deleted route a57a555e-e4eb-49ad-af74-e11a7d7a8ad8_172.16.138.0_24 172.16.138.0/24 after 438.418613ms
```


## vSpehre with Tanzuで構成してみる

vSphere with Tanzu環境では、Supervisor Cluster有効時にNSX-T側の構成が完了しているため、TKGと比べるとシンプルにRoutable Podクラスターを構成することができます。ネームスペースのオプションで「クラスターネットワーク設定のオーバーライド」を有効にし、NATモードを無効化し、「名前空間ネットワーク」と「名前空間サブネットプリフィックス」を指定します。「名前空間ネットワーク」はワークロードクラスターとそのPod CIDRとして利用されるネットワークとして利用されます。「名前空間サブネットプリフィックス」はワークロードクラスターのノードに割り当てるIPアドレスのプリフィックスとして利用されますが、各ノードのPod CIDRはTKGと同じように/24で割り当てられます。

![Kobito.WZEqAW.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27668/4a055184-d2da-2377-dbf2-5136a3f6e841.png "Kobito.WZEqAW.png")


以下のようなマニフェストを利用してワークロードクラスターを作成することで、Routable Podを利用することが可能になります。

```yaml
apiVersion: run.tanzu.vmware.com/v1alpha2
kind: TanzuKubernetesCluster
metadata:
  name: routablepods
spec:
  topology:
    controlPlane:
      replicas: 3
      vmClass: best-effort-medium
      storageClass: k8s-policy
      tkr:
        reference:
          name: v1.21.2---vmware.1-tkg.1.ee25d55
    nodePools:
    - name: worker-nodepool-a1
      replicas: 3
      vmClass: best-effort-medium
      storageClass: k8s-policy
      tkr:
        reference:
          name: v1.21.2---vmware.1-tkg.1.ee25d55
  settings:
    storage:
      defaultClass: k8s-policy
    network:
      # antrea-nsx-routed is the required CNI for routable pods
      cni:
        name: antrea-nsx-routed
      services:
        cidrBlocks: ["10.97.0.0/24"]
      serviceDomain: tanzukubernetescluster.local
      # pods.cidrBlocks value must be empty when antrea-nsx-routed is the CNI
      pods:
        cidrBlocks:
```

作成したワークロードクラスターの動作を確認すると、ワークロードクラスターのAPI用のロードバランサーや、LoadBalancer Serivceは入力方向(Ingress)から払い出されています。

```bash
$ kubectl config get-clusters
NAME
10.44.186.2
172.16.90.2
supervisor-10.44.186.2

$ kubectl get node -o wide
NAME                                                    STATUS   ROLES                  AGE   VERSION            INTERNAL-IP    EXTERNAL-IP   OS-IMAGE                 KERNEL-VERSION       CONTAINER-RUNTIME
routablepods-control-plane-87fvv                        Ready    control-plane,master   32h   v1.21.2+vmware.1   172.16.96.38   <none>        VMware Photon OS/Linux   4.19.191-4.ph3-esx   containerd://1.4.6
routablepods-control-plane-s8wq4                        Ready    control-plane,master   32h   v1.21.2+vmware.1   172.16.96.34   <none>        VMware Photon OS/Linux   4.19.191-4.ph3-esx   containerd://1.4.6
routablepods-control-plane-t6htb                        Ready    control-plane,master   32h   v1.21.2+vmware.1   172.16.96.39   <none>        VMware Photon OS/Linux   4.19.191-4.ph3-esx   containerd://1.4.6
routablepods-worker-nodepool-a1-s6bsw-b89cf564b-jbs56   Ready    <none>                 32h   v1.21.2+vmware.1   172.16.96.36   <none>        VMware Photon OS/Linux   4.19.191-4.ph3-esx   containerd://1.4.6
routablepods-worker-nodepool-a1-s6bsw-b89cf564b-n5jh4   Ready    <none>                 32h   v1.21.2+vmware.1   172.16.96.35   <none>        VMware Photon OS/Linux   4.19.191-4.ph3-esx   containerd://1.4.6
routablepods-worker-nodepool-a1-s6bsw-b89cf564b-zs2nw   Ready    <none>                 32h   v1.21.2+vmware.1   172.16.96.37   <none>        VMware Photon OS/Linux   4.19.191-4.ph3-esx   containerd://1.4.6

$ kubectl get node -o=jsonpath='{range .items[*]}{.spec.podCIDR}{"\n"}{end}'
172.16.101.0/24
172.16.97.0/24
172.16.102.0/24
172.16.98.0/24
172.16.99.0/24
172.16.100.0/24

$ kubectl get pod -o wide
NAME                     READY   STATUS    RESTARTS   AGE   IP             NODE                                                    NOMINATED NODE   READINESS GATES
nginx-6bbf47444b-2b7gx   1/1     Running   0          54s   172.16.98.6    routablepods-worker-nodepool-a1-s6bsw-b89cf564b-jbs56   <none>           <none>
nginx-6bbf47444b-dhbxn   1/1     Running   0          54s   172.16.100.8   routablepods-worker-nodepool-a1-s6bsw-b89cf564b-zs2nw   <none>           <none>
nginx-6bbf47444b-lj2pb   1/1     Running   0          54s   172.16.100.9   routablepods-worker-nodepool-a1-s6bsw-b89cf564b-zs2nw   <none>           <none>
nginx-6bbf47444b-mtg7z   1/1     Running   0          58s   172.16.99.7    routablepods-worker-nodepool-a1-s6bsw-b89cf564b-n5jh4   <none>           <none>

$ kubectl get svc
NAME         TYPE           CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP      10.97.0.1    <none>        443/TCP        32h
nginx        LoadBalancer   10.97.0.31   172.16.90.3   80:30966/TCP   15s
supervisor   ClusterIP      None         <none>        6443/TCP       32h
```

## まとめ

vSphere環境でNSX-Tを利用すると、vSphere with TanzuやTanzu Kubernetes GridのワークロードクラスターでRoutable Podを利用することが可能になります。Routable Podを利用すると従来のVMと同じようにPodに対して直接通信することが可能になり、NATのオーバーヘッドを削減し、多様なアプリケーションの要件に対応することが可能になります。