TKG 2.1 + Multus で遊んでみる

Kubernetes (K8s) の世界において、基本的に Pod は、NIC を 1つしか持てません。Multus CNI は、Pod に複数の NIC を割り当てられるようにするための CNI (Container Network Interface) Plugin です。
これは、Multus が、他の CNI Plugin を呼び出すための "meta-plugin" として動作することによって実現されています。下記の公式リポジトリにあるアーキテクチャ図とかが、イメージとして分かりやすいかと思います。

https://github.com/k8snetworkplumbingwg/multus-cni

一般に、クラウドネイティブなアプリは、スケールアウトによって性能をスケールさせるため、Pod が 1つしか NIC を持てないからと言って、Pod 1つあたりのネットワーク性能が問題になるようなシーンは少ないかも知れません。
しかし、コンテナで実装されたネットワーク機能、CNF (Cloud Native Network Function) のユースケースにおいては、K8s の管理ネットワーク等とは別に、専用の高速ネットワークとして、2つ目、3つ目... の NIC が欲しくなったりするため、Multus の利用が有力視されるユースケースが存在します。

ただ、コンテナ自体がネットワーク機能を持つようなユースケースであるため、追加の NIC に割り当てられた IP アドレス等のリソース管理は、アプリが独自に行うことが想定されており、K8s の世界から追加のネットワークを管理する機能は、限定的であるのが現状です。
具体的には、例えば、「追加の NIC に割り当てられている IP アドレスを検出または管理 (IPAM) し、K8s の Service に対するエンドポイントとして、自動で更新し続けてくれる」というような機能は、現時点 (2023年4月) の Multus には実装されていません。[^1]

