KubernetessのNetwork PolicyとVMware NSX-T


[vExperts Advent Calendar 2018](https://adventar.org/calendars/3101)の12月21日分の投稿です。

## Network Policy

[Network Policy](https://kubernetes.io/docs/concepts/services-networking/network-policies/)はKubernetesクラスター内のPod間通信や、他のネットワークとの通信を制御するために利用します。ラベルを使用してPodを選択し、選択したPodに許可されるトラフィックを指定するルールを定義します。Network PolicyはCNIプラグインによって実装され、Network Policyを利用するにはNetwork PolicyをサポートするCNIプラグインを使用する必要があります。

### Network Policyに対応するネットワークプラグイン

FlannelはNetwork Policyに対応していませんが、多くのネットワークプラグインはNetwork Policyに対応しており、VMware NSX-TもNetwork Policyに対応しています。各CNIプラグインは比較は以下の資料が参考になります。

- [コンテナネットワーキング（CNI）最前線](https://www.slideshare.net/motonorishindo/cni-124981353)
- [kubedex Kubernetes Network Plugins](https://kubedex.com/kubernetes-network-plugins/)

### VMware NSX-TのNetwork Policy実装

NSX-Tの分散ファイアウォールは、ハイパーバイザー上の仮想マシンに対して透過的なファイアウォール機能を提供することができます。ハイパーバイザー上のKubernetes環境でNSX-T Container Plug-inを利用することにより、Kubernetes環境のコンテナネットワークとしてNSX-Tを利用することが可能になります。Kubernetes環境ではハイパーバイザー上の仮想マシンがNodeとして機能し、仮想マシン内でPod(コンテナ)が起動します。コンテナ間の通信はNode内で行われる可能性があるため、コンテナ間の通信を制御するためにNode VM内のOpen vSwitchがハイパーバイザーと連携し、ハイパーバイザーの分散ファイアウォール機能により同一Node内のPod間の通信を制御することを実現しています。

NSX-TのCNIプラグインは、NCP(NSX-T Container Plug-in)とNSX Node Agent、Open vSwitchによって構成されます。NCPはKubernetes Masterと通信してPodの作成や削除に対してNSX Managerに対して論理ネットワークの変更を行います。NSX Node AgentはDaemonSetとしてKubernetes Node上機能し、Open vSwitchと共にNode上のコンテナネットワークの管理を行います。

```
# kubectl get pod -o wide
NAME                   READY   STATUS    RESTARTS   AGE     IP             NODE     NOMINATED NODE
nsx-ncp-kvjdp          1/1     Running   0          3d19h   192.168.9.21   node01   <none>
nsx-node-agent-b2nzp   2/2     Running   0          3d19h   192.168.9.22   node02   <none>
nsx-node-agent-fpprh   2/2     Running   0          3d19h   192.168.9.21   node01   <none>
```


## 確認してみた

[Enforcing Network Policies in Kubernetes](https://kubernetes.io/blog/2017/10/enforcing-network-policies-in-kubernetes/)を参考にNetwork Policyを構成して、NSX-Tの実装を確認してみます。
確認は以下の環境で行いました。

- Ubuntu 16.04.5
- Kubernetes 1.12.3
- vSphere 6.7.0
- NSX-T 2.3
- NSX Container Plugin 2.3.1

### Nginx Podの作成

テスト用のnginx podを作成します。

```
# cat nginx.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.15.7-alpine
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    app: nginx
```

```
# kubectl apply -f nginx.yaml
deployment.apps/nginx-deployment created
service/nginx created
```

作成されたpodを確認します。作成されたnginx podには```app=nginx```ラベルが付与され、```172.15.0.4```と```172.15.0.5```のIPアドレスが割り当てられ、内部向けのClusterIPとして```10.110.125.44```が割り当てられています。

```
# kubectl get pod -l app=nginx -o wide
NAME                                READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE
nginx-deployment-797d64cb5f-6m4xl   1/1     Running   0          9s    172.15.0.5   node01   <none>
nginx-deployment-797d64cb5f-lrhc6   1/1     Running   0          9s    172.15.0.4   node02   <none>

# kubectl get svc -o wide
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE   SELECTOR
kubernetes   ClusterIP   10.96.0.1       <none>        443/TCP   19h   <none>
nginx        ClusterIP   10.110.125.44   <none>        80/TCP    17s   app=nginx
```

作成されたPodはNSX Manager上でも論理ポートとして認識されます。

![Kobito.0Gqjz5.png](https://qiita-image-store.s3.amazonaws.com/0/27668/47e5b1b5-6d88-1a1a-4c2d-0bf37de4fcfc.png "Kobito.0Gqjz5.png")

論理ポートの詳細画面では、PodのIPアドレスが認識されていることがわかります。以下の画面は、Pod ```nginx-deployment-797d64cb5f-6m4xl```の詳細画面です。

![Kobito.GIFFzG.png](https://qiita-image-store.s3.amazonaws.com/0/27668/cf37be57-6c8a-6d00-95ca-9b2649d7e2fe.png "Kobito.GIFFzG.png")


### クライアント用Podの作成

busyboxコンテナイメージでクライアント用のPodを起動します。

```
# kubectl run busybox --rm -ti --image=busybox /bin/sh
kubectl run --generator=deployment/apps.v1beta1 is DEPRECATED and will be removed in a future version. Use kubectl create instead.
If you don't see a command prompt, try pressing enter.
/ # 
```

Network Policyを利用していないため、wgetコマンドでnginxにアクセスしてみるとClusterIP経由でアクセスが可能です。

```
/ # wget --spider --timeout 1 nginx
Connecting to nginx (10.110.125.44:80)
/ # 
```

### Network Policyの適用

以下のNetwork Policyはを適用します。'''app=foo'''ラベルが付いているPodはnginxに対するアクセスを許可します。

```
# cat access-nginx.yaml
kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: access-nginx
spec:
  podSelector:
    matchLabels:
      app: nginx
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: foo
```

```
# kubectl apply -f access-nginx.yaml
networkpolicy.networking.k8s.io/access-nginx created
# kubectl get networkpolicy
NAME           POD-SELECTOR   AGE
access-nginx   app=nginx      6s
```

### クライアント用Podからのアクセス確認

クライアント用Podには```app=foo```ラベルを付与していないため、Network Policyが有効化されたことでnginxに対してアクセスができなくなります。

```
/ # wget --spider --timeout 1 nginx
Connecting to nginx (10.110.125.44:80)
wget: download timed out
/ # 
```

### 分散ファイアウォールルールの確認

Network Policyを適用したことにより、NSX Managerの分散ファイアウォールルールにセクションとルールが追加されています。

![Kobito.wfi4u5.png](https://qiita-image-store.s3.amazonaws.com/0/27668/72c39023-d59c-eec8-be01-c060a0602d78.png "Kobito.wfi4u5.png")

追加されているルールは次のような状態です。適用先はすべて```proj-k8scluster-default```となっています。

| 名前 | 送信元 | 宛先 | アクション |
|:----|:------|:-----|:---------|
|ir-k8scluster-default-access-nginx-all|src-k8scluster-default-access-nginx-all|tgt-k8scluster-default-access-nginx|許可|
|ir-k8scluster-default-access-nginx|任意|tgt-k8scluster-default-access-nginx|ドロップ|
|er-k8scluster-default-access-nginx|tgt-k8scluster-default-access-nginx|任意|許可|

送信元/宛先として利用されている```src-k8scluster-default-access-nginx-all```と```tgt-k8scluster-default-access-nginx```はNSX ManagerのIPセットとして定義されており、```tgt-k8scluster-default-access-nginx```にはnginx PodのIPアドレスが登録されています。(この時点では```src-k8scluster-default-access-nginx-all```には何も登録されていません)

![Kobito.4Zok8h.png](https://qiita-image-store.s3.amazonaws.com/0/27668/56c51e8e-d0b3-2186-7259-26b36d798d55.png "Kobito.4Zok8h.png")


適用先の```proj-k8scluster-default```は、NSX ManagerのNS Groupとして定義されており、以下のようなメンバーシップ基準によりdefaultネームスペースに存在するすべてのPodがこのNS Groupにマッチします。(今回はdefaultネームスペースで確認を行っているため)

![Kobito.GH9NVN.png](https://qiita-image-store.s3.amazonaws.com/0/27668/41bf3dcd-3920-8f50-9045-31aa09da9198.png "Kobito.GH9NVN.png")


### クライアント用PodのLabelの更新 (app=foo)

起動済みのクライアント用Podのラベルを更新して、```app=foo```に変更します。

```
# kubectl get pod -o wide
NAME                                READY   STATUS    RESTARTS   AGE     IP             NODE     NOMINATED NODE
busybox-68f48b9c57-bt5fg            1/1     Running   0          3m13s   172.15.0.2     node02   <none>
nginx-deployment-797d64cb5f-6m4xl   1/1     Running   0          5m28s   172.15.0.5     node01   <none>
nginx-deployment-797d64cb5f-lrhc6   1/1     Running   0          5m28s   172.15.0.4     node02   <none>
nsx-ncp-kvjdp                       1/1     Running   0          5h33m   192.168.9.21   node01   <none>
nsx-node-agent-b2nzp                2/2     Running   0          5h31m   192.168.9.22   node02   <none>
nsx-node-agent-fpprh                2/2     Running   0          5h31m   192.168.9.21   node01   <none>

# kubectl label pod busybox-68f48b9c57-bt5fg --overwrite app=foo
pod/busybox-68f48b9c57-bt5fg labeled
```

再度アクセスを確認すると、クライアント用Podからnginxへのアクセスが可能になりました。

```
/ # wget --spider --timeout 1 nginx
Connecting to nginx (10.110.125.44:80)
/ # 
```

ラベルを更新したことにより、```src-k8scluster-default-access-nginx-all```IPセットにクライアント用PodのIPアドレス(172.16.0.2)が追加されています。

![Kobito.9zMn19.png](https://qiita-image-store.s3.amazonaws.com/0/27668/4dcfc2a2-de25-80f5-8351-431c5f4d0b2d.png "Kobito.9zMn19.png")


### クライアント用PodのLabelの更新 (app=bar)

クライアント用PodのLabelを```app=bar```に変更すると、```src-k8scluster-default-access-nginx-all```IPセットからクライアント用Podのアドレスが削除され、再びnginxにアクセスできなくなります。

```
# kubectl label pod busybox-68f48b9c57-bt5fg --overwrite app=bar
pod/busybox-68f48b9c57-bt5fg labeled
```

```
/ # wget --spider --timeout 1 nginx
Connecting to nginx (10.110.125.44:80)
wget: download timed out
/ # 
```

## Traceflow機能

NSX ManagerにはTraceflowと呼ばれる論理ネットワークの可視化ツールがあり、この機能を使うことによりPod間通信の通信パスを確認することも可能です。

![Kobito.Rt2vF0.png](https://qiita-image-store.s3.amazonaws.com/0/27668/85691821-6099-a0ee-bbd9-30ace129f5ef.png "Kobito.Rt2vF0.png")


## Node VMとハイパーバイザー内の状態

Node VM上でOpen vSwitchの状態を確認すると、Pod用の論理ポートが生成され、VLAN IDが付与されています。

```
root@node02:~# ovs-vsctl show
3e4093e6-ef13-47d9-a6fa-c8ef144dd7b4
    Bridge br-int
        Port "ens192"
            Interface "ens192"
        Port br-int
            Interface br-int
                type: internal
        Port "busybox-68f48b9c57-bt5fg_7c45839e0879a86"
            tag: 14
            Interface "7c45839e0879a86"
        Port nsx_agent_outer
            tag: 4094
            Interface nsx_agent_outer
        Port "coredns-576cbf47c7-c5wfw_b9d8ffb2df016a5"
            tag: 1
            Interface "b9d8ffb2df016a5"
        Port "nginx-deployment-797d64cb5f-lrhc6_2c0ea440acf1152"
            tag: 13
            Interface "2c0ea440acf1152"
    ovs_version: "2.9.1.9968033"
```

ハイパーバイザー内でNode VMのスイッチポートに対する接続状態を確認すると、Node VM上で管理されているOpen vSwitchの論理ポートがNode VMのサブインターフェースとしてハイパーバイザー上で認識されており、各ポートには分散ファイアウォール用のフィルターが適用されています。Node VM内で起動するコンテナ間の通信制御がハイパーバイザーの分散ファイアウォールにオフロードされています。

```
[root@nsxt-esx02:~] summarize-dvfilter
...
world 5739119 vmm0:node02 vcUuid:'50 36 53 ba cb 95 d2 06-e8 8c 33 b4 7b ba d1 0e'
 port 67108884 node02.eth1
  vNic slot 2
   name: nic-5739119-eth1-vmware-sfw.2
   agentName: vmware-sfw
   state: IOChain Attached
   vmState: Detached
   failurePolicy: failClosed
   serviceVMID: none
   filter source: Dynamic Filter Creation
...
 port 67108904 node02.eth1.13
  vNic slot 1
   name: nic-5739119-eth33-vmware-sfw.1
   agentName: vmware-sfw
   state: IOChain Attached
   vmState: Detached
   failurePolicy: failClosed
   serviceVMID: none
   filter source: Alternate Opaque Channel
 port 67108906 node02.eth1.14
  vNic slot 1
   name: nic-5739119-eth35-vmware-sfw.1
   agentName: vmware-sfw
   state: IOChain Attached
   vmState: Detached
   failurePolicy: failClosed
   serviceVMID: none
   filter source: Alternate Opaque Channel
...
```

Pod向けのインターフェースに適用されているフィルターを確認すると分散ファイアウォールルールが実際に適用されている事がわかります。

```
[root@nsxt-esx02:~] vsipioctl getrules -f nic-5739119-eth33-vmware-sfw.1
ruleset mainrs {
  # generation number: 0
  # realization time : 2018-12-17T09:22:20
  rule 6266 at 1 in protocol any from addrset 105ee703-a9f2-4a5c-bf48-e56338b44a97 to addrset 9a0c2b94-3a78-42af-9d38-a2432cc89a00 accept;
  rule 6269 at 2 in protocol any from any to addrset 9a0c2b94-3a78-42af-9d38-a2432cc89a00 drop;
  rule 6270 at 3 out protocol any from addrset 9a0c2b94-3a78-42af-9d38-a2432cc89a00 to any accept;
  rule 2 at 4 inout protocol any from any to any accept;
}

ruleset mainrs_L2 {
  # generation number: 0
  # realization time : 2018-12-17T09:22:20
  rule 1 at 1 inout ethertype any stateless from any to any accept;
}
```

ファイアウォールルールの送信元、宛先として利用されているアドレスセットの内容を確認することが可能です。

```
[root@nsxt-esx02:~] vsipioctl getaddrsets -f nic-5739119-eth33-vmware-sfw.1
addrset 105ee703-a9f2-4a5c-bf48-e56338b44a97 {
# generation number: 0
# realization time : 2018-12-17T09:30:34
ip 172.15.0.2,
}

addrset 9a0c2b94-3a78-42af-9d38-a2432cc89a00 {
# generation number: 0
# realization time : 2018-12-17T09:22:19
ip 172.15.0.4,
ip 172.15.0.5,
}
```


## まとめ

NSX-Tをk8sのコンテナネットワークとして利用すことにより、NSX-Tの分散ファイアウォール機能によりNetwork Policyを利用することが可能です。また、NSX-TはKubernetesのネームスペース作成じに動的にセグメントを生成したり、オンプレミスの環境であっても```type: LoadBalancer```の利用が可能になります。仮想マシン環境との親和性も高いため、NSX-Tの管理画面を通じて、Kubernetes環境とVM環境が混在する場合も一貫したネットワーク管理が可能になります。