Antrea NodePortLocal (NPL) を動かす

# Antrea とは

Antrea とは、オープンソースの Kubernetes CNI で、L3/4 のネットワーキングの機能とセキュリティを Kubernetes クラスターに提供するものです。オープンな仮想スイッチとして長い実績を誇る Open vSwitch をデータプレーンに利用しています。

https://github.com/antrea-io/antrea

Antrea では標準的な Kubernetes クラスターネットワーキングではサポートされない様々な機能の実装が試みられています。高度なネットワークセキュリティ機能である Antrea Cluster Network Policy (ACNP) については、<a href="https://qiita.com/ynakaoku/items/14884f4fb04423bf9747">こちらの記事</a>でもお話ししました。
今回は、Antrea 1.4 からベータリリースとなった <a href='https://antrea.io/docs/main/docs/node-port-local/'> NodePortLocal (NPL) </a> を試してみたいと思います。NPL はその名が示すとおり、NodePort と同様に Kubernetes クラスター外部から内部の Pod へのアクセスを提供する仕組みです。通常の NodePort はすべての K8s ノード上に作成され、関連付けられたサービス ClusterIP を介してバックエンド Pod へトラフィックを分散します。そのため必ずしもアクセスしたノード上に Pod が存在せず、他のノードに転送される可能性があります。加えて LoadBalancer サービスを利用する場合に、Pod へ到達するためのホップ数が増えてしまうかもしれません。Antrea 環境では NPL を使うことで Pod が実行されているノードを直接バックエンドとして選択できるようになり、不適正なトラフィックの発生を抑えることができるようになります。

本記事では、このような利点がある Antrea NPL の機能を確認していきたいと思います。

# Antrea クラスターの準備・アップデート

## K8s クラスターの確認  

今回は以前用意した Antrea クラスターを利用して NPL を動作させてみます。これから Kubernetes クラスターを準備する場合は、<a href="https://qiita.com/ynakaoku/items/14884f4fb04423bf9747">こちらの記事</a>もご参照ください。なおバージョンは1.21にアップグレードしています。  

```bash
$ kubectl get nodes -o wide  
NAME          STATUS   ROLES                  AGE    VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION       CONTAINER-RUNTIME
k8s-master    Ready    control-plane,master   273d   v1.21.7   192.168.110.90   <none>        Ubuntu 18.04.6 LTS   4.15.0-166-generic   containerd://1.5.5
k8s-worker1   Ready    <none>                 273d   v1.21.7   192.168.110.91   <none>        Ubuntu 18.04.6 LTS   4.15.0-166-generic   containerd://1.5.5
k8s-worker2   Ready    <none>                 271d   v1.21.7   192.168.110.92   <none>        Ubuntu 18.04.6 LTS   4.15.0-166-generic   containerd://1.5.5
```

## Antrea クラスターのアップグレード

NPL は 2021年11月5日にリリースされた Antrea v1.4.0 からベータサポートになりました。本機能を使うため、構築済みの Antrea クラスターを 1.4.0 にバージョンアップします。
Antrea と antctl の初期セットアップについてはこちらもご参照下さい:  
https://github.com/vmware-tanzu/antrea/blob/main/docs/getting-started.md

前回、対象の Antrea クラスターは v1.0.0 で動作していました。 

```bash
$ antctl version
antctlVersion: v1.0.0
controllerVersion: v1.0.0
```

今回は Antrea v1.4.0 にアップグレードしますので、以下のように指定して適用します。

```bash
$ kubectl apply -f https://github.com/vmware-tanzu/antrea/releases/download/v1.4.0/antrea.yml
```
antctl で新しいバージョンが適用されていることを確認します。

```bash
$ antctl version
antctlVersion: v1.0.0
controllerVersion: v1.4.0
```
### antctl のインストール  
antctl も同じようにアップグレードしておきます。 

```bash
$ curl -Lo ./antctl "https://github.com/vmware-tanzu/antrea/releases/download/v1.4.0/antctl-linux-x86_64"
$ chmod +x ./antctl
$ mv ./antctl /usr/local/bin
$ antctl version
antctlVersion: v1.4.0
controllerVersion: v1.4.0
```

