kubernetesのSR-IOVを試してみた

# はじめに

k8sでpodにVFのNICを割り当てて通信できるかを試してみました。
以下の実施内容の記録です。

* [SR-IOV環境の構築](#構築)
* [SR-IOVワークロードのデプロイ](#sr-iovワークロードのデプロイ)
* [SR-IOVネットワーク経由の通信確認](#sr-iov-nicをもつpodの通信確認)
* [ネットワークスループット計測](#ネットワークスループット計測)
* [Serviceを使ってSR-IOVネットワーク経由で通信する方法の検証](#serviceを使ってsr-iovネットワーク経由で通信する方法の検証)

# SR-IOVとは

* Single Root I/O Virtualizationの略
* PCIデバイス（NICやRAIDカード）側で仮想化をサポートする規格
* 従来，複数の仮想マシンから送られてきた1つのPCIデバイスへのI/O要求は，ハイパーバイザが調停して要求を一本化し，デバイスから返ってきた結果を仮想マシンに振り分けるソフト処理が必要だった。<br>しかし，SR-IOVに対応したデバイスがあれば，この処理をPCIデバイス側にオフロードできる。<br>ハードウエアによる処理であるため，パフォーマンスの大幅な向上が期待できる。
* VMwareのサイトには、「単一のルートポートにある単一の PCIe （Peripheral Component Interconnect Express） 物理デバイスを、ハイパーバイザーやゲスト OS に対して、複数の別個の物理デバイスとして認識されるようにする仕様です。」と説明されている。

<img src="https://xtech.nikkei.com/it/article/Keyword/20100115/343227/zu1.jpg?__scale=w:637,h:433,q:100&_sh=09f0ce0450" width=70%>
https://xtech.nikkei.com/it/article/Keyword/20100115/343227/zu1.jpg

# k8sでSR-IOVを使うには

[SR-IOV Network Device Plugin](https://github.com/k8snetworkplumbingwg/sriov-network-device-plugin)を使えば、k8sでSR-IOVを使うことができる。

以下のCNIプラグインが必要。

* Any CNI meta plugin supporting Device Plugin based network provisioning
    * [Multus CNI](https://github.com/intel/multus-cni), or [DANM](https://github.com/nokia/danm)
* SR-IOV CNI
    * uring Pod creation, plumbs allocated SR-IOV VF to a Pods network namespace using VF information given by the meta plugin
    * On Pod deletion, reset and release the VF from the Pod

# 環境

* サーバー
    * 機種：FUJITSU Server PRIMERGY RX2530 M5
    * 10G NIC：Intel® Ethernet Controller XL710（controleplane: × 2, worker: × 4）
    * OS：Ubuntu 20.04.2
* kubernetes v1.20.4
* containerd v1.3.3
* CNI
    * Calico v3.18.0
    * SR-IOV Network Device Plugin v3.3.1
    * Multus v3.4.1

# ネットワーク

| 用途 　　　　 | サブネット      | VLAN ID     |
| ------------ | -------------- | ----------- |
| 構築（SSH等） | 10.110.0.0/24  | 180         |
| k8s nodes    | 172.16.90.0/24 | 181         |
| SR-IOV       | 172.16.91.0/24 | 182 (untag) |

![network-diagram](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/350260/45f9558e-bdcf-8897-ad3f-611359e95892.png)

**本記事ではSR-IOVネットワークをuntag VLANとしましたが、後日tag VLANでも試してうまくいきました。**
**tag VLANを使う場合は、NetworkAttachmentDefinitionにVLAN IDを指定する必要があります。**

# 構築

## 前提条件

* k8sクラスタが構築済であること
* Worker nodeがSR-IOV対応のNICを有すること

[Quick Start](https://github.com/k8snetworkplumbingwg/sriov-network-device-plugin#quick-start)に従って、作業を進める。

## SR-IOV Virtual Functions(VF)作成

VF作成は、device pluginの機能に含まれないため、事前に作成しておく必要がある。

本記事では、VFを3つ作成する。

worker01

```
worker01:~$ cat /sys/class/net/enp24s0f2/device/sriov_numvfs 
0
worker01:~$ cat <<EOF | sudo tee /sys/class/net/enp24s0f2/device/sriov_numvfs
> 3
> EOF
3
worker01:~$ cat /sys/class/net/enp24s0f2/device/sriov_numvfs
3
worker01:~$ lspci | grep "Virtual Function"
18:0a.0 Ethernet controller: Intel Corporation Ethernet Virtual Function 700 Series (rev 02)
18:0a.1 Ethernet controller: Intel Corporation Ethernet Virtual Function 700 Series (rev 02)
18:0a.2 Ethernet controller: Intel Corporation Ethernet Virtual Function 700 Series (rev 02)
```

NICも確認してみる。

```
worker01:~$ ip l show enp24s0f2
6: enp24s0f2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9000 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 40:a6:b7:18:34:ca brd ff:ff:ff:ff:ff:ff
    vf 0     link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff, spoof checking on, link-state auto, trust off
    vf 1     link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff, spoof checking on, link-state auto, trust off
    vf 2     link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff, spoof checking on, link-state auto, trust off
worker01:~$ ip a show enp24s0f2v0
18: enp24s0f2v0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 6a:26:e3:6e:0f:82 brd ff:ff:ff:ff:ff:ff
worker01:~$ ip a show enp24s0f2v1
20: enp24s0f2v1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether ca:45:9b:1b:10:39 brd ff:ff:ff:ff:ff:ff
worker01:~$ ip a show enp24s0f2v2
19: enp24s0f2v2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether be:3d:19:83:c6:40 brd ff:ff:ff:ff:ff:ff
```

この設定はマシンの再起動でリセットされてしまうので、設定を永続化するためには`/etc/rc.local`に上記コマンドを指定する必要があるとのこと。

worker02

```
worker02:~$ lspci | grep "Virtual Function"
worker02:~$ cat <<EOF | sudo tee /sys/class/net/enp24s0f2/device/sriov_numvfs
> 3
> EOF
3
worker02:~$ lspci | grep "Virtual Function"
18:0a.0 Ethernet controller: Intel Corporation Ethernet Virtual Function 700 Series (rev 02)
18:0a.1 Ethernet controller: Intel Corporation Ethernet Virtual Function 700 Series (rev 02)
18:0a.2 Ethernet controller: Intel Corporation Ethernet Virtual Function 700 Series (rev 02)
worker02:~$ ip l show enp24s0f2
6: enp24s0f2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9000 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 40:a6:b7:18:37:ea brd ff:ff:ff:ff:ff:ff
    vf 0     link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff, spoof checking on, link-state auto, trust off
    vf 1     link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff, spoof checking on, link-state auto, trust off
    vf 2     link/ether 00:00:00:00:00:00 brd ff:ff:ff:ff:ff:ff, spoof checking on, link-state auto, trust off
worker02:~$ ip a show enp24s0f2v0
22: enp24s0f2v0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 8e:6b:a3:5e:91:4d brd ff:ff:ff:ff:ff:ff
worker02:~$ ip a show enp24s0f2v1
21: enp24s0f2v1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether ca:a0:3b:3f:68:99 brd ff:ff:ff:ff:ff:ff
worker02:~$ ip a show enp24s0f2v2
20: enp24s0f2v2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 8e:d4:33:5f:2e:06 brd ff:ff:ff:ff:ff:ff
```

## SR-IOV CNIのビルド

```
controlplane:~$ git clone https://github.com/k8snetworkplumbingwg/sriov-cni.git
Cloning into 'sriov-cni'...
remote: Enumerating objects: 87, done.
remote: Counting objects: 100% (87/87), done.
remote: Compressing objects: 100% (56/56), done.
remote: Total 20703 (delta 36), reused 37 (delta 27), pack-reused 20616
Receiving objects: 100% (20703/20703), 29.67 MiB | 7.01 MiB/s, done.
Resolving deltas: 100% (7504/7504), done.
controlplane:~$ cd sriov-cni/
controlplane:~/sriov-cni$ sudo apt install make

（省略）

controlplane:~/sriov-cni$ make
Running gofmt...
/bin/sh: 1: go: not found
Building golint...
make: go: Command not found
make: *** [Makefile:66: /home/xxxxxx/sriov-cni/bin/golint] Error 127
```

golangが入ってないので、インストールする。

```
controlplane:~$ wget https://golang.org/dl/go1.16.linux-amd64.tar.gz

（省略）

controlplane:~$ sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.16.linux-amd64.tar.gz
controlplane:~$ export PATH=$PATH:/usr/local/go/bin
controlplane:~$ go version
go version go1.16 linux/amd64
```

再度、ビルドを実行

```
controlplane:~/sriov-cni$ make

（省略）

controlplane:~/sriov-cni$ cp build/sriov /opt/cni/bin
```

ドキュメントにはないが、パーミションを他のCNIプラグインと合わせておく。

```
controlplane:~$ sudo chown root:root /opt/cni/bin/sriov 
controlplane:~$ sudo chmod 755 /opt/cni/bin/sriov 
controlplane:~$ sudo ls -l /opt/cni/bin/sriov 
-rwxr-xr-x 1 root root 3894356 Mar 10 04:58 /opt/cni/bin/sriov
```

ドキュメントに記載ないが、全ノードにCNIプラグインが必要と思われるため、ビルドしたバイナリをworker nodeにも配置する。

## SR-IOV network device pluginのビルドとデプロイ

イメージはDocker Hubのものを使用するのでビルドはしない。

device pluginをk8sにデプロイする。
[ConfigMap](https://github.com/k8snetworkplumbingwg/sriov-network-device-plugin/blob/v3.3.1/deployments/configMap.yaml)にVFの定義が必要。
今回使用するデバイスのベンダーコード、デバイスドライバ、デバイスコードは以下の通り。

```
worker01:~$ dmesg |grep enp24s0f2 |grep 18:0a.0
[346246.258461] iavf 0000:18:0a.0 enp24s0f2v0: renamed from eth2

（省略）

worker01:~$ lspci -nn | grep 18:0a.0
18:0a.0 Ethernet controller [0200]: Intel Corporation Ethernet Virtual Function 700 Series [8086:154c] (rev 02)
```

今回使用する環境の場合、デフォルトの定義で問題なそうなので、マニフェストを編集せずにConfigMapをapplyする。

```
controlplane:~/sriov-network-device-plugin$ k apply -f deployments/configMap.yaml 
configmap/sriovdp-config created
controlplane:~/sriov-network-device-plugin$ k apply -f deployments/k8s-v1.16/sriovdp-daemonset.yaml 
serviceaccount/sriov-device-plugin created
daemonset.apps/kube-sriov-device-plugin-amd64 created
daemonset.apps/kube-sriov-device-plugin-ppc64le created
daemonset.apps/kube-sriov-device-plugin-arm64 created
controlplane:~/sriov-network-device-plugin$ k -n kube-system get pod -l app=sriovdp -o wide
NAME                                   READY   STATUS    RESTARTS   AGE    IP            NODE           NOMINATED NODE   READINESS GATES
kube-sriov-device-plugin-amd64-2fvtq   1/1     Running   0          2m5s   172.16.90.3   worker02       <none>           <none>
kube-sriov-device-plugin-amd64-fpptr   1/1     Running   0          2m5s   172.16.90.2   worker01       <none>           <none>
kube-sriov-device-plugin-amd64-q99tf   1/1     Running   0          2m5s   172.16.90.1   controlplane   <none>           <none>
```

## CNI meta pluginのインストール

MultusかDANMをインストールする必要がある。
今回はMultusを使用。（Multusは少しだけ知ってるので）
Multusの[Quickstart Installation Guide](https://github.com/intel/multus-cni#quickstart-installation-guide)に従ってインストールする。

* 前提条件
    * k8sクラスタにCNIプラグインがインストール済であること<br>
今回の場合は、Calicoをインストール済

```
controlplane:~$ k apply -f https://raw.githubusercontent.com/intel/multus-cni/v3.6/images/multus-daemonset.yml
customresourcedefinition.apiextensions.k8s.io/network-attachment-definitions.k8s.cni.cncf.io created
clusterrole.rbac.authorization.k8s.io/multus created
clusterrolebinding.rbac.authorization.k8s.io/multus created
serviceaccount/multus created
configmap/multus-cni-config created
daemonset.apps/kube-multus-ds-amd64 created
daemonset.apps/kube-multus-ds-ppc64le created
controlplane:~$ k get pod -l app=multus -A -o wide
NAMESPACE     NAME                         READY   STATUS    RESTARTS   AGE   IP            NODE           NOMINATED NODE   READINESS GATES
kube-system   kube-multus-ds-amd64-9wc5g   1/1     Running   0          60s   172.16.90.2   worker01       <none>           <none>
kube-system   kube-multus-ds-amd64-g4xgv   1/1     Running   0          60s   172.16.90.3   worker02       <none>           <none>
kube-system   kube-multus-ds-amd64-mmwsf   1/1     Running   0          60s   172.16.90.1   controlplane   <none>           <none>
```

各worker nodeでVFが認識されているかの確認。

```
controlplane:~$ kubectl get node worker01 -o jsonpath='{.status.allocatable}' |jq -r '."intel.com/intel_sriov_netdevice"'
3
controlplane:~$ kubectl get node worker02 -o jsonpath='{.status.allocatable}' |jq -r '."intel.com/intel_sriov_netdevice"'
3
```

SR-IOV Network CRDの作成

[テンプレート](https://github.com/k8snetworkplumbingwg/sriov-network-device-plugin/blob/v3.3.1/deployments/sriov-crd.yaml)をベースにサブネット、ゲートウェイを環境に合わせて変更する。

```yaml
kind: NetworkAttachmentDefinition
apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
  name: sriov-net1
  annotations:
    k8s.v1.cni.cncf.io/resourceName: intel.com/intel_sriov_netdevice
spec:
  config: '{
  "type": "sriov",
  "cniVersion": "0.3.1",
  "name": "sriov-network",
  "ipam": {
    "type": "host-local",
    "subnet": "172.16.91.0/24",
    "routes": [{
      "dst": "0.0.0.0/0"
    }],
    "gateway": "172.16.91.1"
  }
}'
```

```
controlplane:~$ k apply -f sriov-network-device-plugin/deployments/sriov-crd.yaml 
networkattachmentdefinition.k8s.cni.cncf.io/sriov-net1 created
controlplane:~$ k get network-attachment-definitions.k8s.cni.cncf.io 
NAME         AGE
sriov-net1   2m6s
```

# SR-IOVワークロードのデプロイ

以下のマニフェストを使用する。

* https://github.com/k8snetworkplumbingwg/sriov-network-device-plugin/blob/v3.3.1/deployments/pod-tc1.yaml

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: testpod1
  annotations:
    k8s.v1.cni.cncf.io/networks: sriov-net1
spec:
  containers:
  - name: appcntr1 
    image: centos/tools 
    imagePullPolicy: IfNotPresent
    command: [ "/bin/bash", "-c", "--" ]
    args: [ "while true; do sleep 300000; done;" ]
    resources:
      requests:
        intel.com/intel_sriov_netdevice: '1'
      limits:
        intel.com/intel_sriov_netdevice: '1'
```

`metadata.annotation`に`k8s.v1.cni.cncf.io/networks: sriov-net1`、`spec.containers.resources`にpodに割り当てるVFポート数を指定すれば、SR-IOVのNICをもつpodをデプロイできるようである。

上記マニフェストに`spec.nodeName`の定義を追加してworker01にpodをスケジューリングする。

```
controlplane:~$ k apply -f pod-tc1.yaml 
pod/testpod1 created
controlplane:~$ k get pod -o wide
NAME       READY   STATUS    RESTARTS   AGE   IP             NODE       NOMINATED NODE   READINESS GATES
testpod1   1/1     Running   0          5s    192.168.5.27   worker01   <none>           <none>
```

とりあえず`running`となった。

つづいてpodのIPを確認してみる。

```
controlplane:~$ k exec -it testpod1 -- ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
3: eth0@if21: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default 
    link/ether 92:40:9e:d4:af:84 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.5.27/32 brd 192.168.5.27 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::9040:9eff:fed4:af84/64 scope link 
       valid_lft forever preferred_lft forever
20: net1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 6e:c6:50:5f:c1:66 brd ff:ff:ff:ff:ff:ff
    inet 172.16.91.24/24 brd 172.16.91.255 scope global net1
       valid_lft forever preferred_lft forever
    inet6 fe80::6cc6:50ff:fe5f:c166/64 scope link 
       valid_lft forever preferred_lft forever
```

NICが2つ付いていることを確認できた。
`192.168.5.27/32`がCallicoのネットワークで、もう一つの`172.16.91.24/24`はSR-IOVのネットワーク。

どのVFが使用されているかを確認する。
PCIデバイスはコンテナの環境変数で確認できる。

```
controlplane:~$ k exec -it testpod1 -- env |grep PCIDEVICE_
PCIDEVICE_INTEL_COM_INTEL_SRIOV_NETDEVICE=0000:18:0a.1
```

コンテナにVFポートが割り当てられていることを確認できた。
次に、podのホストである`worker01`nodeのVFを確認する。

```
worker01:~$ ip a | grep enp24s0f2v
18: enp24s0f2v0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
19: enp24s0f2v2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
```

`enp24s0f2v1`がworker nodeから見えなくなった。
VFがコンテナのnamespaceになったためと思われる。

# SR-IOV NICをもつpodの通信確認

まずは、先程デプロイしたpod`testpod1`からPFに割り振ったIPアドレス（ゲートウェイ）に通信できるか確認

```
controlplane:~$ k exec -it testpod1 -- ping -c 3 172.16.91.1
PING 172.16.91.1 (172.16.91.1) 56(84) bytes of data.
64 bytes from 172.16.91.1: icmp_seq=1 ttl=64 time=0.332 ms
64 bytes from 172.16.91.1: icmp_seq=2 ttl=64 time=0.153 ms
64 bytes from 172.16.91.1: icmp_seq=3 ttl=64 time=0.133 ms

--- 172.16.91.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2034ms
rtt min/avg/max/mdev = 0.133/0.206/0.332/0.089 ms
```

通信できた。
次にpod間通信を確認するため、`testpod1`と同様のpod`testpod2`を`worker02`にスケジューリングする。

```
controlplane:~$ k get pod -o wide
NAME       READY   STATUS    RESTARTS   AGE   IP              NODE       NOMINATED NODE   READINESS GATES
testpod1   1/1     Running   0          10m   192.168.5.27    worker01   <none>           <none>
testpod2   1/1     Running   0          4s    192.168.30.73   worker02   <none>           <none>
controlplane:~$ k exec -it testpod2 -- ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
3: eth0@if31: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default 
    link/ether c2:1e:ef:19:51:b0 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.30.73/32 brd 192.168.30.73 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::c01e:efff:fe19:51b0/64 scope link 
       valid_lft forever preferred_lft forever
22: net1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 8e:6b:a3:5e:91:4d brd ff:ff:ff:ff:ff:ff
    inet 172.16.91.5/24 brd 172.16.91.255 scope global net1
       valid_lft forever preferred_lft forever
    inet6 fe80::8c6b:a3ff:fe5e:914d/64 scope link 
       valid_lft forever preferred_lft forever
```

![cluster_network_diagram](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/350260/61a51397-aa07-6a43-664b-5e171055745f.png)


`testpod1` => `testpod2`へ通信が通るか試してみる。

```
controlplane:~$ k exec -it testpod1 -- ping -c 3 172.16.91.5
PING 172.16.91.5 (172.16.91.5) 56(84) bytes of data.
64 bytes from 172.16.91.5: icmp_seq=1 ttl=64 time=0.336 ms
64 bytes from 172.16.91.5: icmp_seq=2 ttl=64 time=0.179 ms
64 bytes from 172.16.91.5: icmp_seq=3 ttl=64 time=0.121 ms

--- 172.16.91.5 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2036ms
rtt min/avg/max/mdev = 0.121/0.212/0.336/0.090 ms
```

疎通できた。

# ネットワークスループット計測

pod間のデータ転送速度を、calico, SR-IOVネットワークのそれぞれを経由する場合で計測する。

まずは、iperf3で計測される側のpod`iperf3-server`を`worker02`に、計測する側のpod`iperf3-client`をworker01にデプロイする。

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: iperf3-server
  annotations:
    k8s.v1.cni.cncf.io/networks: sriov-net1
spec:
  nodeName: worker02
  containers:
  - name: iperf3-server
    image: networkstatic/iperf3
    imagePullPolicy: IfNotPresent
    command: [ "iperf3", "-s" ]
    resources:
      requests:
        intel.com/intel_sriov_netdevice: '1' 
      limits:
        intel.com/intel_sriov_netdevice: '1'
```

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: iperf3-client
  annotations:
    k8s.v1.cni.cncf.io/networks: sriov-net1
spec:
  nodeName: worker01
  containers:
  - name: iperf3-client
    image: networkstatic/iperf3
    imagePullPolicy: IfNotPresent
    command: [ "sleep", "infinity" ]
    resources:
      requests:
        intel.com/intel_sriov_netdevice: '1' 
      limits:
        intel.com/intel_sriov_netdevice: '1'
```

```
controlplane:~$ k get pod -o wide
NAME            READY   STATUS    RESTARTS   AGE    IP              NODE       NOMINATED NODE   READINESS GATES
iperf3-client   1/1     Running   0          14m    192.168.5.29    worker01   <none>           <none>
iperf3-server   1/1     Running   0          4s     192.168.30.77   worker02   <none>           <none>
testpod1        1/1     Running   0          150m   192.168.5.27    worker01   <none>           <none>
testpod2        1/1     Running   0          140m   192.168.30.73   worker02   <none>           <none>
controlplane:~$ k exec -it iperf3-client -- ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
3: eth0@if23: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default 
    link/ether 8a:f4:d9:75:0d:a6 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.5.29/32 brd 192.168.5.29 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::88f4:d9ff:fe75:da6/64 scope link 
       valid_lft forever preferred_lft forever
19: net1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 96:3d:6c:f7:e4:5a brd ff:ff:ff:ff:ff:ff
    inet 172.16.91.26/24 brd 172.16.91.255 scope global net1
       valid_lft forever preferred_lft forever
    inet6 fe80::943d:6cff:fef7:e45a/64 scope link 
       valid_lft forever preferred_lft forever
controlplane:~$ k exec -it iperf3-server -- ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
3: eth0@if35: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default 
    link/ether 6e:57:bf:f4:d5:b3 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.30.77/32 brd 192.168.30.77 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::6c57:bfff:fef4:d5b3/64 scope link 
       valid_lft forever preferred_lft forever
21: net1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether ca:a0:3b:3f:68:99 brd ff:ff:ff:ff:ff:ff
    inet 172.16.91.9/24 brd 172.16.91.255 scope global net1
       valid_lft forever preferred_lft forever
    inet6 fe80::c8a0:3bff:fe3f:6899/64 scope link 
       valid_lft forever preferred_lft forever
```

calicoネットワーク経由の計測

```
controlplane:~$ k exec -it iperf3-client -- iperf3 -c 192.168.30.77
Connecting to host 192.168.30.77, port 5201
[  5] local 192.168.5.29 port 48086 connected to 192.168.30.77 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec   527 MBytes  4.42 Gbits/sec  2236    527 KBytes       
[  5]   1.00-2.00   sec   534 MBytes  4.47 Gbits/sec  2458    601 KBytes       
[  5]   2.00-3.00   sec   545 MBytes  4.58 Gbits/sec  1234    633 KBytes       
[  5]   3.00-4.00   sec   544 MBytes  4.56 Gbits/sec  1035    696 KBytes       
[  5]   4.00-5.00   sec   535 MBytes  4.49 Gbits/sec  1358    538 KBytes       
[  5]   5.00-6.00   sec   562 MBytes  4.72 Gbits/sec  1530    717 KBytes       
[  5]   6.00-7.00   sec   548 MBytes  4.59 Gbits/sec  1893    496 KBytes       
[  5]   7.00-8.00   sec   579 MBytes  4.86 Gbits/sec  978    640 KBytes       
[  5]   8.00-9.00   sec   592 MBytes  4.97 Gbits/sec  1655    564 KBytes       
[  5]   9.00-10.00  sec   509 MBytes  4.27 Gbits/sec  1203    601 KBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  5.35 GBytes  4.59 Gbits/sec  15580             sender
[  5]   0.00-10.03  sec  5.34 GBytes  4.58 Gbits/sec                  receiver

iperf Done.
```

SR-IOVネットワーク経由の計測

```
controlplane:~$ k exec -it iperf3-client -- iperf3 -c 172.16.91.9
Connecting to host 172.16.91.9, port 5201
[  5] local 172.16.91.26 port 54806 connected to 172.16.91.9 port 5201
[ ID] Interval           Transfer     Bitrate         Retr  Cwnd
[  5]   0.00-1.00   sec  1.10 GBytes  9.43 Gbits/sec    0   1.35 MBytes       
[  5]   1.00-2.00   sec  1.10 GBytes  9.41 Gbits/sec    0   1.42 MBytes       
[  5]   2.00-3.00   sec  1.09 GBytes  9.41 Gbits/sec    0   1.58 MBytes       
[  5]   3.00-4.00   sec  1.10 GBytes  9.42 Gbits/sec    0   1.58 MBytes       
[  5]   4.00-5.00   sec  1.10 GBytes  9.42 Gbits/sec    0   1.58 MBytes       
[  5]   5.00-6.00   sec  1.10 GBytes  9.41 Gbits/sec    0   1.58 MBytes       
[  5]   6.00-7.00   sec  1.10 GBytes  9.41 Gbits/sec    4    964 KBytes       
[  5]   7.00-8.00   sec  1.09 GBytes  9.41 Gbits/sec    9    959 KBytes       
[  5]   8.00-9.00   sec  1.10 GBytes  9.42 Gbits/sec    8    938 KBytes       
[  5]   9.00-10.00  sec  1.10 GBytes  9.42 Gbits/sec    4   1.21 MBytes       
- - - - - - - - - - - - - - - - - - - - - - - - -
[ ID] Interval           Transfer     Bitrate         Retr
[  5]   0.00-10.00  sec  11.0 GBytes  9.42 Gbits/sec   25             sender
[  5]   0.00-10.00  sec  11.0 GBytes  9.41 Gbits/sec                  receiver

iperf Done.
```

SR-IOVの方がCalicoよりも2倍程度の転送速度となった。

# Serviceを使ってSR-IOVネットワーク経由で通信する方法の検証

SR-IOVネットワークを使用することで高スループットが得られた。
しかし、SR-IOVのネットワークで通信するためにはIPアドレスを指定する必要があり、serviceを使えなそう。
k8sではpodはつくって消してを繰り返すのが前提であり、その度にpodのIPアドレスが変わるため、serviceが使えないとなるとなかなか不便そう。
[Headless Service](https://kubernetes.io/ja/docs/concepts/services-networking/service/#headless-service)を使用すればkube-proxyを介さずpodに通信できるので、SR-IOVネットワーク経由で通信できるかもしれないので試してみる。

まずは、SR-IOVのNICをもつpodをデプロイする。

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: hello-world
  annotations:
    k8s.v1.cni.cncf.io/networks: sriov-net1
  labels:
    app: hello-world
spec:
  containers:
  - name: hello-worlde  
    image: gcr.io/google-samples/node-hello:1.0
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 8080
      protocol: TCP
    resources:
      requests:
        intel.com/intel_sriov_netdevice: '1'
      limits:
        intel.com/intel_sriov_netdevice: '1'
```

```
controlplane:~$ k apply -f pod-helloworld.yaml 
pod/hello-world created
controlplane:~$ k get pod -l app=hello-world
NAME          READY   STATUS    RESTARTS   AGE
hello-world   1/1     Running   0          78s
controlplane:~$ k exec -it hello-world -- ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
3: eth0@if36: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default 
    link/ether a2:16:bd:b6:2f:8f brd ff:ff:ff:ff:ff:ff
    inet 192.168.30.78/32 brd 192.168.30.78 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::a016:bdff:feb6:2f8f/64 scope link 
       valid_lft forever preferred_lft forever
20: net1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 8e:d4:33:5f:2e:06 brd ff:ff:ff:ff:ff:ff
    inet 172.16.91.10/24 brd 172.16.91.255 scope global net1
       valid_lft forever preferred_lft forever
    inet6 fe80::8cd4:33ff:fe5f:2e06/64 scope link 
       valid_lft forever preferred_lft forever
```

つづいて、headless serviceを作成する。

```
controlplane:~$ k apply -f service-helloworld.yaml 
service/hello-world created
controlplane:~$ k get endpoints hello-world 
NAME          ENDPOINTS            AGE
hello-world   192.168.30.78:8080   2m5s
```

Calicoネットワークのendpointしか作成されなかった。
headless serviceを使っても、SR-IOVネットワーク経由の通信はできなかった。
Multusでは現状、デフォルトネットワークしかserviceに対応していないようである。

https://github.com/k8snetworkplumbingwg/multus-cni/issues/256

# 所感

k8sでSR-IOVを使うことにより高スループットを得られるが、反面、以下のデメリットがあることがわかった。

* Worker nodeのSR-IOV用のNIC（PV）でbondを組めないため、ネットワークを冗長化できない
* k8sのserviceはデフォルトネットワークにしか対応していないため、SR-IOVで通信するにはserviceではなくIPアドレスを指定する必要がある

なのでk8sでSR-IOVを使うには、割り切りが必要ということがわかった。

# 参考

* https://xtech.nikkei.com/it/article/Keyword/20100115/343227/
* https://github.com/k8snetworkplumbingwg/sriov-network-device-plugin
* https://docs.vmware.com/jp/VMware-vSphere/7.0/com.vmware.vsphere.networking.doc/GUID-CC021803-30EA-444D-BCBE-618E0D836B9F.html
* https://golang.org/doc/install
* https://github.com/intel/multus-cni#quickstart-installation-guide
* https://metonymical.hatenablog.com/entry/2019/12/15/121251
* https://www.na3.jp/entry/20140121/p1
* https://gihyo.jp/admin/serial/01/ubuntu-recipe/0539?page=2
* https://hub.docker.com/r/networkstatic/iperf3
* https://kubernetes.io/ja/docs/concepts/services-networking/service/#headless-service
* https://github.com/k8snetworkplumbingwg/multus-cni/issues/256