Multusのフィジビリティスタディ

# Multus CNIを試してみる

Multusは2つ以上のCNIのNetwork InterfaceをなんとかPODへアタッチする仕組みで、肝心なところは基本的に連携するCNIに依存し、IP Address Management(IPAM)もCNIに頼る思想です。  
一方で、一般的にCNIが提供されていないNodeの物理NICにつないでいくために以下のPluginが用意されており、別途CNIを用意しなくてもMultus CRDをインストールする手順を実行すると利用可能となります。このためか、一般的なMultusに関するBlog等を見るとこのPluginを見て「Multus＝2つ目のNICを使う仕組み」と強調されてる気がしますが、異なる特徴の複数CNI（SR-IOV CNIなど）を取り入れる仕組みとしての利用として考えたほうが良いかもしれません。ただ、取り込むCNI（特にIPAM）がIPAM含め対応している必要ががありそうです。（calicoはいまのところうまくいってません。）それと、K8s Serviceとの連携もいまのところ難しいです。  

* `bridge`: Creates a bridge, adds the host and the container to it.
* `ipvlan`: Adds an [ipvlan](https://www.kernel.org/doc/Documentation/networking/ipvlan.txt) interface in the container.
* `loopback`: Set the state of loopback interface to up.
* `macvlan`: Creates a new MAC address, forwards all traffic to that to the container.
* `ptp`: Creates a veth pair.
* `vlan`: Allocates a vlan device.
* `host-device`: Move an already-existing device into a container.

これ以外に`flannel`のinterfaceを付与するPluginだけは別途Flannelをインストールしなくても提供されます。

https://github.com/containernetworking/plugins

実際、このディフォルトのPluginと連携する以下IPAM Configが用意されていますが、（いまのところ）貧弱でProductionユースには用途が限定されるものと思われます。

* `dhcp`: Runs a daemon on the host to make DHCP requests on behalf of the container
* `host-local`: Maintains a local database of allocated IPs
* `static`:  Allocate a static IPv4/IPv6 addresses to container and it's useful in debugging purpose.

認識しておかないといけないポイントとして、これらのIPAMは一般的なIPAM-CNIのようにCluster wideで連携してくれるものではありません。`host-local`に至ってはNode毎にIPアドレスの資源管理をするため、Cluster wideで設定を入れておくと（NetworkAttachmentDefinitionリソース）複数NodeでPODを生成するとIPアドレス重複が起きます。このため、単に`Deployment`や`StafulSet`等でスケーリングだけでIPアドレスの資源管理が難しく、管理方法を別途検討するか、外部にDHCPサーバを建てる必要があります。  
（[このIssue](https://github.com/intel/multus-cni/issues/185)ではglobal IPAMが可能な記載がありましたが、手前の環境ではダメでした。他の条件もありそうなのでコードを見てみます。）  
`static`については現時点の最新版（v0.8.6）では使えませんでした。（CNIが無いと怒られるんですが`host-local`指定にしておいてPODのManifest側で設定すれば同じことが出来そうです。）

`spec.containers.securityContext.privileged`を`true`を設定するとSecondary IPを付与できます。
~~ちなみにPod内のWorkload側でSecondary IPを付与したりすることは権限上出来ませんでした。（Priviledgeの設定をうまくやるといけるのかも？）~~

---
## 動作検証 その1

以下3つのInterfaceを付与したPODをDeploymentで3POD生成してみます。

* calico（ディフォルトのCNI）
* flannel #1
* flannel #2
* macvlan

`macvlan`のIPAMは`host-local`とするため各NodeにIP rangeの設定を入れています。


### `network-attachment-definitions.k8s.cni.cncf.io`のリソース生成

`flannel`は以下でCluster wideのIPAMを設定が可能です。

    kubectl apply -f - << EOF
    apiVersion: "k8s.cni.cncf.io/v1"
    kind: NetworkAttachmentDefinition
    metadata:
      name: vlan-conf
    spec:
      config: '{
                "cniVersion": "0.3.1",
                "plugins": [
              {
                "type": "flannel",
                "delegate": {
                  "hairpinMode": true,
                  "isDefaultGateway": true
                }
              },
              {
                "type": "portmap",
                "capabilities": {"portMappings": true},
                "snat": true
              }
                  ]
            }'
    EOF

`macvlan`のConfigは各Nodeで設定するため`spec.config`は空になっています。

    kubectl apply -f - << EOF
    apiVersion: "k8s.cni.cncf.io/v1"
    kind: NetworkAttachmentDefinition
    metadata:
      name: macvlan-conf
	EOF



### 各NodeにIP rangeを設定

1つのNodeで以下を設定。 

    mkdir -p /etc/cni/multus/net.d/
	cat <<EOF > /etc/cni/multus/net.d/macvlan.conf
	{
	  "cniVersion": "0.3.0",
	  "type": "macvlan",
	  "name": "macvlan-conf",
	  "master": "enp6s0",
	  "mode": "bridge",
	  "ipam": {
	      "type": "host-local",
	      "ranges": [
	          [ {
	               "subnet": "172.16.1.0/24",
	               "rangeStart": "172.16.1.100",
	               "rangeEnd": "172.16.1.149"
	          } ]
	      ]
	  }
	}
	EOF

もう1つのNodeで以下を設定。 

    mkdir -p /etc/cni/multus/net.d/
	cat <<EOF > /etc/cni/multus/net.d/macvlan.conf
	{
	  "cniVersion": "0.3.0",
	  "type": "macvlan",
	  "name": "macvlan-conf",
	  "master": "enp6s0",
	  "mode": "bridge",
	  "ipam": {
	      "type": "host-local",
	      "ranges": [
	          [ {
	               "subnet": "172.16.1.0/24",
	               "rangeStart": "172.16.1.150",
	               "rangeEnd": "172.16.1.199"
	          } ]
	      ]
	  }
	}
	EOF


### Deploymentをapply

自前のチェック用のContainerを生成しています。

    kubectl apply -f - << EOF
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: ncurl-multus
      labels:
        app: ncurl-multus
    spec:
      replicas: 3
      selector:
        matchLabels:
          app: ncurl-multus
      template:
        metadata:
          labels:
            app: ncurl-multus
          annotations:
            k8s.v1.cni.cncf.io/networks: vlan-conf, vlan-conf, macvlan-conf
        spec:
          containers:
          - name: ncurl-multul
            image: tomotake/ubuntu-ncurl:latest
            command: ["bash"]
            args: ["-c", "mknod -m 777 fifo p ; while true ; do cat fifo | nc -v -t -k -l 30000 | tee fifo ; done"]
            ports:
            - containerPort: 30000
              protocol: TCP
              name: tcp-test-0
            readinessProbe:
              tcpSocket:
                port: 30000
              failureThreshold: 1
              periodSeconds: 1
    EOF


### 生成したPodのIF確認

以下のように2つのNodeにまたがって生成されました。

    $ kubectl get po -o wide -l app=ncurl-multus
    NAME                            READY   STATUS    RESTARTS   AGE     IP               NODE       NOMINATED NODE   READINESS GATES
    ncurl-multus-785bf987d7-q4xxw   1/1     Running   0          2m43s   172.16.188.244   ubu05-01   <none>           <none>
    ncurl-multus-785bf987d7-rrnpk   1/1     Running   0          2m43s   172.16.253.176   ubu05-02   <none>           <none>
    ncurl-multus-785bf987d7-sxn2f   1/1     Running   0          2m43s   172.16.189.4     ubu05-01   <none>           <none>

この結果こんな感じにNW IFが付与されています。

    $ for p in $( kubectl get po -l app=ncurl-multus -o name) ; do kubectl exec $p -- hostname ; kubectl exec $p -- ip a | grep inet\  ; done 
    ncurl-multus-785bf987d7-q4xxw
        inet 127.0.0.1/8 scope host lo
        inet 172.16.188.244/32 scope global eth0
        inet 10.244.0.80/24 scope global net1
        inet 10.244.0.81/24 scope global net2
        inet 172.16.1.100/24 scope global net3
    ncurl-multus-785bf987d7-rrnpk
        inet 127.0.0.1/8 scope host lo
        inet 172.16.253.176/32 scope global eth0
        inet 10.244.0.72/24 scope global net1
        inet 10.244.0.73/24 scope global net2
        inet 172.16.1.150/24 scope global net3
    ncurl-multus-785bf987d7-sxn2f
        inet 127.0.0.1/8 scope host lo
        inet 172.16.189.4/32 scope global eth0
        inet 10.244.0.82/24 scope global net1
        inet 10.244.0.83/24 scope global net2
        inet 172.16.1.101/24 scope global net3

---
## 動作検証 その2

次にこの状態のまま以下で`NetworkAttachmentDefinition`の`macvlan`の設定をcluster wideの設定で入れ、POD側で固定IPアドレスの設定を行います。  
`NetworkAttachmentDefinition`側のIPAM設定でIP rangeを指定していますが、この指定がなかったりこのrangeから外れたIPアドレスをPODに生成しようとしても`ContainerCreating`状態で止まってしまいます。  

    kubectl apply -f - << EOF
    apiVersion: "k8s.cni.cncf.io/v1"
    kind: NetworkAttachmentDefinition
    metadata:
      name: macvlan-conf
    spec:
      config: '{
                "cniVersion": "0.3.1",
                "plugins": [
                    {
                        "type": "macvlan",
                        "master": "enp6s0",
                        "capabilities": { "ips": true },
                        "ipam": {
                          "type": "host-local",
                          "subnet": "172.16.1.0/24",
                          "rangeStart": "172.16.1.200",
                          "rangeEnd": "172.16.1.246",
                          "routes": [
                          { "dst": "0.0.0.0/0" }
                          ],
                          "gateway": "172.16.1.1"
                        }
                    }, {
                        "capabilities": { "mac": true },
                        "type": "tuning"
                    } ]
            }'
    EOF

