kubernetesで活用しているCNI PluginのIPAM動作を試す

通常、kubernetesでPod環境を作成する際には、Kubeletが、CRIを活用して、Pod用Sandboxコンテナを生成します。
そして、Sandboxコンテナに割り当てられたNetwork Namespaceに、Pod環境として動作できるように、CNI Pluginを活用して、IPアドレスが付与されます。

[- Architecture of The CRI Plugin より引用 -](https://github.com/containerd/cri/blob/v1.11.1/docs/architecture.md)
![architecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/f1530a18-6ab0-e775-ceda-24735e1d0698.png)


今回は、CNI Pluginを活用してIPAM動作を確認してみます。
ちなみに、kubernetes環境は、一切、使用しないのであしからず。。


# ■ CNIとは？
CNI（Container Network Interface）では、Linuxコンテナでネットワークインターフェイスを構成するプラグインを作成するための仕様とプラグインが活用されています。
ちなみに、これらの動作概要については、こちらのBlog記事が参考になりました。

- [マルチホストでのDocker Container間通信 第3回: Kubernetesのネットワーク(CNI, kube-proxy, kube-dns)](https://tech.uzabase.com/entry/2017/09/12/164756)
- [How a Kubernetes Pod Gets an IP Address](https://ronaknathani.com/blog/2020/08/how-a-kubernetes-pod-gets-an-ip-address/)

詳細を確認したい場合には、こちらが参考になります。

- [CNI Specification](https://www.cni.dev/docs/spec/)
- [CNI Plugins Overview](https://www.cni.dev/plugins/)

# ■ CNI Plugin環境整備
以下、手持ちのLinux環境を使って、簡単な動作確認を行っています。

## (1) Linux環境
- OSバージョン: Ubuntu 16.04.1 LTS
- Go Version: go1.15 linux/amd64

## (2) CNI Plugin環境セットアップ
[gitリポジトリ: containernetworking/plugins](https://github.com/containernetworking/plugins)から、ソースファイルを入手します
なお、以降の基本操作は、全て、rootユーザで行うものとします

```
# cd $HOME
# git clone https://github.com/containernetworking/plugins.git
# cd plugins
# git checkout -b v0.9.0 v0.9.0
Switched to a new branch 'v0.9.0'
```
手動で、Pluginコマンドのビルドを行います

```
# ./build_linux.sh 
Building plugins 
  bandwidth
  firewall
  flannel
  portmap
  sbr
  tuning
  vrf
  bridge
  host-device
  ipvlan
  loopback
  macvlan
  ptp
  vlan
  dhcp
  host-local
  static
```
CNI Pluginバイナリファイルが作成できたことを確認しておきます

```
# ls -l /root/plugins/bin
total 72620
-rwxr-xr-x 1 root root  4175439 Jan 14 09:12 bandwidth
-rwxr-xr-x 1 root root  4547099 Jan 14 09:12 bridge
-rwxr-xr-x 1 root root 10338275 Jan 14 09:12 dhcp
-rwxr-xr-x 1 root root  4811536 Jan 14 09:12 firewall
-rwxr-xr-x 1 root root  3365183 Jan 14 09:12 flannel
-rwxr-xr-x 1 root root  4155129 Jan 14 09:12 host-device
-rwxr-xr-x 1 root root  3584921 Jan 14 09:12 host-local
-rwxr-xr-x 1 root root  4307875 Jan 14 09:12 ipvlan
-rwxr-xr-x 1 root root  3546106 Jan 14 09:12 loopback
-rwxr-xr-x 1 root root  4382656 Jan 14 09:12 macvlan
-rwxr-xr-x 1 root root  3981546 Jan 14 09:12 portmap
-rwxr-xr-x 1 root root  4482408 Jan 14 09:12 ptp
-rwxr-xr-x 1 root root  3716713 Jan 14 09:12 sbr
-rwxr-xr-x 1 root root  3168905 Jan 14 09:12 static
-rwxr-xr-x 1 root root  3687960 Jan 14 09:12 tuning
-rwxr-xr-x 1 root root  4303412 Jan 14 09:12 vlan
-rwxr-xr-x 1 root root  3775552 Jan 14 09:12 vrf
```
## (3) CNIツール環境セットアップ
CNIツールをインストールします

```
# go get github.com/containernetworking/cni/cnitool
```
CNIツールが動作できることを確認しておきます

```
# cnitool
cnitool: Add, check, or remove network interfaces from a network namespace
  cnitool add   <net> <netns>
  cnitool check <net> <netns>
  cnitool del   <net> <netns>
```

## (4) CNI用コンフィグレーションファイルの配置
CNI Pluginを呼び出して、"bridge"->"ipam"のパイプラインが動作できるようにコンフィグファイルを配置しておきます

```/etc/cni/net.d/10-mysample.conf
{
    "cniVersion": "0.4.0",
    "name": "mynet",
    "type": "bridge",
    "bridge": "mybridge",
    "isDefaultGateway": true,
    "ipam": {
        "type": "host-local",
        "subnet": "192.168.100.0/24"
     }
}
```

# ■ 実際に、CNIツールを動かしてみる

CNI Pluginを呼び出して、"bridge"->"ipam"のパイプライン動作によって、Netnetwork namespaceにIPアドレスが割り当てられる様子を確認してみます

## (1) 初期状態の確認
Network namespaceが存在していないことを確認しておく

```
# ip netns
```
Linux Bridgeが存在していないことを確認しておく

```
# brctl show | grep mybridge
```

## (2) テスト用Network namespaceの作成
手作業で、Network namespaceを作成します

```
# ip netns add testing1
# ip netns add testing2
# ip netns add testing3
```
Network namespaceが３個、作成されていることを確認しておきます

```
# ip netns
testing3
testing2
testing1
```

## (3) CNIツールを動かして、IPアドレスを割り当てる
### "testing1"ネームスペースでの作業
まずは、"testing1"ネームスペースに、IPアドレスを割り当てます

```
# CNI_PATH=/root/plugins/bin cnitool add mynet /var/run/netns/testing1
{
    "cniVersion": "0.4.0",
    "interfaces": [
        {
            "name": "mybridge",
            "mac": "3e:af:39:a3:b4:93"
        },
        {
            "name": "veth0820d945",
            "mac": "96:8b:02:29:7a:84"
        },
        {
            "name": "eth0",
            "mac": "e2:ab:88:4c:67:2d",
            "sandbox": "/var/run/netns/testing1"
        }
    ],
    "ips": [
        {
            "version": "4",
            "interface": 2,
            "address": "192.168.100.2/24",
            "gateway": "192.168.100.1"
        }
    ],
    "routes": [
        {
            "dst": "0.0.0.0/0",
            "gw": "192.168.100.1"
        }
    ],
    "dns": {}
```
"testing1"ネームスペースの”eth0”のネットワークインタフェースに割り当てられたIPアドレスを確認します
-> <b>”192.168.100.2/24”</b>が付与されています

```
# ip netns exec testing1 ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: eth0@if5404: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether e2:ab:88:4c:67:2d brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.100.2/24 brd 192.168.100.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::e0ab:88ff:fe4c:672d/64 scope link 
       valid_lft forever preferred_lft forever
```
Linux Bridgeの様子も確認しておきます
-> mybridgeが新たに作成されて、<b>”veth0820d945”</b>が接続されています

```
# brctl show
bridge name	bridge id		STP enabled	interfaces
... (snip)
mybridge		8000.3eaf39a3b493	no		veth0820d945
```


### "testing2"ネームスペースでの作業
まずは、"testing2"ネームスペースに、IPアドレスを割り当てます

```
# CNI_PATH=/root/plugins/bin cnitool add mynet /var/run/netns/testing2
{
    "cniVersion": "0.4.0",
    "interfaces": [
        {
            "name": "mybridge",
            "mac": "3e:af:39:a3:b4:93"
        },
        {
            "name": "vethdc29b15e",
            "mac": "76:59:67:9d:1a:14"
        },
        {
            "name": "eth0",
            "mac": "e2:55:2e:09:ad:75",
            "sandbox": "/var/run/netns/testing2"
        }
    ],
    "ips": [
        {
            "version": "4",
            "interface": 2,
            "address": "192.168.100.3/24",
            "gateway": "192.168.100.1"
        }
    ],
    "routes": [
        {
            "dst": "0.0.0.0/0",
            "gw": "192.168.100.1"
        }
    ],
    "dns": {}
```
"testing2"ネームスペースの”eth0”のネットワークインタフェースに割り当てられたIPアドレスを確認します
-> <b>”192.168.100.3/24”</b>が付与されています

```
# ip netns exec testing2 ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: eth0@if5405: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether e2:55:2e:09:ad:75 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.100.3/24 brd 192.168.100.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::e055:2eff:fe09:ad75/64 scope link 
       valid_lft forever preferred_lft forever
```
Linux Bridgeの様子も確認しておきます
-> mybridgeに、<b>”vethdc29b15e”</b>が接続されています

```
# brctl show
bridge name	bridge id		STP enabled	interfaces
... (snip)		
mybridge		8000.3eaf39a3b493	no		veth0820d945
							vethdc29b15e
```

### "testing3"ネームスペースでの作業
まずは、"testing3"ネームスペースに、IPアドレスを割り当てます

```
# CNI_PATH=/root/plugins/bin cnitool add mynet /var/run/netns/testing3
{
    "cniVersion": "0.4.0",
    "interfaces": [
        {
            "name": "mybridge",
            "mac": "3e:af:39:a3:b4:93"
        },
        {
            "name": "vethc3a9e0b8",
            "mac": "16:f9:fd:71:5e:a0"
        },
        {
            "name": "eth0",
            "mac": "6e:f8:3d:da:f0:bb",
            "sandbox": "/var/run/netns/testing3"
        }
    ],
    "ips": [
        {
            "version": "4",
            "interface": 2,
            "address": "192.168.100.4/24",
            "gateway": "192.168.100.1"
        }
    ],
    "routes": [
        {
            "dst": "0.0.0.0/0",
            "gw": "192.168.100.1"
        }
    ],
    "dns": {}
```
"testing3"ネームスペースの”eth0”のネットワークインタフェースに割り当てられたIPアドレスを確認します
-> <b>”192.168.100.4/24”</b>が付与されています

```
# ip netns exec testing3 ip a
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: eth0@if5406: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default 
    link/ether 6e:f8:3d:da:f0:bb brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.100.4/24 brd 192.168.100.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::6cf8:3dff:feda:f0bb/64 scope link 
       valid_lft forever preferred_lft forever
```
Linux Bridgeの様子も確認しておきます
-> mybridgeに、<b>”vethc3a9e0b8”</b>が接続されています

```
# brctl show
bridge name	bridge id		STP enabled	interfaces
... (snip)		
mybridge		8000.3eaf39a3b493	no		veth0820d945
							vethc3a9e0b8
							vethdc29b15e
```

## (4) 実際に、Namespace間での疎通性を確認しておく
"testing1"ネームスペースから、"testing2"にpingを打ってみます

```
# ip netns exec testing1 ping -I 192.168.100.2 192.168.100.3
PING 192.168.100.3 (192.168.100.3) from 192.168.100.2 : 56(84) bytes of data.
64 bytes from 192.168.100.3: icmp_seq=1 ttl=64 time=0.169 ms
64 bytes from 192.168.100.3: icmp_seq=2 ttl=64 time=0.141 ms
64 bytes from 192.168.100.3: icmp_seq=3 ttl=64 time=0.155 ms
64 bytes from 192.168.100.3: icmp_seq=4 ttl=64 time=0.157 ms
64 bytes from 192.168.100.3: icmp_seq=5 ttl=64 time=0.156 ms
^C
--- 192.168.100.3 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 3998ms
rtt min/avg/max/mdev = 0.141/0.155/0.169/0.016 ms
```
"testing1"ネームスペースから、"testing3"にpingを打ってみます

```
# ip netns exec testing1 ping -I 192.168.100.2 192.168.100.4
PING 192.168.100.4 (192.168.100.4) from 192.168.100.2 : 56(84) bytes of data.
64 bytes from 192.168.100.4: icmp_seq=1 ttl=64 time=0.263 ms
64 bytes from 192.168.100.4: icmp_seq=2 ttl=64 time=0.131 ms
64 bytes from 192.168.100.4: icmp_seq=3 ttl=64 time=0.154 ms
64 bytes from 192.168.100.4: icmp_seq=4 ttl=64 time=0.156 ms
64 bytes from 192.168.100.4: icmp_seq=5 ttl=64 time=0.154 ms
^C
--- 192.168.100.4 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 3998ms
rtt min/avg/max/mdev = 0.131/0.171/0.263/0.048 ms
```

# ■ CNI Pluginの挙動の振り返り
CNI Pluginを呼び出して、"bridge"->"ipam"のパイプライン動作によって、Netnetwork namespaceにIPアドレスが割り当てられましたので、その結果を、振り返っておきます。


## (1) /var/lib/cni/networks/mynet

```
# ls -l /var/lib/cni/networks/mynet
total 16
-rw-r--r-- 1 root root 34 Jan 14 09:58 192.168.100.2
-rw-r--r-- 1 root root 34 Jan 14 10:11 192.168.100.3
-rw-r--r-- 1 root root 34 Jan 14 10:15 192.168.100.4
-rw-r--r-- 1 root root 13 Jan 14 10:15 last_reserved_ip.0
-rwxr-x--- 1 root root  0 Jan 11 12:31 lock
```
```/var/lib/cni/networks/mynet/192.168.100.2
cnitool-49346948b0d506e05f2d
```
```/var/lib/cni/networks/mynet/192.168.100.3
cnitool-256083c5997e421f082a
```
```/var/lib/cni/networks/mynet/192.168.100.4
cnitool-c11ae72283507a6e6b3c
```
```/var/lib/cni/networks/mynet/last_reserved_ip.0
192.168.100.4
```

## (2) /var/lib/cni/results
```
# ls -l /var/lib/cni/results
total 12
-rw------- 1 root root 763 Jan 14 10:11 mynet-cnitool-256083c5997e421f082a-eth0
-rw------- 1 root root 763 Jan 14 09:58 mynet-cnitool-49346948b0d506e05f2d-eth0
-rw------- 1 root root 763 Jan 14 10:15 mynet-cnitool-c11ae72283507a6e6b3c-eth0
```
```
# cat mynet-cnitool-49346948b0d506e05f2d-eth0 | jq .
{
  "kind": "cniCacheV1",
  "containerId": "cnitool-49346948b0d506e05f2d",
  "config": "eyJjbmlWZXJzaW9uIjoiMC40LjAiLCJuYW1lIjoibXluZXQiLCJwbHVnaW5zIjpbeyJicmlkZ2UiOiJteWJyaWRnZSIsImNuaVZlcnNpb24iOiIwLjQuMCIsImlwYW0iOnsic3VibmV0IjoiMTkyLjE2OC4xMDAuMC8yNCIsInR5cGUiOiJob3N0LWxvY2FsIn0sImlzRGVmYXVsdEdhdGV3YXkiOnRydWUsIm5hbWUiOiJteW5ldCIsInR5cGUiOiJicmlkZ2UifV19",
  "ifName": "eth0",
  "networkName": "mynet",
  "result": {
    "cniVersion": "0.4.0",
    "dns": {},
    "interfaces": [
      {
        "mac": "3e:af:39:a3:b4:93",
        "name": "mybridge"
      },
      {
        "mac": "96:8b:02:29:7a:84",
        "name": "veth0820d945"
      },
      {
        "mac": "e2:ab:88:4c:67:2d",
        "name": "eth0",
        "sandbox": "/var/run/netns/testing1"
      }
    ],
    "ips": [
      {
        "address": "192.168.100.2/24",
        "gateway": "192.168.100.1",
        "interface": 2,
        "version": "4"
      }
    ],
    "routes": [
      {
        "dst": "0.0.0.0/0",
        "gw": "192.168.100.1"
      }
    ]
  }
}
```
```
# cat mynet-cnitool-256083c5997e421f082a-eth0 |jq .
{
  "kind": "cniCacheV1",
  "containerId": "cnitool-256083c5997e421f082a",
  "config": "eyJjbmlWZXJzaW9uIjoiMC40LjAiLCJuYW1lIjoibXluZXQiLCJwbHVnaW5zIjpbeyJicmlkZ2UiOiJteWJyaWRnZSIsImNuaVZlcnNpb24iOiIwLjQuMCIsImlwYW0iOnsic3VibmV0IjoiMTkyLjE2OC4xMDAuMC8yNCIsInR5cGUiOiJob3N0LWxvY2FsIn0sImlzRGVmYXVsdEdhdGV3YXkiOnRydWUsIm5hbWUiOiJteW5ldCIsInR5cGUiOiJicmlkZ2UifV19",
  "ifName": "eth0",
  "networkName": "mynet",
  "result": {
    "cniVersion": "0.4.0",
    "dns": {},
    "interfaces": [
      {
        "mac": "3e:af:39:a3:b4:93",
        "name": "mybridge"
      },
      {
        "mac": "76:59:67:9d:1a:14",
        "name": "vethdc29b15e"
      },
      {
        "mac": "e2:55:2e:09:ad:75",
        "name": "eth0",
        "sandbox": "/var/run/netns/testing2"
      }
    ],
    "ips": [
      {
        "address": "192.168.100.3/24",
        "gateway": "192.168.100.1",
        "interface": 2,
        "version": "4"
      }
    ],
    "routes": [
      {
        "dst": "0.0.0.0/0",
        "gw": "192.168.100.1"
      }
    ]
  }
}
```
```
# cat mynet-cnitool-c11ae72283507a6e6b3c-eth0 | jq .
{
  "kind": "cniCacheV1",
  "containerId": "cnitool-c11ae72283507a6e6b3c",
  "config": "eyJjbmlWZXJzaW9uIjoiMC40LjAiLCJuYW1lIjoibXluZXQiLCJwbHVnaW5zIjpbeyJicmlkZ2UiOiJteWJyaWRnZSIsImNuaVZlcnNpb24iOiIwLjQuMCIsImlwYW0iOnsic3VibmV0IjoiMTkyLjE2OC4xMDAuMC8yNCIsInR5cGUiOiJob3N0LWxvY2FsIn0sImlzRGVmYXVsdEdhdGV3YXkiOnRydWUsIm5hbWUiOiJteW5ldCIsInR5cGUiOiJicmlkZ2UifV19",
  "ifName": "eth0",
  "networkName": "mynet",
  "result": {
    "cniVersion": "0.4.0",
    "dns": {},
    "interfaces": [
      {
        "mac": "3e:af:39:a3:b4:93",
        "name": "mybridge"
      },
      {
        "mac": "16:f9:fd:71:5e:a0",
        "name": "vethc3a9e0b8"
      },
      {
        "mac": "6e:f8:3d:da:f0:bb",
        "name": "eth0",
        "sandbox": "/var/run/netns/testing3"
      }
    ],
    "ips": [
      {
        "address": "192.168.100.4/24",
        "gateway": "192.168.100.1",
        "interface": 2,
        "version": "4"
      }
    ],
    "routes": [
      {
        "dst": "0.0.0.0/0",
        "gw": "192.168.100.1"
      }
    ]
  }
}
```
これらの情報と、CNI Pluginのソースコードでの挙動がマッチングできれば、将来、CNI Pluginの独自開発に役立つはず。

# ■ Namespace環境のクリーンナップ
最後に、環境をキレイに戻しておきましょう。

```
# CNI_PATH=/root/plugins/bin cnitool del mynet /var/run/netns/testing1
# CNI_PATH=/root/plugins/bin cnitool del mynet /var/run/netns/testing2
# CNI_PATH=/root/plugins/bin cnitool del mynet /var/run/netns/testing3
```
```
# ip link set mybridge down
# brctl delbr mybridge
```
```
# ip netns del testing1
# ip netns del testing2
# ip netns del testing3
```

以上です。。。

# ■ （追記）IPv4/IPv6 DualStackのIPAM動作を確認する
追加確認として、["host-local IP address management plugin"](https://www.cni.dev/plugins/ipam/host-local/)を参考に、IPv4/IPv6 DualStackのIPAM動作を確認してみます

```
# CNI_COMMAND=ADD CNI_CONTAINERID=example CNI_NETNS=/dev/null CNI_IFNAME=dummy0 CNI_PATH=. /root/plugins/bin/host-local << EOS
> {
>   "cniVersion": "0.3.1",
>   "name": "examplenet",
>   "ipam": {
>     "type": "host-local",
>     "ranges": [
>       [
>         {
>           "subnet": "203.0.113.0/24"
>         }
>       ],
>       [
>         {
>           "subnet": "2001:db8:1::/64"
>         }
>       ]
>     ],
>     "dataDir": "/tmp/cni-example"
>   }
> }
> EOS
{
    "cniVersion": "0.3.1",
    "ips": [
        {
            "version": "4",
            "address": "203.0.113.2/24",
            "gateway": "203.0.113.1"
        },
        {
            "version": "6",
            "address": "2001:db8:1::2/64",
            "gateway": "2001:db8:1::1"
        }
    ],
    "dns": {}
}
```
いい感じに、動作できている。