Antrea とは
Antrea とは、オープンソースの Kubernetes CNI で、L3/4 のネットワーキングの機能とセキュリティを Kubernetes クラスターに提供するものです。オープンな仮想スイッチとして長い実績を誇る Open vSwitch をデータプレーンに利用しています。
Antrea では標準的な Kubernetes クラスターネットワーキングではサポートされない様々な機能の実装が試みられています。高度なネットワークセキュリティ機能である Antrea Cluster Network Policy (ACNP) については、こちらの記事でもお話ししました。
今回は、Antrea 1.4 からベータリリースとなった NodePortLocal (NPL) を試してみたいと思います。NPL はその名が示すとおり、NodePort と同様に Kubernetes クラスター外部から内部の Pod へのアクセスを提供する仕組みです。通常の NodePort はすべての K8s ノード上に作成され、関連付けられたサービス ClusterIP を介してバックエンド Pod へトラフィックを分散します。そのため必ずしもアクセスしたノード上に Pod が存在せず、他のノードに転送される可能性があります。加えて LoadBalancer サービスを利用する場合に、Pod へ到達するためのホップ数が増えてしまうかもしれません。Antrea 環境では NPL を使うことで Pod が実行されているノードを直接バックエンドとして選択できるようになり、不適正なトラフィックの発生を抑えることができるようになります。
本記事では、このような利点がある Antrea NPL の機能を確認していきたいと思います。
Antrea クラスターの準備・アップデート
K8s クラスターの確認
今回は以前用意した Antrea クラスターを利用して NPL を動作させてみます。これから Kubernetes クラスターを準備する場合は、こちらの記事もご参照ください。なおバージョンは1.21にアップグレードしています。
$ 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 で動作していました。
$ antctl version
antctlVersion: v1.0.0
controllerVersion: v1.0.0
今回は Antrea v1.4.0 にアップグレードしますので、以下のように指定して適用します。
$ kubectl apply -f https://github.com/vmware-tanzu/antrea/releases/download/v1.4.0/antrea.yml
antctl で新しいバージョンが適用されていることを確認します。
$ antctl version
antctlVersion: v1.0.0
controllerVersion: v1.4.0
antctl のインストール
antctl も同じようにアップグレードしておきます。
$ 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 の詳細を表示し、設定状態を確認します。
$ kubectl get configmap antrea-config-ckdtm6hc68 -n kube-system -o yaml
NodePortLocal
の設定を探して確認します。antrea-agent.conf
設定の NodePortLocal
にて enable: false
となっていますが、コメントアウトされています。Antrea 1.4 からデフォルトで NodePortLocal が enable になっているので、この状態ですでに有効になっています。
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 を編集モードで開きます。
kubectl edit configmap antrea-config-ckdtm6hc68 -n kube-system
nodePortLocal
設定までスクロールダウンし、以下のように enable
設定と portRange
のコメントアウトを外し、enable: true
に変更します。
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 になっていることを確認します。
$ 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 は独自のものを使用しています)
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
kubectl apply -f nginx.yaml
service/nginx created
deployment.apps/nginx created
nginx
の3つの Pod と Service が作成されました。
$ 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つのノードどれからでもアクセス可能です。
http://192.168.110.90:32474/
http://192.168.110.91:32474/
http://192.168.110.92:32474/
ここで Deployment のレプリカ数を 3 から 1 に変更してみます。
$ 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
スケールインした後は下図のような状態になっているはずです。
この状態で同じように3つのノードすべての NodePort にアクセスすると、どのノードからもレスポンスを得ることができました。しかし、実際にレスポンスをしているのはスケールインで残った上記の Pod 一つのみです。
このように、通常の NodePort を使うモデルではリクエスト先のノードで必ずしも Pod が動いているわけではないことが分かります。外部ロードバランサと組み合わせて使う場合、不適正なロードバランシングの原因になる可能性があります。
NodePortLocal (NPL)
では NodePortLocal ではどのような動作になるかをみていきます。
NodePortLocal は Service に対して nodeportlocal.antrea.io
アノテーションを追加することにより制御しています。また、NodePortLocal を設定できる Service は ClusterIP もしくは LoadBalancer のみです。
NPL を設定したサンプルアプリの展開
ここでは ClusterIP に対して設定してみたいと思います。先ほどのマニフェストを元に以下のような NPL 用のマニフェスト nginx-npl.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
で作成したアプリを削除した後、新しいマニフェストを適用します。
$ 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
アノテーションが追加されます。
kubectl get pod nginx-d84c8f9cc-2pqp6 -o 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://192.168.110.91:61031/
実際には Pod は3つありますので、その他の Pod のアノテーションも確認してみます。
$ 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 を分散して展開させることが可能です。
外部ロードバランサの利用
NodePortLocal が真価を発揮するのは外部ロードバランサと組み合わせる場合です。外部ロードバランサと組み合わせることで、ノードに対する負荷分散をより効果的に行うことが可能となります。各 Pod と NPL をサーバプールメンバーとして識別できるので、ロードバランサのヘルスチェックやパーシステンスといった機能を有効に活用することもできます。
現状、外部ロードバランサと NPL の設定を自動的に行えるソリューションとしては VMware 社の NSX Advanced Load Balancer (AVI) がありますが、他社の LB 製品でもサポートが進んでいるようです。今後多くのロードバランサでも NPL がサポートされると素晴らしいですね。