### IPアドレス固定のPOD生成

以下、複数のNW IFを付与したPODを生成してみます。
* calico（ディフォルトのCNI）
* flannel #1
* flannel #2
* macvlan
* macvlan（固定IPアドレス指定）

IPアドレスを固定指定しているので、Deploymentですが`replicas: 1`になっています。


    kubectl apply -f - << EOF
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: ncurl-multus-2
      labels:
        app: ncurl-multus-2
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: ncurl-multus-2
      template:
        metadata:
          labels:
            app: ncurl-multus-2
          annotations:
            k8s.v1.cni.cncf.io/networks: '[
              { "name": "vlan-conf" },
              { "name": "vlan-conf" }, 
              { "name": "macvlan-conf" },
              { "name": "macvlan-conf",
                "ips": [ "172.16.1.201" ]}
            ]'        
        spec:
          containers:
          - name: ncurl-multus
            image: tomotake/ubuntu-ncurl:latest
            command: ["bash"]
            args: ["-c", "mknod -m 777 fifo p ; while true ; do cat fifo | nc -v -t -k -l 30000 | tee fifo ; done"]
            ports:
            - containerPort: 30000
              protocol: TCP
              name: tcp-test-0
            readinessProbe:
              tcpSocket:
                port: 30000
              failureThreshold: 1
              periodSeconds: 1



