Kubernetes用のCNIプラグインとしてVMWareがオープンソースで開発しているのが”Antrea”です。大きな特徴は、データパスにOpenVSwitchを使っていて、プロジェクトが発表された2019年の投稿によると、以下の利点があるようです。
- 性能 … OVSはiptablesよりパフォーマンスが良く、ルールが多いほど差は顕著になる
- 携帯性 … OVSはLinux、Windows、その他のOSでサポートされている
- 運用性 … 既存のOVSの資産を使って障害解析や監視ができる
- 柔軟性と拡張性 … OVSは新機能の統合を容易にできる
この投稿では、AntreaをCNIプラグインとしてKubernetesを構築し、Overlayネットワークとネットワークポリシーがどのように構成されているのかを見てみましょう。
ここでは次の構成で組んでいます:
- 3x AWS EC2 instance (one master, two worker node)
- kubeadm
- antrea (v0.9.1)
1. Kubernetesクラスタの作成
EKSやGKEなどではAntreaを使ったOverlayネットワークがまだサポートされていないため、EC2インスタンスを使って自前のKubernetesクラスタを作成します。EC2インスタンスはt2.medium、セキュリティグループは外部からのSSH接続、各ノード間の通信が許可されるように設定してます。
sudo kubeadm init --pod-network-cidr=192.168.0.0/16
masterノードの初期化が終わったら、画面の出力に沿ってKUBECONFIGを保存します。その後に、画面に表示されている sudo kubeadm join...
をworkerノードで実行します。
この状態で kubectl get node
を実行して、ノードが3つ表示されていることと、ステータスが Not Ready
になっていることを確認します。
質問:なぜ各ノードは”Not Ready”なのでしょうか?
回答:Kubernetesの各ノードはkubeletの起動時にネットワークでCNIを使用する( --network-pugin=cni
)ように指定された場合、特定のディレクトリにCNIのバイナリ及び構成ファイルを配置する必要があります。Kubeadmで構成した場合は、/opt/cni/bin/配下にバイナリはありますが、/etc/cni/net.d/にCNIの設定ファイルがないため、Readyとなりません。
2. Antreaの導入
Antreaを導入するには、次のコマンドでマニフェストを読み込むだけです。
kubectl apply -f https://github.com/vmware-tanzu/antrea/releases/download/v0.9.1/antrea.yml
これにより、Antreaに必要なKubernetesオブジェクトが作成されます。アーキテクチャの詳細は公式ドキュメントのページが参考になりますが、主なオブジェクトとしては、コントローラの役目を果たす”Antrea Controller”、そしてDaemonSetとして各ノードに配置されCNIプラグインとして動く”Antrea Agent”があります。
この状態で kubectl get node
を実行して、ノードのステータスが Ready
になっていることを確認します。
質問:なぜ各ノードが”Ready”になったのでしょうか?
回答:Antrea-Agentがそれぞれのノードに配置されると、initContainerとして”isntall-cni”が作成されます。このコンテナは、/etc/cni/net.d/にantreaの構成ファイルを、/opt/cni/bin/にantreaのバイナリをそれぞれ配置します。これによりCNIプラグインの使用準備が整ったと判断され、kubeletからAPI-Serverに対して”Ready”が通知されます。

この図からも見えるように、antrea-agentはOpenVSwitchを作り、ホスト接続用のantrea-gw0というインタフェースとOverlay用のantrea-tun0というインタフェースを作成します。
3. Podの作成と疎通試験
試験用に名前空間を作成します。
$ kubectl create ns antrea-demo
namespace/antrea-demo created
Nginxサーバを作成します。
$ kubectl get -n antrea-demo pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-6799fc88d8-gbhs2 1/1 Running 0 29s 192.168.2.4 ip-172-16-1-153 <none> <none>
nginx-6799fc88d8-hrktw 1/1 Running 0 29s 192.168.1.4 ip-172-16-1-174 <none> <none>
クライアントを作成し、Nginxサーバに接続できることを確認します。
$ kubectl run -n antrea-demo access --image=busybox -- sleep infinity
pod/access created
ubuntu@ip-172-16-1-14:~$ kubectl get pod/access -n antrea-demo -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
access 1/1 Running 0 27s 192.168.1.6 ip-172-16-1-174 <none> <none>
$ kubectl exec -it access -n antrea-demo -- sh
/ # wget -q -O - 192.168.2.4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
* snipped *
</html>
/ #
質問:PodのIPアドレス(192.168.0.0/16)は外部(この場合はAWS VPC)に経路情報がありません。では、なぜPod同士の接続が可能なのでしょうか?
回答:Podからの通信はOpenVSwitchに着信後、Geneveでカプセル化されます。その結果、外に出ていくパケットは「送信元:Podがあるホストのeth0のIPアドレス、送信先:NGINXが動いているPodがあるホストのeth0のIPアドレス」になるため、VPC上で正常にルーティングされます。
では、この通信がどのように実現されているかを、手順を追って詳しく見ていきましょう。現在のクラスタの状態は下の図を参照してください。

