NSX ALB 21.1 と TKG 1.6 の組合せで Ingress を構成してみる

# はじめに

今回は要素が盛り沢山なので、関連するソフトウェアをはじめに紹介します。


## VMware NSX Advanced Load Balancer (NSX ALB)

NSX ALB は、クラウドネイティブ時代にピッタリなソフトウェアベースのロードバランサーです。旧 Avi Networks の技術を活用しており、マルチクラウド環境においても、柔軟にスケールし、かつ WAF (Web Application Firewall) や GSLB (Global Server Load Balancing) など、豊富な機能を備えていることが特徴です。
また、トラヒックの可視化や分析の機能が非常に優れており、個人的に、とても好きなソリューションです。

https://docs.vmware.com/jp/VMware-NSX-Advanced-Load-Balancer/index.html


## VMware Tanzu Kubernetes Grid (TKG)

TKG は、vSphere with Tanzu との区別のため、Tanzu Kubernetes Grid multicloud (TKGm) と呼称されることもありますが、マルチクラウド環境において、OSS Kubernetes 準拠で、かつ、シンプルな操作でライフサイクル管理できる Kubernetes ソリューションです。
非常に省リソースな環境でも動作しますし、コンテナ レジストリやユーザー認証の連携など、豊富な拡張機能を提供しています。

https://docs.vmware.com/jp/VMware-Tanzu-Kubernetes-Grid/index.html


## NSX ALB + TKG のデプロイ方法

NSX ALB と TKG を組合せた際のデプロイ方法については、VMware Japan Blog でも紹介されています。
スクリーンショットを含めて、分かりやすく紹介されているので、一見、ボリュームがあるように感じられますが、プロダクションレディで、複数の Kubernetes クラスタを運用管理できるにも関わらず、これだけのステップで Kubernetes 環境が展開できるのは、なかなかシンプルで良いんじゃないかな、と思っています。

https://blogs.vmware.com/vmware-japan/2022/03/tkg-1-5-1-install.html

## Avi Kubernetes Operator (AKO)

NSX ALB を使って Kubernetes のコンテナネットワークを実現する際、NSX ALB と Kubernetes の橋渡しを実現しているのが、AKO というオープンソース ソフトウェア (OSS) です。

https://github.com/vmware/load-balancer-and-ingress-services-for-kubernetes