### 生成したPodのIF確認

IP rangeの設定（172.16.1.150-199）が残っているNodeでPODが生成されています。

    tomotake@ubu05-00:~/multus-test$ kubectl get po -l app=ncurl-multus-2 -o wide
    NAME                              READY   STATUS    RESTARTS   AGE   IP               NODE       NOMINATED NODE   READINESS GATES
    ncurl-multus-2-848bfd457c-d7wlk   1/1     Running   0          46m   172.16.253.173   ubu05-02   <none>           <none>

固定したIPアドレスが付与されいます。

    $ for p in $( kubectl get po -l app=ncurl-multus-2 -o name) ; do kubectl exec $p -- hostname ; kubectl exec $p -- ip a | grep inet\  ; done
    ncurl-multus-2-848bfd457c-d7wlk                                              
        inet 127.0.0.1/8 scope host lo                                                                                                           
        inet 172.16.253.173/32 scope global eth0                                                                                        
        inet 10.244.0.66/24 scope global net1                                                                                           
        inet 10.244.0.67/24 scope global net2
        inet 172.16.1.211/24 scope global net3
        inet 172.16.1.201/24 scope global net4

追加の確認としてNodeにIP rangeの設定を残したまま先程の確認で使用したPODのをdeleteして再生成させIPAMのIPアドレス自動割り当ての挙動を確認しました。  
IPAM`host-local`の場合、Node側のIP rangeの設定よりCluster wide側の設定が優先的に扱われているようです。

    $ kubectl get po -l app=ncurl-multus -o wide
    NAME                            READY   STATUS    RESTARTS   AGE     IP               NODE       NOMINATED NODE   READINESS GATES
    ncurl-multus-785bf987d7-6kj2z   1/1     Running   0          2m16s   172.16.253.177   ubu05-02   <none>           <none>
    ncurl-multus-785bf987d7-9lgr2   1/1     Running   0          2m15s   172.16.189.5     ubu05-01   <none>           <none>
    ncurl-multus-785bf987d7-q2fjj   1/1     Running   0          2m15s   172.16.253.178   ubu05-02   <none>           <none>

しかもIPアドレス重複が発生してしまっている状態になっていました。

    $ for p in $( kubectl get po -l app=ncurl-multus -o name) ; do kubectl exec $p -- hostname ; kubectl exec $p -- ip a | grep inet\  ; done
    ncurl-multus-785bf987d7-6kj2z
        inet 127.0.0.1/8 scope host lo
        inet 172.16.253.177/32 scope global eth0
        inet 10.244.0.74/24 scope global net1
        inet 10.244.0.75/24 scope global net2
        inet 172.16.1.200/24 scope global net3
    ncurl-multus-785bf987d7-9lgr2
        inet 127.0.0.1/8 scope host lo
        inet 172.16.189.5/32 scope global eth0
        inet 10.244.0.84/24 scope global net1
        inet 10.244.0.85/24 scope global net2
        inet 172.16.1.200/24 scope global net3
    ncurl-multus-785bf987d7-q2fjj
        inet 127.0.0.1/8 scope host lo
        inet 172.16.253.178/32 scope global eth0
        inet 10.244.0.76/24 scope global net1
        inet 10.244.0.77/24 scope global net2
        inet 172.16.1.202/24 scope global net3