### 設定の確認

NodePortLocal を使用するには、Antrea の configmap で enable にされている必要があります。以下のコマンドで configmap の詳細を表示し、設定状態を確認します。 

```bash
$ kubectl get configmap antrea-config-ckdtm6hc68 -n kube-system -o yaml
```

`NodePortLocal` の設定を探して確認します。`antrea-agent.conf` 設定の `NodePortLocal` にて `enable: false` となっていますが、コメントアウトされています。Antrea 1.4 からデフォルトで NodePortLocal が enable になっているので、この状態ですでに有効になっています。

```yaml
apiVersion: v1
data:
  antrea-agent.conf: |
    # FeatureGates is a map of feature names to bools that enable or disable experimental features.
    featureGates:
    # Enable AntreaProxy which provides ServiceLB for in-cluster Services in antrea-agent.
    # It should be enabled on Windows, otherwise NetworkPolicy will not take effect on
    # Service traffic.
    #  AntreaProxy: true

    # Enable EndpointSlice support in AntreaProxy. Don't enable this feature unless that EndpointSlice
    # API version v1beta1 is supported and set as enabled in Kubernetes. If AntreaProxy is not enabled,
    # this flag will not take effect.
    #  EndpointSlice: false

    # Enable traceflow which provides packet tracing feature to diagnose network issue.
    #  Traceflow: true

    # Enable NodePortLocal feature to make the Pods reachable externally through NodePort
    #  NodePortLocal: true

(snip...)

    nodePortLocal:
    # Enable NodePortLocal, a feature used to make Pods reachable using port forwarding on the host. To
    # enable this feature, you need to set "enable" to true, and ensure that the NodePortLocal feature
    # gate is also enabled (which is the default).
    #  enable: false
    # Provide the port range used by NodePortLocal. When the NodePortLocal feature is enabled, a port
    # from that range will be assigned whenever a Pod's container defines a specific port to be exposed
    # (each container can define a list of ports as pod.spec.containers[].ports), and all Node traffic
    # directed to that port will be forwarded to the Pod.
    #  portRange: 61000-62000
(snip...)
```

ただし実際に NPL を動作させるには、`nodePortLocal` 設定のオプションで機能を有効化し、ポートレンジの設定を行う必要があります。
まず以下のコマンドで ConfigMap を編集モードで開きます。

```bash
kubectl edit configmap antrea-config-ckdtm6hc68 -n kube-system
```
`nodePortLocal` 設定までスクロールダウンし、以下のように `enable` 設定と `portRange` のコメントアウトを外し、`enable: true` に変更します。

```yaml
    nodePortLocal:
    # Enable NodePortLocal, a feature used to make Pods reachable using port forwarding on the host. To
    # enable this feature, you need to set "enable" to true, and ensure that the NodePortLocal feature
    # gate is also enabled (which is the default).
      enable: true
    # Provide the port range used by NodePortLocal. When the NodePortLocal feature is enabled, a port
    # from that range will be assigned whenever a Pod's container defines a specific port to be exposed
    # (each container can define a list of ports as pod.spec.containers[].ports), and all Node traffic
    # directed to that port will be forwarded to the Pod.
      portRange: 61000-62000
```

念のため、 antctl コマンドで featuregate にて NPL が Enable になっていることを確認します。

```bash
$ antctl get featuregates
Antrea Agent Feature Gates
FEATUREGATE              STATUS         VERSION
NetworkPolicyStats       Enabled        BETA
NodePortLocal            Enabled        BETA
AntreaProxy              Enabled        BETA
Egress                   Disabled       ALPHA
Traceflow                Enabled        BETA
AntreaIPAM               Disabled       ALPHA
FlowExporter             Disabled       ALPHA
AntreaPolicy             Enabled        BETA
EndpointSlice            Disabled       ALPHA

Antrea Controller Feature Gates
FEATUREGATE              STATUS         VERSION
AntreaPolicy             Enabled        BETA
Egress                   Disabled       ALPHA
Traceflow                Enabled        BETA
NetworkPolicyStats       Enabled        BETA
NodeIPAM                 Disabled       ALPHA
```