[^1]: [multus-service](https://github.com/k8snetworkplumbingwg/multus-service) として、Multus が追加した NIC についても、K8s の Service のような抽象化機能を実現しようとしているプロジェクトが存在しています。しかし、現時点 (2023年4月) においては、GitHub のページにもある通り、あくまで開発初期段階として扱う必要があるようです。

また、ややこしいですが、この追加の NIC は、Multus 自身が用意してくれるのではなく、Mutlus はあくまで「元々 K8s Node に刺さっている NIC を使う」というイメージになります。
確かに、macvlan や ipvlan を使って、1つの NIC を複数の NIC であるかのように扱うことはできます。しかし、例えば、K8s Node が VM として動作している場合において、Multus に設定を入れたら、「自動で VM に仮想 NIC を追加してくれる」という訳ではありません。
Muluts の役目は、あくまで「他の CNI Plugin を呼び出すまで」であり、呼び出される CNI Plugin にも NIC 自体を用意する機能は実装されていません。

とはいえ、Mutlus の提供してくれる技術としては、とても興味深いです。
今回は、Tanzu Kubernetes Grid (TKG) 2.1 において、Tanzu CLI でインストールできる拡張パッケージとして、Multus が提供されているので、これで遊んでみたいと思います。
また、2つ目の NIC に対する IP アドレス管理 (IPAM) として Whereabouts という CNI Plugin も提供されているので、Mutlus と組合せて、遊んでみます。


# 前提

利用しているソフトウェアのバージョンは、下記の通りです。

- Tanzu Kubernetes Grid 2.1.1
- tanzu-standard パッケージ 2.1.1

また、TKG としては Standalone Management Cluster として構成し、実際に Multus で遊ぶ Workload Cluster を 1つデプロイしている状況を想定しています。

:::note warn
**Workload Cluster のサイジングにおける注意**
Node 台数は、Multus でどこまで遊ぶか次第で調整、で良いかと思います。しかし、Node の CPU や Mem といったリソースのサイジングとしては `large` サイズ (4 CPUs, 16 GB memory, 40 GB disk) 以上が必要になります。
私が試した範囲では、どちらかと言うと CPU リソースが重要で、`small` や `medium` サイズ (2 CPUs) では、CPU リソース不足で Multus パッケージがインストールできませんでした。
:::


# 手順

基本は、Tanzu Kubernetes Grid 2.1 の公式ドキュメントに記載に従って、実施していきます。

https://docs.vmware.com/en/VMware-Tanzu-Kubernetes-Grid/2.1/using-tkg-21/workload-packages-cni.html

まず、`kubectl` コマンドを使って、これから Multus をインストールしていく TKG クラスタに、Context が切り替わっていることを確認します。

```sh
$ kubectl config get-contexts
CURRENT   NAME                          CLUSTER      AUTHINFO           NAMESPACE
          management-admin@management   management   management-admin
*         tkc01-admin@tkc01             tkc01        tkc01-admin
```

ここでは、`management` という名前の Management Cluster からデプロイした、`tkc01` という名前の Workload Cluster に、Multus をインストールしていきます。


## tanzu-standard パッケージの追加

TKG の拡張機能が提供されている `tanzu-standard` パッケージのリポジトリを登録します。

```sh
tanzu package repository add tanzu-standard --url projects.registry.vmware.com/tkg/packages/standard/repo:v2.1.1 --namespace tkg-system
```

バージョンについては、ご利用の TKG の ver に合わせて、選択してください。
登録済みのリポジトリを確認するコマンドで、↓正しく登録されているか確認します。

```sh
tanzu package repository list -A
```


```text:出力例
  NAMESPACE   NAME            SOURCE                                                                   STATUS
  tkg-system  tanzu-standard  (imgpkg) projects.registry.vmware.com/tkg/packages/standard/repo:v2.1.1  Reconcile succeeded
```


## 利用可能な Multus パッケージの確認

リポジトリを通じて、インストール可能な Multus パッケージの情報、主にバージョン情報について、確認していきます。

少し冗長ですが、順を追って紹介していくと、まず、登録済みのリポジトリを通じて、インストール可能なパッケージの一覧を確認します。

```sh
tanzu package available list
```

```text:出力例
  NAME                                          DISPLAY-NAME
  cert-manager.tanzu.vmware.com                 cert-manager
  contour.tanzu.vmware.com                      contour
  external-dns.tanzu.vmware.com                 external-dns
  fluent-bit.tanzu.vmware.com                   fluent-bit
  fluxcd-helm-controller.tanzu.vmware.com       Flux Helm Controller
  fluxcd-kustomize-controller.tanzu.vmware.com  Flux Kustomize Controller
  fluxcd-source-controller.tanzu.vmware.com     Flux Source Controller
  grafana.tanzu.vmware.com                      grafana
  harbor.tanzu.vmware.com                       harbor
  multus-cni.tanzu.vmware.com                   multus-cni
  prometheus.tanzu.vmware.com                   prometheus
  whereabouts.tanzu.vmware.com                  whereabouts
```

ここでは、Multus と Whereabouts のパッケージを確認していますが、その他にも複数の OSS パッケージが提供されているのが分かります。

次に、パッケージ名を指定して `get` コマンドを実行すると、利用可能なバージョン情報を確認できます。

```sh
tanzu package available get multus-cni.tanzu.vmware.com
```

```text:出力例
NAME:                   multus-cni.tanzu.vmware.com
DISPLAY-NAME:           multus-cni
CATEGORIES:             - networking
SHORT-DESCRIPTION:      This package provides the ability for enabling attaching multiple network
interfaces to pods in Kubernetes
LONG-DESCRIPTION:       This package provides the ability for enabling attaching multiple network
interfaces with different types of CNI to pods in Kubernetes, such as sriov and
macvlan
PROVIDER:               VMware
MAINTAINERS:            - name: Wang Jun
- name: Li Chenrui
- name: Zhou Ke
SUPPORT-DESCRIPTION:    Support provided by VMware for deployment on Tanzu clusters. Best-effort support
for deployment on any conformant Kubernetes cluster. Contact support by opening
a support request via VMware Cloud Services or my.vmware.com.

  VERSION               RELEASED-AT
  3.7.1+vmware.1-tkg.1  2021-06-05 03:00:00 +0900 JST
  3.7.1+vmware.2-tkg.1  2021-06-05 03:00:00 +0900 JST
  3.7.1+vmware.2-tkg.2  2021-06-05 03:00:00 +0900 JST
  3.8.0+vmware.1-tkg.1  2023-03-08 23:51:55 +0900 JST
  3.8.0+vmware.2-tkg.2  2023-03-08 23:51:55 +0900 JST
```

さらに、パッケージ名に加えて、バージョンまで指定することで、デフォルト値が記載された設定ファイルの雛形を取得することが出来ます。

```sh
tanzu package available get multus-cni.tanzu.vmware.com/3.8.0+vmware.2-tkg.2 --default-values-file-output multus-cni-data-values.yaml
```

```text:出力例
Created default values file at multus-cni-data-values.yaml

NAME:                         multus-cni.tanzu.vmware.com
DISPLAY-NAME:                 multus-cni
CATEGORIES:                   - networking
SHORT-DESCRIPTION:            This package provides the ability for enabling attaching multiple network
interfaces to pods in Kubernetes
LONG-DESCRIPTION:             This package provides the ability for enabling attaching multiple network
interfaces with different types of CNI to pods in Kubernetes, such as sriov and
macvlan
PROVIDER:                     VMware
MAINTAINERS:                  - name: Wang Jun
- name: Li Chenrui
- name: Zhou Ke
SUPPORT-DESCRIPTION:          Support provided by VMware for deployment on Tanzu clusters. Best-effort support
for deployment on any conformant Kubernetes cluster. Contact support by opening
a support request via VMware Cloud Services or my.vmware.com.
VERSION:                      3.8.0+vmware.2-tkg.2
RELEASED-AT:                  2023-03-08 23:51:55 +0900 JST
MIN-CAPACITY-REQUIREMENTS:
RELEASE-NOTES:                multus-cni 3.8.0
https://github.com/k8snetworkplumbingwg/multus-cni/releases/tag/v3.8
LICENSES:                     VMware’s End User License Agreement (Underlying OSS license: Apache License 2.0)
```

参考までに、この設定ファイルの中身は、↓こんな感じでした。

```text:multus-cni-data-values.yaml
# daemonset:
#   resources:
#     limits:
#       cpu: 100m
#       memory: 50Mi
#     requests:
#       cpu: 100m
#       memory: 50Mi
# namespace: kube-system
```

ここでは特にデフォルト値から変更する必要が無かったので、以降の手順で利用していませんが、必要に応じて、設定ファイルを作成してください。


## Multus パッケージのインストール

パッケージ本体のインストールの前に、パッケージをインストールする先の Namespace を作成します。
特に制限がある訳では無いですし、TKG 2.1 時点では、Multus のアンインストールがサポートされていないため、後片付けを意識して作成する必要もありません。
しかし、インストール先の Namespace は事前に作成しておく必要があるので、ここでは分かりやすさのために、`multus-cni` という名前で作成しておきます。

```sh
kubectl create namespace multus-cni
```

その上で、実際に Multus パッケージをインストールしていきます。

```sh
tanzu package install multus-cni --package multus-cni.tanzu.vmware.com --version 3.8.0+vmware.2-tkg.2 --namespace multus-cni
```

:::note info
**補足**
今回は、前述の設定ファイルを変更しておらず、デフォルト設定のままでインストールしているので、上記のオプションのみですが、何かしら設定を変更している場合には、`--values-file` で、作成した設定ファイルを指定します。

```sh
tanzu package install multus-cni --package multus-cni.tanzu.vmware.com --version 3.8.0+vmware.2-tkg.2 --values-file multus-cni-data-values.yaml --namespace multus-cni
```
:::

下記のコマンドで、正しくインストールできているか確認します。

```sh
tanzu package installed get multus-cni --namespace multus-cni
```

```text:出力例
NAMESPACE:          multus-cni
NAME:               multus-cni
PACKAGE-NAME:       multus-cni.tanzu.vmware.com
PACKAGE-VERSION:    3.8.0+vmware.2-tkg.2
STATUS:             Reconcile succeeded
CONDITIONS:         - type: ReconcileSucceeded
  status: "True"
  reason: ""
  message: ""
```

ここまでで、Multus はインストールできているので、適当な Pod を作成し、2つ目の NIC を割り当てることが出来ますが、前述の通り、Whereabouts という IP アドレス管理 (IPAM) を提供してくれる CNI と組合せたいと思います。
Whereabouts によって、K8s Node を跨いで、クラスタ全体で適切に IP アドレスの割り当てが実行されます。


## Whereabouts パッケージのインストール

パッケージの確認から記載していくと冗長なので、いきなりパッケージ情報の確認と、設定ファイルの雛形の取得から記載しています。

```sh
tanzu package available get whereabouts.tanzu.vmware.com/0.5.4+vmware.1-tkg.1 --default-values-file-output whereabouts-data-values.yaml
```

```text:出力例
Created default values file at whereabouts-data-values.yaml

NAME:                         whereabouts.tanzu.vmware.com
DISPLAY-NAME:                 whereabouts
CATEGORIES:                   - networking
SHORT-DESCRIPTION:            A CNI IPAM plugin that assigns IP addresses cluster-wide
LONG-DESCRIPTION:             A CNI IPAM plugin that assigns IP addresses cluster-wide
PROVIDER:                     VMware
MAINTAINERS:                  - name: Jun Wang
- name: Chenrui Li
- name: Ke Zhou
SUPPORT-DESCRIPTION:          Support provided by VMware for deployment on Tanzu clusters. Best-effort support
for deployment on any conformant Kubernetes cluster. Contact support by opening
a support request via VMware Cloud Services or my.vmware.com.
VERSION:                      0.5.4+vmware.1-tkg.1
RELEASED-AT:                  2023-03-08 23:51:55 +0900 JST
MIN-CAPACITY-REQUIREMENTS:
RELEASE-NOTES:                whereabouts 0.5.4
https://github.com/k8snetworkplumbingwg/whereabouts/releases/tag/v0.5.4
LICENSES:                     VMware’s End User License Agreement (Underlying OSS license: Apache License 2.0)
```

ただ、こちらも今回はデフォルト設定から変更する必要が無かったので、以降の手順では利用していません。

```text:whereabouts-data-values.yaml
# ip_reconciler:
#   config:
#     resources:
#       requests:
#         cpu: 100m
#         memory: 50Mi
#     schedule: '*/5 * * * *'
# namespace: kube-system
# whereabouts:
#   config:
#     resources:
#       limits:
#         cpu: 100m
#         memory: 50Mi
#       requests:
#         cpu: 100m
#         memory: 50Mi
```

よって、まずはインストール先の Namespace を作成し、

```sh
kubectl create namespace whereabouts
```

Whereabouts パッケージをインストールします。

```sh
tanzu package install whereabouts --package whereabouts.tanzu.vmware.com --version 0.5.4+vmware.1-tkg.1 --namespace whereabouts
```

こちらも、下記のコマンドで、正しくインストールできているか確認します。

```sh
tanzu package installed get whereabouts --namespace whereabouts
```

```text:出力例
NAMESPACE:          whereabouts
NAME:               whereabouts
PACKAGE-NAME:       whereabouts.tanzu.vmware.com
PACKAGE-VERSION:    0.5.4+vmware.1-tkg.1
STATUS:             Reconcile succeeded
CONDITIONS:         - type: ReconcileSucceeded
  status: "True"
  reason: ""
  message: ""
```


## Multus の設定

パッケージが正しくインストールされたので、ここからは Multus (および Whereabouts) に対して、「どんなネットワークに接続して欲しいか」を設定していきます。
そのためには、`NetworkAttachmentDefinition` という CRD (Custom Resource Definition) を作成します。

今回作成したマニフェストは、↓こんな感じです。

```yaml:multus-cni-crd.yaml
---
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
  name: macvlan-conf
spec:
  config: '{
    "cniVersion": "0.3.1",
    "plugins": [
      {
      "type": "macvlan",
      "capabilities": { "ips": true },
      "master": "eth0",
      "mode": "bridge",
      "ipam": {
        "type": "whereabouts",
        "range": "192.168.120.0/24",
        "range_start": "192.168.120.128",
        "range_end": "192.168.120.191",
        "gateway": "192.168.120.254"
        }
      } ]
    }'
```

内容としては、Node VM の `eth0` を親インタフェースとして、macvlan を使って追加の NIC を作成するように、設定してあります。
その際、Whereabouts を利用して、`192.168.120.128-192.168.120.191` の範囲で IP アドレスを払い出せるようにしてあります。

このマニフェストを適用しておきます。

```sh
kubectl apply -f multus-cni-crd.yaml
```


## 動作確認

まず、2つの NIC を持つ Pod を作成するために、↓このようなマニフェストを作成します。
Annotation 部分で、先ほど作成した `NetworkAttachmentDefinition` が指定しています。

```yaml:pod-with-multi-cni.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod0
  annotations:
    k8s.v1.cni.cncf.io/networks: macvlan-conf
spec:
  containers:
  - name: pod0
    command: ["/bin/ash", "-c", "trap : TERM INT; sleep infinity & wait"]
    image: busybox
  imagePullSecrets:
  - name: docker-hub
```

このマニフェストを使って、Pod を作成します。

```sh
kubectl apply -f pod-with-multi-cni.yaml
```

Pod の実行を確認できたら、

```sh
$ kubectl get pods pod0
NAME   READY   STATUS    RESTARTS   AGE
pod0   1/1     Running   0          2m3s
```

Annotation の "k8s.v1.cni.cncf.io/network-status" 部分を見ると、`net1` として、2本目の NIC に IP アドレスが割り当てられいることが確認できます。

```sh
$ kubectl describe pod pod0
# ...(略)...
Annotations:  k8s.v1.cni.cncf.io/network-status:
                [{
                    "name": "antrea",
                    "interface": "eth0",
                    "ips": [
                        "100.96.4.26"
                    ],
                    "mac": "c2:ac:40:6d:57:0a",
                    "default": true,
                    "dns": {},
                    "routes": [
                        {
                            "dst": "0.0.0.0/0",
                            "gw": "100.96.4.1"
                        }
                    ]
                },{
                    "name": "default/macvlan-conf",
                    "interface": "net1",
                    "ips": [
                        "192.168.120.128"
                    ],
                    "mac": "8e:4d:5d:39:30:fe",
                    "dns": {}
                }]
              k8s.v1.cni.cncf.io/networks: macvlan-conf
              # ...(略)...
```

簡易な確認ですが、この `pod0` から Gateway に対して、ping が通るかだけ、確認したいと思います。

まず、`kubectl` コマンドを使って`pod0` に入り込みます。

```sh
kubectl exec -it pod0 -- sh
```

IP アドレスも先ほど確認した通りに割り当てられているようです。

```text
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0@if31: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue
    link/ether c2:ac:40:6d:57:0a brd ff:ff:ff:ff:ff:ff
    inet 100.96.4.26/24 brd 100.96.4.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::c0ac:40ff:fe6d:570a/64 scope link
       valid_lft forever preferred_lft forever
3: net1@eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue
    link/ether 8e:4d:5d:39:30:fe brd ff:ff:ff:ff:ff:ff
    inet 192.168.120.128/24 brd 192.168.120.255 scope global net1
       valid_lft forever preferred_lft forever
    inet6 fe80::8c4d:5dff:fe39:30fe/64 scope link
       valid_lft forever preferred_lft forever
/ # ip r
default via 100.96.4.1 dev eth0
100.96.4.0/24 dev eth0 scope link  src 100.96.4.26
192.168.120.0/24 dev net1 scope link  src 192.168.120.128
```

実際に Gateway に対して、ping を実行してみると、疎通できていることが確認できます。

```text
/ # ping -I net1 -c 3 192.168.120.254
PING 192.168.120.254 (192.168.120.254): 56 data bytes
64 bytes from 192.168.120.254: seq=0 ttl=64 time=0.504 ms
64 bytes from 192.168.120.254: seq=1 ttl=64 time=0.310 ms
64 bytes from 192.168.120.254: seq=2 ttl=64 time=0.242 ms

--- 192.168.120.254 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.242/0.352/0.504 ms
```


# この先...

Multus + Whereabouts を使って、TKG 上で動作する Pod に、2つ目の NIC を割り当てられることが確認できました。
ただし、前述の通り、K8s の Service の世界から見える IP アドレスは、1つ目の NIC `eth0` のものであるため、残念ながら、まだまだ使い勝手が良い訳ではありません。

しかし、Multus 自身あるいは、周辺の機能が整ってくれば、間違いなく面白い技術ではあるので、また更新があれば、ご紹介したいと思います。