Antrea CNI pluginの導入とフローテーブル

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を試してみます。

Leave a Reply

Your email address will not be published. Required fields are marked *