# Antrea NodePortLocal を試す

では実際に NodePortLocal を使ってみます。K8s 標準の NodePort との動作の違いも見てみたいと思います。

## NodePort の確認

### サンプルアプリの展開

まず、動作確認のために使用するアプリケーションをデプロイします。以下のような Deployment と Service のマニフェスト `nginx.yaml` を作成し、default Namespace にデプロイしました。(DockerHub のための ImagePullSecret は独自のものを使用しています)

```yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  type: NodePort
  ports:
  - name: web
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
          protocol: TCP
      imagePullSecrets:
      - name: regcred
```
```bash
kubectl apply -f nginx.yaml
service/nginx created
deployment.apps/nginx created
```

`nginx` の3つの Pod と Service が作成されました。

```bash
$ kubectl get all -n default
NAME                        READY   STATUS    RESTARTS   AGE
pod/nginx-d84c8f9cc-fh5x4   1/1     Running   0          4m31s
pod/nginx-d84c8f9cc-nnsgb   1/1     Running   0          4m31s
pod/nginx-d84c8f9cc-s7zpn   1/1     Running   0          4m31s

NAME            TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
service/nginx   NodePort   10.99.79.182   <none>        80:32474/TCP   4m31s

NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx   3/3     3            3           4m31s

NAME                              DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-d84c8f9cc   3         3         3       4m31s
```

`nginx` Service は `NodePort` タイプなので、外部からアクセスすることが可能です。下図のように、この環境にある3つのノードどれからでもアクセス可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/13f48ed0-aa47-69c0-7154-e258b7e9cefe.png)

```http
http://192.168.110.90:32474/
http://192.168.110.91:32474/
http://192.168.110.92:32474/
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/bb79c251-0089-9b82-e96d-3899844669c8.png)

ここで Deployment のレプリカ数を 3 から 1 に変更してみます。

```bash
$ kubectl scale deployment nginx --replicas=1 
deployment.apps/nginx scaled
$ kubectl get pod
NAME                    READY   STATUS    RESTARTS   AGE
nginx-d84c8f9cc-nnsgb   1/1     Running   0          16m 
```
スケールインした後は下図のような状態になっているはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/78688403-726d-243d-2650-fd457562fb59.png)
この状態で同じように3つのノードすべての NodePort にアクセスすると、どのノードからもレスポンスを得ることができました。しかし、実際にレスポンスをしているのはスケールインで残った上記の Pod 一つのみです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/aa79c9d5-681c-8496-90da-c211d49e7a38.png)

このように、通常の NodePort を使うモデルではリクエスト先のノードで必ずしも Pod が動いているわけではないことが分かります。外部ロードバランサと組み合わせて使う場合、不適正なロードバランシングの原因になる可能性があります。

## NodePortLocal (NPL)

では NodePortLocal ではどのような動作になるかをみていきます。
NodePortLocal は Service に対して `nodeportlocal.antrea.io` アノテーションを追加することにより制御しています。また、NodePortLocal を設定できる Service は ClusterIP もしくは LoadBalancer のみです。

### NPL を設定したサンプルアプリの展開

ここでは ClusterIP に対して設定してみたいと思います。先ほどのマニフェストを元に以下のような NPL 用のマニフェスト `nginx-npl.yaml` を作成しました。

```yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx
  annotations:
    nodeportlocal.antrea.io/enabled: "true"
spec:
  ports:
  - name: web
    port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
          protocol: TCP
      imagePullSecrets:
      - name: regcred