$ kubectl exec -n kube-system antrea-agent-ct6tx -c antrea-ovs -- ovs-vsctl -- --columns=name,ofport list Interface
name : access-b675ad
ofport : 7
name : antrea-gw0
ofport : 2
name : nginx-67-cb2eac
ofport : 5
name : antrea-tun0
ofport : 1
name : coredns--6b79cb
ofport : 3
$ kubectl exec -n kube-system antrea-agent-jcldl -c antrea-ovs -- ovs-vsctl -- --columns=name,ofport list Interface
name : antrea-tun0
ofport : 1
name : nginx-67-b5d4c8
ofport : 5
name : coredns--7be79d
ofport : 3
name : antrea-gw0
ofport : 2
クライアントPodから外部ネットワークに出るまで

table=0, 10で入力ポートの判定、table=70でOverlayトンネルに入るための準備が行われています。
- TTL調整
- Overlayトンネルの宛先設定 … 宛先ノードのeth0アドレス(172.16.1.153)がNXM_NX_TUN_IPV4_DST[]に0xac100199として格納
- NXM_NX_REG1に0x1を格納
table=105でコネクショントラッキング、そしてtable=110でtable=70でREG1に格納された数字のポート、ここでは1番なのでtun0から出力されます。
外部ネットワーク上
外部のネットワーク上に出る際にパケットはGeneveでカプセル化されます。


外部ネットワークから宛先Podまで

table=0でtun0から入ってきたパケットの判定、table=70で宛先のIPアドレスに紐づけてMACアドレスの書き換えと、TTL調整をします。table=80ではPodが接続されているポートに出力されるための準備が行われています。
- NXM_NX_REG1に0x5を格納
- NXM_NX_REG0[16]に0x1を格納
table=105では送信元と同じようにコネクショントラッキングが行われ、table=110でtable=80でREG1に格納された数字のポート、ここでは5番なのでnginxのPodが接続されているvethペアから出力されます。
4. ネットワークポリシーの適用
Pod間に不必要なトラフィックが流れないように制限することは、問題を未然に防ぎ、万が一問題が起こった時に被害を最小限にするためにも大切です。
Antreaが提供するネットワークポリシーはOSIのL3とL4で制限をかけることができ、一般的なファイアウォールと考えることができます。ここでは、ベストプラクティスであるゼロトラストに従って、全ての通信を拒否設定にした上で、必要な通信のみを許可することとします。
まずは名前空間に対して、名前空間内のPod間通信を拒否をするように設定します。
$ kubectl create -f - <<EOF
> kind: NetworkPolicy
> apiVersion: networking.k8s.io/v1
> metadata:
> name: default-deny
> namespace: antrea-demo
> spec:
> podSelector:
> matchLabels: {}
> EOF
networkpolicy.networking.k8s.io/default-deny created
次にrun=accessというラベルのPodからの、app=nginxというラベルのPodに対しての通信を許可します。
$ kubectl create -f - <<EOF
> kind: NetworkPolicy
> apiVersion: networking.k8s.io/v1
> metadata:
> name: nginx-access
> namespace: antrea-demo
> spec:
> podSelector:
> matchLabels:
> app: nginx
> ingress:
> - from:
> - podSelector:
> matchLabels:
> run: access
> EOF
networkpolicy.networking.k8s.io/nginx-access created
結果は次の通り、問題なく通信できています。
$ kubectl exec access -n antrea-demo access -- wget -q -O - 192.168.1.3
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
では、このネットワークポリシーがどのように実装されているかを見てみましょう。これはアクセスの宛先であるNginxのPodが稼働しているノードのOpenVSwitchの不ローテーブル抜粋です。

table=0でtun0から入力された通信は、table=70とtable=80で出力されるインタフェースの情報が引き渡されます。ここまではネットワークポリシーが適用される前と同じです。table=90がネットワークポリシーを実装しているテーブルになり、前の段で出力インタフェースを設定される時に同時に設定されたレジスタと、送信元、ここではbusyboxのIPアドレスの両方をconjunctionで判定し、その結果table=105へresubmitされています。ここからはネットワークポリシーの適用がない時と同じく、table=105でコネクショントラッキング、table=110で出力インタフェースへ出力となります。ここで、例えば送信元がconjunctionで合致しない場合には、table=90のpriority=0にdropがあり、全て落とされます。
ネットワーク周りはPodのIPやサービスIP、そしてCNIによってはOverlay、ネットワークポリシーなどがあり、Kubernetesクラスタを学習する際に頭を悩ませる部分です。特にネットワークポリシーの適用で多くのCNIはiptablesを使っており、その流れを理解し、障害分析をするのには苦労が伴っていました。AntreaではOpenVSwitchを使うことで、ルーティングとネットワークポリシーを一元的に確認することができ、初期の学習コストを下げることができそうです。またOctantを使うことでGUIで視覚的に見ることもできるそうなので、次回の検証ではOctantを試してみます。