![](https://github.com/vmware/load-balancer-and-ingress-services-for-kubernetes/blob/master/ako_arch.png?raw=true)

AKO 自体は、Kubernetes クラスタ上において Pod として動作しており、Kubernetes 上で各種リソースの制御情報の更新を検知して、NSX ALB Controller に更新を連携し、コンテナ向けに様々なロードバランシング機能を提供します。
L4 ロードバランシングはもちろん、L7 ロードバランシング (Ingress) や、WAF (Web Application Firewall) などのセキュリティも、Kubernetes API を通じて制御することが可能です。

今回は、この仕組みを使い込んでいくための入り口として、NSX ALB と TKG の組合せにおいて、Ingress を構成してみようと思います。


# 前提

前述の VMware Japan Blog の記事を参考にしていただきつつ、基本は TKG の公式ドキュメントに従って、vSphere 環境に TKG の Management Cluster がデプロイされていることを想定しています。

https://docs.vmware.com/en/VMware-Tanzu-Kubernetes-Grid/1.6/vmware-tanzu-kubernetes-grid-16/GUID-mgmt-clusters-vsphere.html

また、NSX ALB では、Ingress リソースの DNS 名前解決に、NSX ALB の DNS Provider (DNS サーバー) を利用することが可能です。クライアント端末は、NSX ALB を DNS サーバーとして利用することで、Ingress の払出しに合わせて、自動で名前解決できるようになりますので、事前に構成しておくと非常に便利です。
この NSX ALB の DNS Provider の構成に関しては、下記の記事で紹介しています。

https://qiita.com/iwaseyusuke/items/fc5730fb30de5439b591

なお、利用している製品のバージョンは、下記の通りです。

* NSX Advanced Load Balancer 21.1.4-2p3 (Build 9210)
* Tanzu Kubernetes Grid 1.6
* vCenter 7.0 Update 3h (Build 20395099)
* ESXi 7.0 Update 3g (Build 20328353)


# 手順

## Management Cluster に対する AKODeploymentConfig の設定

まず、Management Cluster に Admin 権限でアクセスできるコンテキストでの操作が必要となります。

```sh
kubectl config use-context management-admin@management
```

この状態で、`AKODeploymentConfig` と呼ばれるリソースを用いて、実際に Ingress を利用する Workload Cluster を展開する際の事前設定を入れ込んで置きます。

```yaml: npl-enabled.yaml
apiVersion: networking.tkg.tanzu.vmware.com/v1alpha1
kind: AKODeploymentConfig
metadata:
  name: npl-enabled
spec:
  adminCredentialRef:
    name: avi-controller-credentials
    namespace: tkg-system-networking
  certificateAuthorityRef:
    name: avi-controller-ca
    namespace: tkg-system-networking
  cloudName: Default-Cloud
  clusterSelector:
    matchLabels:
      npl-enabled: "true"
  controlPlaneNetwork:
    cidr: xxx.xxx.xxx.xxx/xx
    name: XXXX-Network
  controller: xxx.xxx.xxx.xxx
  dataNetwork:
    cidr: xxx.xxx.xxx.xxx/xx
    name: XXXX-Network
  extraConfigs:
    cniPlugin: antrea
    disableStaticRouteSync: false
    ingress:
      disableIngressClass: false
      nodeNetworkList:
        - cidrs:
          - xxx.xxx.xxx.xxx/xx
          networkName: XXXX-Network
      serviceType: NodePortLocal
      shardVSSize: MEDIUM
  serviceEngineGroup: Default-Group
```

* `spec.cloudName` および `spec.serviceEngineGroup`
    * TKG の Management Cluster を展開する際、`tanzu` CLI から起動したウィザードの中で指定した NSX ALB のクラウド名と、Service Engine Group 名です。
* `spec.clusterSelector`
    * Workload Cluster を作成した後、この設定を有効化する対象のクラスタを指定するためのラベルセレクタを定義しています。
    * 今回の例では `npl-enabled="true"` でラベル付けされたクラスタに対して、この設定が有効化されます。
* `spec.controlPlaneNetwork`
    * 名前の通りですが、Kubernetes のコントロールプレーンのネットワークが接続する IP アドレス帯と、実際に Node VM が接続すべき Port Group 名を指定します。
* `spec.controller`
    * NSX ALB Controller の IP (Controller クラスタの VIP) を指定します。
* `spec.dataNetwork`
    * こちらも名前の通りですが、Kubernetes のデータプレーンのネットワークを指定します。
    * TKG 1.6 の構成に依存しますが、`spec.controlPlaneNetwork` と同じ設定になる場合があります。
* `spec.extraConfigs.cniPlugin` および `spec.extraConfigs.ingress.serviceType`
    * 後述しますが、今回の構成オプションの場合には、CNI として Antrea が必須であり、Ingress のタイプとして `NodePortLocal` を選択しています。
* `spec.extraConfigs.ingress.nodeNetworkList`
    * Ingress として利用するネットワークを指定します。
    * こちらも、TKG 1.6 の構成に依存しますが、`spec.controlPlaneNetwork` と同じ設定になる場合があります。

マニフェストが作成できたら、これを Management Cluster に適用します。

```sh
kubectl apply -f npl-enabled.yaml
```


## Workload Cluster の展開

Management Cluster をデプロイする際に使用した Cluster Config ファイルをベースに Workload Cluster を展開していきます。
この Cluster Config ファイルは `tanzu` CLI からウィザードを起動して Managemet Cluster をデプロイした場合、下記のディレクトリに配置されています。

```
$ ls .config/tanzu/tkg/clusterconfigs/
xxxxxxxxxx.yaml  # 10桁のランダムなファイル名前
```

このファイルをコピーし、いくつかのパラメータの修正をします。

```yaml: tkc01.yaml
...(snip)...
# TKG 1.6 での不具合を回避するため、下記をコメントアウト。
#AVI_LABELS: |
#    '': ''
...(snip)...
# ファイル内でクラスタ名を指定すると、コマンド実行時に引数が少なくて済む。
CLUSTER_NAME: tkc01
...(snip)...
# 特に K8s クラスタのコントロールプレーンの IP に指定が無ければ、コメントアウト。
#VSPHERE_CONTROL_PLANE_ENDPOINT: xxx.xxx.xxx.xxx
...(snip)...
# 今回の構成オプションの場合、下記の設定の追記が必要。
ANTREA_NODEPORTLOCAL: "true"
```

作成した Cluster Config を使って、Workload Cluster をデプロイします。

```sh
tanzu cluster create -f tkc01.yaml
```

Workload Cluster がデプロイできたら、`AKODeploymentConfig` の中で設定したラベル定義に合うように、クラスタにラベル付けしておきます。これによって、事前に構成した `AKODeploymentConfig` の設定が、クラスタに対して反映されます。

```sh
kubectl label cluster tkc01 npl-enabled="true"
```


## Workload Cluster へのアクセス

まず、下記のコマンドで、作成した Workload Cluster にアクセスするための kubeconfig を取得します。

```sh
tanzu cluster kubeconfig get tkc01 --admin
```

これによって、`~/.kube/config` に、作成した Workload Cluster へのコンテキスト情報が格納されたので、`kubectl` コマンドを使って、コンテキストを切り替えます。

```sh
kubectl config use-context tkc01-admin@tkc01
```

Workload Cluster の展開の最後の手順で、ラベル付けをしたことを契機に、設定が伝搬される仕組みですが、迅速に設定を反映させるための Tips として、AKO の Pod を手動で削除し、強制的に Pod を再作成させるという方法があります。あるいは、確実に設定を反映させるための手段として、念押しで実施しても良いかも知れません。

```sh
kubectl delete pod ako-0 -n avi-system
```

ここまでの手順によって、Ingress Class に `avi-lb` が登録されていることが確認できます。

```sh
$ kubectl get ingressclass
NAME     CONTROLLER              PARAMETERS   AGE
avi-lb   ako.vmware.com/avi-lb   <none>       1h
```


# 動作確認

実際にサンプルのアプリをデプロイして、Ingress の動作を確認してみます。

面白みがあるマニフェストではないですが、下記のような内容を用意しました。

```yaml: nginx-with-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx
spec:
  ingressClassName: avi-lb
  rules:
    - host: nginx.ako.test
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nginx
                port:
                  number: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  type: ClusterIP
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
      imagePullSecrets:
      - name: docker-hub
```

* Ingress
    * `spec.ingressClassName` で `avi-lb` を指定しています。
* Service
    * `spec.type` として `ClusterIP` を指定している点に注意してください。
    * `LoadBalancer` を指定した場合、Ingress 用の NSX ALB の Virtual Service (VS) と、L4 LB 用の VS を二重で通過する設定となってしまい、Ingress 用の VS が上手く構成されません。
* Deployment
    * 参考までに、`spec.template.spec.imagePullSecrets` を指定していますが、Docker Hub の Rate Limit を回避するために入れてあります。
    * 手元の端末で Docker Hub にログインした後、Secret を作成してください。
      ```sh
      # Docker Hub にログイン
      docker login

      # Secret の作成
      kubectl create secret generic docker-hub \
        --from-file=.dockerconfigjson=${HOME}/.docker/config.json \
        --type=kubernetes.io/dockerconfigjson
      ```

作成したマニフェストを使って nginx をデプロイします。

```sh
$ kubectl apply -f nginx-with-ingress.yaml
ingress.networking.k8s.io/nginx created
service/nginx created
deployment.apps/nginx created
```

NSX ALB Controller の UI で [Applications] > [Dashborad] のページに進み、表示形式を [View VS Tree] とすると、下記のようなイメージで Ingress が構成されていることが分かります。

![Screen Shot 2022-11-23 at 0.27.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245234/96cba4d2-9035-d05d-30a1-5ae874fb98e6.png)

もし、NSX ALB で DNS Provider を構成し、クライアントがその DNS を使っていれば、`nginx.ako.test` が自動で名前解決されますので、クライアントからブラウザアクセスすれば、それが確認できます。

![Screen Shot 2022-11-23 at 0.52.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/245234/99450745-5ae4-b54e-33af-be65533914dd.png)


# 参考

今回の記事では、詳細には触れませんでしたが、NSX ALB と TKG 1.6 の組合せで、Ingress を構成する場合には、下記の 4つの構成オプションがあります。

https://docs.vmware.com/en/VMware-Tanzu-Kubernetes-Grid/1.6/vmware-tanzu-kubernetes-grid-16/GUID-networking-configure-l7.html

> After you install NSX Advanced Load Balancer using the steps in Install NSX Advanced Load Balancer, you can configure L7 ingress for your workload clusters using one of the following options:
> * L7 ingress in ClusterIP mode
> * L7 ingress in NodePortLocal mode
> * L7 ingress in NodePort mode
> * NSX ALB L4 ingress with Contour L7 ingress

このうち、今回の記事で利用したのは、L7 ingress in NodePortLocal mode の手順を実践しています。この構成オプションの場合、CNI が Antrea (TKG のデフォルト) が必要ですが、NodePortLocal を用いることで、コンテナネットワーク内でのトラヒックが最適化され、余計なトラヒックの発生を防ぐことができます。

他のオプションを含めて、NSX ALB が提供するコンテナネットワークに関しては、VMware Japan Blog の記事で、分かりやすく解説されていますので、ぜひご参考にしていただければと思います。

https://blogs.vmware.com/vmware-japan/2022/06/nsx-alb-ako-features-1.html