```
Service に対して `nodeportlocal.antrea.io/enabled: "true"` のアノテーションを追加しています。Deployment の設定は同じです。
先ほどの `nginx.yaml` で作成したアプリを削除した後、新しいマニフェストを適用します。

```bash
$ kubectl delete -f nginx.yaml
service "nginx" deleted
deployment.apps "nginx" deleted
$
$ kubectl apply -f nginx-npl.yaml
service/nginx created
deployment.apps/nginx created
$
$ kubectl get all -n default
NAME                        READY   STATUS    RESTARTS   AGE
pod/nginx-d84c8f9cc-2pqp6   1/1     Running   0          8m4s
pod/nginx-d84c8f9cc-b5wkk   1/1     Running   0          8m4s
pod/nginx-d84c8f9cc-cx658   1/1     Running   0          8m4s

NAME                 TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP   273d
service/nginx        ClusterIP   10.98.160.236   <none>        80/TCP    8m4s

NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/nginx   3/3     3            3           8m4s

NAME                              DESIRED   CURRENT   READY   AGE
replicaset.apps/nginx-d84c8f9cc   3         3         3       8m4s
```

nginx Service は ClusterIP として実行されますが、この Service のメンバーになっている Pod には独自の `nodeportlocal.antrea.io` アノテーションが追加されます。

```bash
kubectl get pod nginx-d84c8f9cc-2pqp6 -o yaml
```
```yaml
apiVersion: v1
kind: Pod
metadata:
  annotations:
    nodeportlocal.antrea.io: '[{"podPort":80,"nodeIP":"192.168.110.91","nodePort":61031,"protocols":["tcp"]}]'
  creationTimestamp: "2022-01-05T13:42:42Z"
  generateName: nginx-d84c8f9cc-
  labels:
    app: nginx
    pod-template-hash: d84c8f9cc
  name: nginx-d84c8f9cc-2pqp6
  namespace: default
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: nginx-d84c8f9cc
    uid: e4e23f14-606b-4243-ac7e-3ddd804117db
  resourceVersion: "34174047"
  uid: 86a3e48c-52b9-4c69-838a-e4373e32ad88
spec:
  containers:
  - image: nginx
(snip...)
```
このように `nodeportlocal.antrea.io` アノテーションが自動的に追加され、JSON 形式でノードの IP アドレスと割り当てられたノードのポート番号が表現されています。ポート番号は ConfigMap で指定したポートレンジから払い出されます。この表示から読み取れる以下のURLにアクセスすれば、Nginx に接続できます。

```http
http://192.168.110.91:61031/
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/8c235021-7a51-f1a2-631f-40656c4a1c12.png)
実際には Pod は3つありますので、その他の Pod のアノテーションも確認してみます。

```bash
$ kubectl get pod -o yaml | grep nodeportlocal
      nodeportlocal.antrea.io: '[{"podPort":80,"nodeIP":"192.168.110.91","nodePort":61031,"protocols":["tcp"]}]'
      nodeportlocal.antrea.io: '[{"podPort":80,"nodeIP":"192.168.110.91","nodePort":61030,"protocols":["tcp"]}]'
      nodeportlocal.antrea.io: '[{"podPort":80,"nodeIP":"192.168.110.91","nodePort":61029,"protocols":["tcp"]}]'
```
Anti-Affinity設定をしていないこともあり同じノード上にデプロイされていますが、それぞれに異なるポート番号が割り当てられ、それぞれ別のインスタンスとして扱われていることが分かります。この場合、192.168.110.91以外のノードにはPodが展開されていないので、NodePortLocalも設定されません。

理想的には下図のように Pod と NPL を分散して展開させることが可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1018265/12de984a-53d9-7b21-5f0d-c02fe88bc8d5.png)

## 外部ロードバランサの利用

NodePortLocal が真価を発揮するのは外部ロードバランサと組み合わせる場合です。外部ロードバランサと組み合わせることで、ノードに対する負荷分散をより効果的に行うことが可能となります。各 Pod と NPL をサーバプールメンバーとして識別できるので、ロードバランサのヘルスチェックやパーシステンスといった機能を有効に活用することもできます。  
現状、外部ロードバランサと NPL の設定を自動的に行えるソリューションとしては VMware 社の NSX Advanced Load Balancer (AVI) がありますが、他社の LB 製品でもサポートが進んでいるようです。今後多くのロードバランサでも NPL がサポートされると素晴らしいですね。