Metonymical Deflection

ゆるく日々のコト・たまにITインフラ

CentOS8 KubernetesのSR-IOV設定方法

Kubernetes(以下、k8s)のSR-IOV設定方法を記載します。
PodとVMI(Virtual Machine Instance)の両方でそれぞれSR-IOVを設定していきます。

1.構成

1-1.環境
1.Master
VMWare              : VMware(R) Workstation 15 Pro 15.5.1 build-15018445 

2.Worker
筐体                             : ProLiant DL360p Gen8
System ROM                       : P71 05/24/2019
NIC                              : Intel X520-SR2(82599ES), X540-AT2

3.Master&Worker共通
OS                               : CentOS8.0(1905)
Kernel                           : 4.18.0-80.el8.x86_64
Installed Environment Groups     : 
  @^graphical-server-environment
  @container-management
  @development
  @virtualization-client
  @virtualization-hypervisor
  @virtualization-tools 

Kubernetes                       : 1.17.0
Docker                           : 19.03.5
flannel                          : latest
KubeVirt                         : 0.24.0
Multus                           : latest
sriov-cni                        : latest
sriov-network-device-plugin      : latest
1-2.全体の構成

f:id:metonymical:20191215102800p:plain

1-3.全体の流れ
  1. 事前準備
  2. k8s Cluster & flannelの構築
  3. KubeVirtの構築
  4. SR-IOV関連のビルド
  5. SR-IOV関連の設定
  6. VMIとPodのデプロイ

1~3までは比較的多くのドキュメントが存在しますので、重要な箇所以外はある程度割愛します。*1

2.事前準備

2-1.諸々の準備
SELinuxの無効化 : Master&Worker*2
sed -i -e "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/selinux/config

FW無効化 : Master&Worker
systemctl stop firewalld
systemctl disable firewalld

パススルー設定 : Master&Worker*3
sed -i -e "s/#options kvm_intel nested=1/options kvm_intel nested=1/g" /etc/modprobe.d/kvm.conf

swap無効化 : Master&Worker
vi /etc/fstab

#/dev/mapper/cl-swap     swap                    swap    defaults        0 0

hostsファイル設定 : Master&Worker*4
vi /etc/hosts

127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4 c80g105.md.jp c80g105
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

192.168.11.105 c80g105 c80g105.md.jp
192.168.11.106 c80g106 c80g106.md.jp
2-2.HugePageとIOMMUの有効化 : Worker
sed -i -e "/GRUB_CMDLINE_LINUX=/s/\"$/ default_hugepagesz=1G hugepagesz=1G hugepages=16\"/g" /etc/default/grub
sed -i -e "/GRUB_CMDLINE_LINUX=/s/\"$/ intel_iommu=on iommu=pt pci=realloc\"/g" /etc/default/grub
grub2-mkconfig -o /etc/grub2.cfg

vi /etc/fstab

最終行に以下を追記
nodev  /mnt/huge_1GB hugetlbfs pagesize=1GB    0 0

hugepageの有効化
iommuの有効化
grubに設定反映
マウント

2-3.SR-IOVのVF設定 : Worker
vi /etc/rc.local

echo 8 > /sys/class/net/ens1f0/device/sriov_numvfs
echo 8 > /sys/class/net/ens1f1/device/sriov_numvfs
echo 8 > /sys/class/net/ens2f0/device/sriov_numvfs
echo 8 > /sys/class/net/ens2f1/device/sriov_numvfs
sleep 1
exit 0

chmod +x /etc/rc.d/rc.local

2枚のNICの各ポートにVFをそれぞれ8つづつ設定
実行権限付与

<補足>
過去記事では、Blacklistに追加していますが、今回は不要です。

2-4.vfio-pciの設定 : Worker
vi /etc/modprobe.d/vfio_pci.conf

options vfio_pci ids=8086:10ed

echo "vfio-pci" > /etc/modules-load.d/vfio-pci.conf
echo "options vfio_iommu_type1 allow_unsafe_interrupts=1" > /etc/modules-load.d/iommu.conf

vfio-pciを有効化するデバイス(NIC)のDeviceID設定
vfio-pciの永続化設定
vfio-pciのiommu利用の永続化

<補足(重要)>
DeviceIDはそれぞれ以下の通りとなります。

8086:10ed X520 VMI用 vfio-pci
8086:1515 X540 Pod用 ixgbevf

今回は2枚のNICでそれぞれVMI用とPod用として分けているため、vfio-pciを有効化するのは、X520のみとしています。*5
というのも、以下2点を確認したからです。*6

  • VMIでixgbevfを使用すると、kubectl get vmi上ではscheduledのまま固まります。flannelのIPはアサインされますがPingは通りません。*7
  • Podでvfio-pciNICを掴むことができませんでした。ip link showなどで確認しても追加されていません。

ここまでの準備が整ったらMaster&Workerを一旦再起動してください。

3.k8s Cluster & flannelの構築

3-1.Dockerのインストール : Master&Worker
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo && \
dnf -y install https://download.docker.com/linux/centos/7/x86_64/stable/Packages/containerd.io-1.2.10-3.2.el7.x86_64.rpm && \
dnf -y install docker-ce && \
systemctl start docker && \
systemctl enable docker
3-3.k8sのインストール : Master&Worker
Master
dnf -y install kubeadm kubectl

Worker
dnf -y install kubeadm

Master&Worker
systemctl start kubelet.service && \
systemctl enable kubelet.service
3-4.Dockerの設定 : Master&Worker
cat > /etc/docker/daemon.json <<EOF
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2",
  "storage-opts": [
    "overlay2.override_kernel_check=true"
  ]
}
EOF

mkdir -p /etc/systemd/system/docker.service.d

systemctl daemon-reload && \
systemctl restart docker
3-5.k8sClusterの構築 : Master
kubeadm init --apiserver-advertise-address=192.168.11.105 --pod-network-cidr=10.244.0.0/16

<出力例>
最後に以下の出力が表示されますので、赤文字部分をコピーしておいてください。
WorkerがMasterへJoinする際に使用します。

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.11.105:6443 --token 0gfh5j.vgu76alcycb2tc2e \
    --discovery-token-ca-cert-hash sha256:edcb1a3856838586a6ea7c99200daafa4fbb639e822838f4df81ce09d2faaac3 
3-6.k8s Cluster構築後の設定 : Master
コンフィグファイルのコピー
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

コマンド補完設定
echo "source <(kubectl completion bash)" >> ~/.bashrc
3-7.flannelのインストール : Master
mkdir /root/tmp && \
cd /root/tmp/ && \
wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml && \
kubectl apply -f kube-flannel.yml

kubectl get nodes

<出力例>
以下のようにReadyとなるまで待ってください。

[root@c80g105 ~]# kubectl get nodes
NAME            STATUS   ROLES    AGE   VERSION
c80g105.md.jp   Ready    master   48m   v1.17.0
3-8.WorkerのJoin : Worker
kubeadm join 192.168.11.105:6443 --token 0gfh5j.vgu76alcycb2tc2e \
    --discovery-token-ca-cert-hash sha256:edcb1a3856838586a6ea7c99200daafa4fbb639e822838f4df81ce09d2faaac3

<出力例>
以下のようにWorkerもReadyとなるまで待ってください。

[root@c80g105 ~]# kubectl get nodes
NAME            STATUS   ROLES    AGE   VERSION
c80g105.md.jp   Ready    master   48m   v1.17.0
c80g106.md.jp   Ready    <none>   19s   v1.17.0

ここまでの準備が整ったらMaster&Workerをもう一度再起動してください。

4.KubeVirtの構築

4-1.KubeVirtインストール : Master
export RELEASE=v0.24.0
kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/${RELEASE}/kubevirt-operator.yaml
kubectl apply -f https://github.com/kubevirt/kubevirt/releases/download/${RELEASE}/kubevirt-cr.yaml

<出力例>
Podの状態が以下のようにすべてRunningになるまでには、2分程度掛かります。

kubectl get pods -n kubevirt

[root@c80g116 ~]# kubectl -n kubevirt get pods
NAME                               READY   STATUS    RESTARTS   AGE
virt-api-64f59f44dd-prkj4          1/1     Running   3          70m
virt-api-64f59f44dd-w9984          1/1     Running   3          71m
virt-controller-79cc7fb59d-6bz7d   1/1     Running   3          70m
virt-controller-79cc7fb59d-wt8g4   1/1     Running   3          70m
virt-handler-ljfqg                 1/1     Running   5          88m
virt-operator-54db765c7d-4xkq5     1/1     Running   3          70m
virt-operator-54db765c7d-5bgw4     1/1     Running   3          64m

全てRunningになったら、以下のコマンドを実行

kubectl -n kubevirt wait kv kubevirt --for condition=Available

<出力例>
以下の出力が表示されればOKです。

[root@c80g105 ~]# kubectl -n kubevirt wait kv kubevirt --for condition=Available
kubevirt.kubevirt.io/kubevirt condition met
4-2.virtctlのインストール : Master : オプション

必須ではありませんが、vmiを操作する際に便利です。*8

export RELEASE=v0.24.0
curl -L -o virtctl \
    https://github.com/kubevirt/kubevirt/releases/download/${RELEASE}/virtctl-${RELEASE}-linux-amd64

chmod +x virtctl
chown root:root virtctl
mv virtctl /usr/bin
4-3.virtctlのインストール : Master : オプション

必須ではありませんが、virtctlはKrewを使ってもインストールできます。*9

export RELEASE=v0.3.3

set -x; cd "$(mktemp -d)" &&
curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/download/v0.3.3/krew.{tar.gz,yaml}" &&
tar zxvf krew.tar.gz &&
KREW=./krew-"$(uname | tr '[:upper:]' '[:lower:]')_amd64" &&
"$KREW" install --manifest=krew.yaml --archive=krew.tar.gz &&
"$KREW" update

export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"

kubectl krew install virt

echo 'PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"' >> ~/.bashrc

5.SR-IOV関連のビルド

5-1.Golangのインストール : Master
dnf -y install epel-release golang
5-2.SR-IOV CNIのビルド : Master
git clone https://github.com/intel/sriov-cni.git && \
cd sriov-cni && \
make && \
cp build/sriov /opt/cni/bin

SRIOV CNIの実行ファイルをWorkerへコピー
scp /opt/cni/bin/sriov root@192.168.11.106:/opt/cni/bin/
5-3.SR-IOV Network Deviceのビルド : Master
cd ~/ && \
git clone https://github.com/intel/sriov-network-device-plugin.git && \
cd sriov-network-device-plugin && \
make

docker build -t nfvpe/sriov-device-plugin -f ./images/Dockerfile .

<出力例>

docker images

[root@c80g105 sriov-network-device-plugin]# docker images
REPOSITORY                           TAG                 IMAGE ID            CREATED             SIZE
nfvpe/sriov-device-plugin            latest              a45310aae832        13 seconds ago      25.3MB
<none>                               <none>              b65d412dee59        19 seconds ago      905MB
k8s.gcr.io/kube-proxy                v1.17.0             7d54289267dc        6 days ago          116MB
k8s.gcr.io/kube-apiserver            v1.17.0             0cae8d5cc64c        6 days ago          171MB
k8s.gcr.io/kube-controller-manager   v1.17.0             5eb3b7486872        6 days ago          161MB
k8s.gcr.io/kube-scheduler            v1.17.0             78c190f736b1        6 days ago          94.4MB
golang                               alpine              69cf534c966a        8 days ago          359MB
k8s.gcr.io/coredns                   1.6.5               70f311871ae1        5 weeks ago         41.6MB
k8s.gcr.io/etcd                      3.4.3-0             303ce5db0e90        7 weeks ago         288MB
alpine                               latest              965ea09ff2eb        7 weeks ago         5.55MB
quay.io/coreos/flannel               v0.11.0-amd64       ff281650a721        10 months ago       52.6MB
k8s.gcr.io/pause                     3.1                 da86e6ba6ca1        24 months ago       742kB
5-4.SR-IOV Network Deviceのビルド : Worker
cd ~/ && \
git clone https://github.com/intel/sriov-network-device-plugin.git && \
cd sriov-network-device-plugin && \
docker build -t nfvpe/sriov-device-plugin -f ./images/Dockerfile .

<出力例>

docker images

[root@c80g106 sriov-network-device-plugin]# docker images
REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE
nfvpe/sriov-device-plugin   latest              8c508ab37c1c        28 seconds ago      25.3MB
<none>                      <none>              571b3587b90d        34 seconds ago      855MB
k8s.gcr.io/kube-proxy       v1.17.0             7d54289267dc        6 days ago          116MB
golang                      alpine              69cf534c966a        8 days ago          359MB
alpine                      latest              965ea09ff2eb        7 weeks ago         5.55MB
quay.io/coreos/flannel      v0.11.0-amd64       ff281650a721        10 months ago       52.6MB
k8s.gcr.io/pause            3.1                 da86e6ba6ca1        24 months ago       742kB
kubevirt/virt-api           <none>              1a3697b92f09        49 years ago        293MB
kubevirt/virt-controller    <none>              ab9c8cce9c60        49 years ago        291MB
kubevirt/virt-handler       <none>              c5919b95ca31        49 years ago        313MB
kubevirt/virt-operator      <none>              45c6d543afb1        49 years ago        326MB

<補足>
この後の工程で、sriovdp-daemonset.yaml をデプロイするのですが、元から以下のように書かれているため、WorkerにおいてもDockerイメージをビルドしています。

      containers:
      - name: kube-sriovdp
        image: nfvpe/sriov-device-plugin
        imagePullPolicy: Never

6.SR-IOV関連の設定

以下のQuick Startに記載された順番に従い設定を行っていきます。
GitHub - k8snetworkplumbingwg/sriov-network-device-plugin: SRIOV network device plugin for Kubernetes

流れは以下の通りです。*10

  1. configMapの設定&デプロイ
  2. sriovdp-daemonsetのデプロイ
  3. Master:Multusのインストール
  4. sriov-crdの設定&デプロイ
  5. リソースの確認
6-1.ConfigMapの設定 : Master

ポイントは

"resourceName": "intel_sriov_netdevice1",
"resourceName": "intel_sriov_netdevice2",

として別けて記載している点です。
CRDやVMI&Podのyamlファイルにも一連の流れとして記載していくため、に別けて書いておきます。

既存ファイルを以下のように書き換えてください。

vi /root/sriov-network-device-plugin/deployments/configMap.yaml

[root@c80g105 ~]# vi /root/sriov-network-device-plugin/deployments/configMap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: sriovdp-config
  namespace: kube-system
data:
  config.json: |
    {
        "resourceList": [{
apiVersion: v1
kind: ConfigMap
metadata:
  name: sriovdp-config
  namespace: kube-system
data:
  config.json: |
    {
        "resourceList": [
            {
                "resourceName": "intel_sriov_netdevice1",
                "selectors": {
                    "vendors": ["8086"],
                    "devices": ["154c", "10ed"],
                    "drivers": ["i40evf", "vfio-pci"]
                }
            },
            {
                "resourceName": "intel_sriov_netdevice2",
                "selectors": {
                    "vendors": ["8086"],
                    "devices": ["154c", "1515"],
                    "drivers": ["i40evf", "ixgbevf"]
                }
            },
            {
                "resourceName": "intel_sriov_dpdk",
                "selectors": {
                    "vendors": ["8086"],
                    "devices": ["154c", "10ed"],
                    "drivers": ["vfio-pci"],
                    "pfNames": ["enp0s0f0","enp2s2f1"]
                }
            },
            {
                "resourceName": "mlnx_sriov_rdma",
                "isRdma": true,
                "selectors": {
                    "vendors": ["15b3"],
                    "devices": ["1018"],
                    "drivers": ["mlx5_ib"]
                }
            }
        ]
    }

kubectl create -f /root/sriov-network-device-plugin/deployments/configMap.yaml

<出力例>

kubectl get configmaps -n kube-system

[root@c80g105 ~]# kubectl get configmaps -n kube-system
NAME                                 DATA   AGE
coredns                              1      4h32m
extension-apiserver-authentication   6      4h33m
kube-flannel-cfg                     2      4h32m
kube-proxy                           2      4h32m
kubeadm-config                       2      4h32m
kubelet-config-1.17                  1      4h32m
multus-cni-config                    1      3h16m
sriovdp-config                       1      3h16m

<補足>
先ほど記載した表の通りに別けて書いてあることが確認できると思います。

8086:10ed X520 VMI用 vfio-pci
8086:1515 X540 Pod用 ixgbevf
6-2.SRIOVDP DaemonSetのデプロイ : Master

特に修正する箇所はありませんので、yamlファイルの中身を確認せず、以下のコマンドを入力しても構いません。

kubectl create -f /root/sriov-network-device-plugin/deployments/k8s-v1.16/sriovdp-daemonset.yaml
vi /root/sriov-network-device-plugin/deployments/k8s-v1.16/sriovdp-daemonset.yaml

[root@c80g105 ~]# vi /root/sriov-network-device-plugin/deployments/k8s-v1.16/sriovdp-daemonset.yaml

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: sriov-device-plugin
  namespace: kube-system

---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: kube-sriov-device-plugin-amd64
  namespace: kube-system
  labels:
    tier: node
    app: sriovdp
spec:
  selector:
    matchLabels:
      name: sriov-device-plugin
  template:
    metadata:
      labels:
        name: sriov-device-plugin
        tier: node
        app: sriovdp
    spec:
      hostNetwork: true
      hostPID: true
      nodeSelector:
        beta.kubernetes.io/arch: amd64
      tolerations:
      - key: node-role.kubernetes.io/master
        operator: Exists
        effect: NoSchedule
      serviceAccountName: sriov-device-plugin
      containers:
      - name: kube-sriovdp
        image: nfvpe/sriov-device-plugin
        imagePullPolicy: Never
        args:
        - --log-dir=sriovdp
        - --log-level=10
        securityContext:
          privileged: true
        volumeMounts:
        - name: devicesock
          mountPath: /var/lib/kubelet/
          readOnly: false
        - name: log
          mountPath: /var/log
        - name: config-volume
          mountPath: /etc/pcidp
      volumes:
        - name: devicesock
          hostPath:
            path: /var/lib/kubelet/
        - name: log
          hostPath:
            path: /var/log
        - name: config-volume
          configMap:
            name: sriovdp-config
            items:
            - key: config.json
              path: config.json

kubectl create -f /root/sriov-network-device-plugin/deployments/k8s-v1.16/sriovdp-daemonset.yaml

<出力例>*11

kubectl get pods -n kube-system

[root@c80g105 ~]# kubectl get pods -n kube-system
NAME                                    READY   STATUS    RESTARTS   AGE
coredns-6955765f44-cn2xp                1/1     Running   1          4h35m
coredns-6955765f44-z9dj6                1/1     Running   1          4h35m
etcd-c80g105.md.jp                      1/1     Running   1          4h35m
kube-apiserver-c80g105.md.jp            1/1     Running   1          4h35m
kube-controller-manager-c80g105.md.jp   1/1     Running   1          4h35m
kube-flannel-ds-amd64-7ltvr             1/1     Running   1          4h35m
kube-flannel-ds-amd64-rfr4f             1/1     Running   1          4h35m
kube-multus-ds-amd64-475wf              1/1     Running   0          3h19m
kube-multus-ds-amd64-9zz5d              1/1     Running   0          3h19m
kube-proxy-dmqvr                        1/1     Running   1          4h35m
kube-proxy-pwmx2                        1/1     Running   0          4h35m
kube-scheduler-c80g105.md.jp            1/1     Running   1          4h35m
kube-sriov-device-plugin-amd64-c2szn    1/1     Running   0          3h19m
kube-sriov-device-plugin-amd64-ttwj5    1/1     Running   0          3h19m
6-3.Multusのインストール : Master

ここも特に設定変更などは不要です。

cd /root && \
git clone https://github.com/intel/multus-cni.git && \
cd multus-cni && \
cat ./images/multus-daemonset.yml | kubectl apply -f -

<出力例>
デプロイ後、以下のような出力が表示されればOKです。

kubectl get pods --all-namespaces | grep -i multus

[root@c80g105 multus-cni]# kubectl get pods --all-namespaces | grep -i multus
kube-system   kube-multus-ds-amd64-7kc44              1/1     Running   0          51s
kube-system   kube-multus-ds-amd64-8ds8r              1/1     Running   0          51s
6-4.SRIOV CRDの設定 : Master
8086:10ed X520 VMI用 vfio-pci

上記VMI用のCRD(Custom Resource Definitions)の設定を行っていきます。
ポイントはannotationsのresourceNameがconfigMap.yamlと一致している点です。
加えて、この後に設定するVMI用yamlにsriov-net1を記載します。

vi /root/sriov-network-device-plugin/deployments/sriov-crd1.yaml

[root@c80g105 ~]# vi /root/sriov-network-device-plugin/deployments/sriov-crd1.yaml

apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
  name: sriov-net1
  annotations:
    k8s.v1.cni.cncf.io/resourceName: intel.com/intel_sriov_netdevice1
spec:
  config: '{
  "type": "sriov",
  "cniVersion": "0.3.1",
  "name": "sriov300_1",
  "vlan": 300,
  "ipam": {
    "type": "host-local",
    "subnet": "192.168.30.0/24",
    "routes": [{
      "dst": "0.0.0.0/0"
    }],
    "gateway": "192.168.30.254"
  }
}'

kubectl create -f /root/sriov-network-device-plugin/deployments/sriov-crd1.yaml


続いて、Pod用のCRD設定を行っていきます。

8086:1515 X540 Pod用 ixgbevf
vi /root/sriov-network-device-plugin/deployments/sriov-crd2.yaml

[root@c80g105 ~]# vi /root/sriov-network-device-plugin/deployments/sriov-crd2.yaml

apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
  name: sriov-net2
  annotations:
    k8s.v1.cni.cncf.io/resourceName: intel.com/intel_sriov_netdevice2
spec:
  config: '{
  "type": "sriov",
  "cniVersion": "0.3.1",
  "name": "sriov300_2",
  "vlan": 300,
  "ipam": {
    "type": "host-local",
    "subnet": "192.168.30.0/24",
    "routes": [{
      "dst": "0.0.0.0/0"
    }],
    "gateway": "192.168.30.254"
  }
}'

kubectl create -f /root/sriov-network-device-plugin/deployments/sriov-crd2.yaml

<出力例>
以下のように表示されればOKです。

[root@c80g105 ~]# kubectl get network-attachment-definitions
NAME         AGE
sriov-net1  3h29m
sriov-net2  3h29m
6-5.リソースの確認 : Master

SR-IOVのVFがWorkerのリソースとしてk8sに認識されていることを確認します。

dnf -y install jq
kubectl get node c80g106.md.jp -o json | jq '.status.allocatable'

<出力例>
以下のような出力が表示されればOKです。

[root@c80g105 ~]# kubectl get node c80g106.md.jp -o json | jq '.status.allocatable'
{
  "cpu": "40",
  "devices.kubevirt.io/kvm": "110",
  "devices.kubevirt.io/tun": "110",
  "devices.kubevirt.io/vhost-net": "110",
  "ephemeral-storage": "400024062519",
  "hugepages-1Gi": "16Gi",
  "intel.com/intel_sriov_dpdk": "0",
  "intel.com/intel_sriov_netdevice": "0",
  "intel.com/intel_sriov_netdevice1": "16",
  "intel.com/intel_sriov_netdevice2": "16",
  "intel.com/mlnx_sriov_rdma": "0",
  "memory": "313085984Ki",
  "pods": "110"
}

7.VMIとPodのデプロイ

VMI用として事前にqcow2イメージファイルを作成しておきます。*12
以下の過去記事などを参照の上、適当な仮想マシンを作成してください。
CentOS7 kickstartによるインストール - Metonymical Deflection
CentOS8 kickstartによるインストール - Metonymical Deflection

ここでは、CentOS7.7をインストールしたqcow2ファイルが、/root直下に既に存在する前提として話を進めます。

7-1.VMI用のイメージ登録 : Worker
イメージファイルのコピー
mkdir /root/docker
cd /root/docker
cp /root/c771.qcow2 ./

ビルド用のDockerfile作成
cat << END > Dockerfile
FROM scratch
ADD c771.qcow2 /disk/
END

イメージのビルド
docker build -t vmidisks/centos:7.7 .

<出力例>

[root@c80g106 docker]# docker images
REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE
vmidisks/centos             7.7                 f5a39b56e448        21 seconds ago      1.64GB
nfvpe/sriov-device-plugin   latest              8c508ab37c1c        6 minutes ago       25.3MB
<none>                      <none>              571b3587b90d        7 minutes ago       855MB
k8s.gcr.io/kube-proxy       v1.17.0             7d54289267dc        6 days ago          116MB
golang                      alpine              69cf534c966a        8 days ago          359MB
nfvpe/multus                v3.4                7cf8e2d1b733        10 days ago         312MB
alpine                      latest              965ea09ff2eb        7 weeks ago         5.55MB
quay.io/coreos/flannel      v0.11.0-amd64       ff281650a721        10 months ago       52.6MB
k8s.gcr.io/pause            3.1                 da86e6ba6ca1        24 months ago       742kB
kubevirt/virt-handler       <none>              c5919b95ca31        49 years ago        313MB
kubevirt/virt-controller    <none>              ab9c8cce9c60        49 years ago        291MB
kubevirt/virt-api           <none>              1a3697b92f09        49 years ago        293MB
kubevirt/virt-operator      <none>              45c6d543afb1        49 years ago        326MB
7-2.VMIのデプロイ : Master
vi /root/sriov-network-device-plugin/deployments/c771.yaml

[root@c80g105 deployments]# vi /root/sriov-network-device-plugin/deployments/c771.yaml

apiVersion: kubevirt.io/v1alpha3
kind: VirtualMachineInstance
metadata:
  name: c771
spec:
  domain:
    resources:
      requests:
        memory: "2048Mi"
        cpu: "1"
    devices:
      disks:
      - name: containerdisk
        disk:
          bus: virtio
      interfaces:
      - name: pod
        bridge: {}
      - name: net1
        sriov: {}
      - name: net2
        sriov: {}
  volumes:
  - name: containerdisk
    containerDisk:
      image: vmidisks/centos:7.7
      imagePullPolicy: Never
  networks:
  - name: pod
    pod: {}
  - name: net1
    multus:
      networkName: sriov-net1
  - name: net2
    multus:
      networkName: sriov-net1


kubectl create-f /root/sriov-network-device-plugin/deployments/c771.yaml

<出力例>

kubectl get pods
kubectl get vmi

[root@c80g105 ~]# kubectl get pods
kubectl get vmi
NAME                       READY   STATUS    RESTARTS   AGE
virt-launcher-c771-5kdr2   2/2     Running   0          3h29m

[root@c80g105 ~]# kubectl get vmi
NAME   AGE     PHASE     IP               NODENAME
c771   3h29m   Running   10.244.1.14/24   c80g106.md.jp

<補足>
ポイントを5点ほど。

  1. multus: > networkName: に先ほど作成したCRDの名前sriov-net1を指定している点です。これにより、明示的にX520を使用する設定*13となっています。
  2. spec: > domain: > resources: には、requests:のみを記載している点です。ここでlimits: を追記すると起動後に高確率でOOM Killerが発動しVMIが瞬殺されます。*14
  3. kubectl get podsにて、READYが2/2になっています。これはVMIに加えて、virt-launcherが起動しているためです。
  4. kubectl get vmiにて、IPが10.244.1.14/24*15となっています。IPアドレス自体「10.244.1.14」は、PHASEがRunningになると、すぐに表示されます。しかし、私の環境ではプリフィックス「 /24 」が表示されるまでに90秒程度かかりました。また、プリフィックスが表示されなくてもログインできる場合があります。*16
  5. SR-IOVにてアサインされたNICが保持するIPアドレスを確認するためには、VMIへのログインが必要なので、virtctl console コマンドやsshなどでVMIにログインしてください。ログイン後、ip add showすると、以下のように見えると思います。eth1とeth2のMACアドレスがWorkerのVFのMACアドレスと一致していることを確認してください。加えて、外部NW機器にPingが通ることを確認できれば構築完了です。
[root@c771 ~]# ip add show
1: lo:  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
2: eth0:  mtu 1450 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 8e:29:cb:fc:87:4a brd ff:ff:ff:ff:ff:ff
    inet 10.244.1.14/24 brd 10.244.1.255 scope global noprefixroute dynamic eth0
       valid_lft 86312966sec preferred_lft 86312966sec
    inet6 fe80::f6a2:e021:e183:9852/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
3: eth1:  mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 96:e5:96:36:1c:90 brd ff:ff:ff:ff:ff:ff
    inet 192.168.30.139/24 brd 192.168.30.255 scope global noprefixroute dynamic eth1
       valid_lft 2966sec preferred_lft 2966sec
    inet6 fe80::409f:ff:fe56:8741/64 scope link 
       valid_lft forever preferred_lft forever
4: eth2:  mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 9e:46:96:a2:4b:4e brd ff:ff:ff:ff:ff:ff
    inet 192.168.30.138/24 brd 192.168.30.255 scope global noprefixroute dynamic eth2
       valid_lft 2966sec preferred_lft 2966sec
    inet6 fe80::6c:5eff:fee0:56ca/64 scope link 
       valid_lft forever preferred_lft forever

Worker上では以下のように認識されます。

[root@c80g106 ~]# ip link show
  一部省略
6: ens1f0:  mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 90:e2:ba:0b:37:b8 brd ff:ff:ff:ff:ff:ff
    vf 0 MAC 00:00:00:00:00:00, spoof checking on, link-state auto, trust off, query_rss off
    vf 1 MAC 96:e5:96:36:1c:90, vlan 300, spoof checking on, link-state auto, trust off, query_rss off
    vf 2 MAC 00:00:00:00:00:00, spoof checking on, link-state auto, trust off, query_rss off
    vf 3 MAC 00:00:00:00:00:00, spoof checking on, link-state auto, trust off, query_rss off
    vf 4 MAC 00:00:00:00:00:00, spoof checking on, link-state auto, trust off, query_rss off
    vf 5 MAC 00:00:00:00:00:00, spoof checking on, link-state auto, trust off, query_rss off
    vf 6 MAC 00:00:00:00:00:00, spoof checking on, link-state auto, trust off, query_rss off
    vf 7 MAC 00:00:00:00:00:00, spoof checking on, link-state auto, trust off, query_rss off
7: ens1f1:  mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 90:e2:ba:0b:37:b9 brd ff:ff:ff:ff:ff:ff
    vf 0 MAC 00:00:00:00:00:00, spoof checking on, link-state auto, trust off, query_rss off
    vf 1 MAC 00:00:00:00:00:00, spoof checking on, link-state auto, trust off, query_rss off
    vf 2 MAC 00:00:00:00:00:00, spoof checking on, link-state auto, trust off, query_rss off
    vf 3 MAC 9e:46:96:a2:4b:4e, vlan 300, spoof checking on, link-state auto, trust off, query_rss off
    vf 4 MAC 00:00:00:00:00:00, spoof checking on, link-state auto, trust off, query_rss off
    vf 5 MAC 00:00:00:00:00:00, spoof checking on, link-state auto, trust off, query_rss off
    vf 6 MAC 00:00:00:00:00:00, spoof checking on, link-state auto, trust off, query_rss off
    vf 7 MAC 00:00:00:00:00:00, spoof checking on, link-state auto, trust off, query_rss off

7-3.Podのデプロイ
vi /root/sriov-network-device-plugin/deployments/pod2.yaml

apiVersion: v1
kind: Pod
metadata:
  name: pod2
  annotations:
    k8s.v1.cni.cncf.io/networks: sriov-net2
spec:
  containers:
  - name: pod2
    image: docker.io/centos/tools:latest
    command:
    - /sbin/init
    resources:
      requests:
        intel.com/intel_sriov_netdevice2: '1'
      limits:
        intel.com/intel_sriov_netdevice2: '1'

kubectl create -f /root/sriov-network-device-plugin/deployments/pod2.yaml

<出力例>

[root@c80g105 ~]# kubectl get pods -o wide
NAME                       READY   STATUS    RESTARTS   AGE   IP            NODE            NOMINATED NODE   READINESS GATES
pod2                       1/1     Running   1          11h   10.244.1.19   c80g106.md.jp              
virt-launcher-c771-cp2nk   2/2     Running   0          23m   10.244.1.27   c80g106.md.jp              

<補足>
ポイントを3点ほど。

  1. annotations: > k8s.v1.cni.cncf.io/networks: に先ほど作成したCRDの名前sriov-net2を指定している点です。これにより、明示的にX540を使用する設定*17となっています。
  2. resources: > requests: に intel.com/intel_sriov_netdevice2を指定している点です。また「'1'」はVFの数を示していますが、2や3と増やしても、VFは1つしかアサインされませんでした。*18
  3. SR-IOVにてアサインされたNICが保持するIPアドレスは、以下のコマンドで確認できます。net1のMACアドレスがWorkerのVFのMACアドレスと一致していることを確認してください。加えて、外部NW機器にPingが通ることを確認できれば構築完了です。
kubectl exec -it pod2 -- ip addr show

[root@c80g105 ~]# kubectl exec -it pod2 -- ip addr show
1: lo:  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
3: eth0@if31:  mtu 1450 qdisc noqueue state UP group default
    link/ether 0a:56:43:b8:ad:f5 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.244.1.19/24 scope global eth0
       valid_lft forever preferred_lft forever
24: net1:  mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 2e:f2:70:b7:e9:ca brd ff:ff:ff:ff:ff:ff
    inet 192.168.30.6/24 brd 192.168.30.255 scope global net1
       valid_lft forever preferred_lft forever

Worker上では以下のように認識されます。

[root@c80g106 ~]# ip link show
  一部省略
8: ens2f0:  mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether a0:36:9f:3e:70:d4 brd ff:ff:ff:ff:ff:ff
    vf 0 MAC 4e:bc:dc:62:a8:7e, spoof checking on, link-state auto, trust off, query_rss off
    vf 1 MAC ee:c9:15:96:2d:5d, spoof checking on, link-state auto, trust off, query_rss off
    vf 2 MAC c2:82:b1:0f:ca:2e, spoof checking on, link-state auto, trust off, query_rss off
    vf 3 MAC 86:e2:0a:d6:96:ff, spoof checking on, link-state auto, trust off, query_rss off
    vf 4 MAC 6e:5e:70:e8:da:48, spoof checking on, link-state auto, trust off, query_rss off
    vf 5 MAC f2:2f:55:d8:b3:40, spoof checking on, link-state auto, trust off, query_rss off
    vf 6 MAC ae:a8:e8:48:25:2c, spoof checking on, link-state auto, trust off, query_rss off
    vf 7 MAC da:d8:2a:7d:2f:5b, spoof checking on, link-state auto, trust off, query_rss off
9: ens2f1:  mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether a0:36:9f:3e:70:d6 brd ff:ff:ff:ff:ff:ff
    vf 0 MAC ba:73:6f:81:ea:31, spoof checking on, link-state auto, trust off, query_rss off
    vf 1 MAC 0a:24:c7:34:26:ec, spoof checking on, link-state auto, trust off, query_rss off
    vf 2 MAC d6:62:b0:f5:01:fc, spoof checking on, link-state auto, trust off, query_rss off
    vf 3 MAC a2:05:32:03:eb:d4, spoof checking on, link-state auto, trust off, query_rss off
    vf 4 MAC b2:46:0e:e4:93:90, spoof checking on, link-state auto, trust off, query_rss off
    vf 5 MAC 76:d6:8a:9f:1a:84, spoof checking on, link-state auto, trust off, query_rss off
    vf 6 MAC 2e:f2:70:b7:e9:ca, vlan 300, spoof checking on, link-state auto, trust off, query_rss off
    vf 7 MAC 8e:c0:94:c2:98:f9, spoof checking on, link-state auto, trust off, query_rss off
7-4.VMIとPodの差異について

以下にまとめます。

8086:10ed X520 VMI用 vfio-pci
8086:1515 X540 Pod用 ixgbevf

VMI用vfio-pciについて

  • worker上でip add showを打っても、vfio-pci上で動作しているため、blacklist登録時と同様にVFのNICは表示されません。このためip link showでMACアドレスを確認します。
  • VMIによってVFが掴まれていないとき、MACアドレスは00:00:00:00:00:00が正常です。
  • 今回の構成では、IPアドレスは外部NW機器*19にてDHCPサーバを立ててIPアドレスアサインしています。

Pod用ixgbevfについて

  • ixgbevfにて動作しているため、ip add showにてVFのNICが表示されます。
  • PodによってVFが掴まれているか否かに関わらず、任意のMACアドレスアサインされます。そのMACアドレスはそのままPodが継承します。
  • IPアドレスは(恐らく)MultusからDHCPのように自動でアサインされています。
  • アドレスレンジを指定する場合は以下のようにCRDに追記してください。
vi /root/sriov-network-device-plugin/deployments/sriov-crd2.yaml

[root@c80g105 ~]# vi /root/sriov-network-device-plugin/deployments/sriov-crd2.yaml

apiVersion: "k8s.cni.cncf.io/v1"
kind: NetworkAttachmentDefinition
metadata:
  name: sriov-net2
  annotations:
    k8s.v1.cni.cncf.io/resourceName: intel.com/intel_sriov_netdevice2
spec:
  config: '{
  "type": "sriov",
  "cniVersion": "0.3.1",
  "name": "sriov300_2",
  "vlan": 300,
  "ipam": {
    "type": "host-local",
    "subnet": "192.168.30.0/24",
    "rangeStart": "192.168.30.64",
    "rangeEnd": "192.168.30.127",
    "routes": [{
      "dst": "0.0.0.0/0"
    }],
    "gateway": "192.168.30.254"
  }
}'
7-5.VMIとPodのdescribe情報*20

VMIのdescribe
以下のコマンドで確認できます。実際の出力はここにアップしておきます。

kubectl describe pods virt-launcher-c771-cp2nk

Podのdescribe
以下のコマンドで確認できます。実際の出力はここにアップしておきます。

kubectl describe pods pod2

VMIのlog
VMIが正常起動しないときやログインできないときは、以下のログを確認してみてください。
膨大な量のログが出力されるので、ここにアップしておきます。

kubectl logs virt-launcher-c771-cp2nk compute

以上です。

8.最後に

以下のサイトを参考にさせて頂きました。
GitHub - k8snetworkplumbingwg/multus-cni
GitHub - k8snetworkplumbingwg/sriov-cni: DPDK & SR-IOV CNI plugin
GitHub - k8snetworkplumbingwg/sriov-network-device-plugin: SRIOV network device plugin for Kubernetes
https://kubevirt.io/user-guide/docs/latest/creating-virtual-machines/interfaces-and-networks.html
Kubernetes: Multus + SRIOV quickstart – Zenghui Shi

今回はSR-IOVでしたが、次回はOvS-DPDKなどにも挑戦できればと考えています。
f:id:metonymical:20191215111811p:plain

Kubernetesのネットワーク構成といった場合、flannelやCalicoの解説サイトがたくさんあったため、とても助かりました。
しかし、SR-IOVに関してVMIとPodの両方を解説しているサイトが皆無に等しかったため今回の記事を書きました。

今のネットワークのトレンドとしては、P4やeBPF+XDP、SRv6といった辺りかなと思っています。
このため、まだまだ私は遅れ気味だなと日々痛感しておりますが、これでようやく足元ぐらいに辿り着けていられればいいなぁ、と思っています。

また、クラウドネイティブといったキーワードが頻出していますが、実際に自分で手を動かしていじってみた結果、クラウドネイティブであることの意味とか意義とか優位性というモノが、理解できたような気がします。

今回、Podに加えて、KubeVirt上のVMIについても記載しましたが、私の個人的な感想として、一定の需要がありそうな気がしました。

というのも、
いきなり一足飛びでコンテナ化できない or コンテナ化するにはハードルが高いといったケースを鑑みるに、libvirtd上で仮想マシンを稼働させながらも、アーキテクチャk8sに(クラウドネイティブ化したシステムとして)統一したい、といった要件を満たせるかもしれないと思いました。

Migrate for Anthosなどは、まさしくこういった課題を解決するために提供され始めたのかな、という気がします。
Migrate for Anthos  |  Google Cloud

*1:おさらい程度に読み飛ばしてください。

*2:今回は無効化してしまいますが、本来はあまり良くないです。

*3:Masterは不要ですが念のため

*4:名前解決できるようにしておいてください。

*5:このため今回はNICを2枚挿しにしています。

*6:これに気付くまでに2週間程度掛かりました。。。

*7:kubectl get pods上ではrunningになりますが。

*8:virshのようにStart & StopやConsoleログインができます。

*9:私の環境では、tar.gzをDLするとハッシュ値が異なるというエラーで弾かれてしまったためインストールしていません。最近、KubeVirtがv0.24.0にアップデートされた影響かもしれません。

*10:Multusが3番目なのですが、先にインストールしても大丈夫かなと思っています。

*11:Master&Workerにて、それぞれsriov-device-pluginを起動させるため、5-3 & 5-4にて、Docker Imageのビルドをしています。

*12:Cloud Initからイメージファイルを落とした場合、証明書認証が必要となるため、公開鍵を仕込む必要がありますので、私は自前でqcow2ファイルを作っています。

*13:即ち、vfio-pciを使用する設定

*14:これに気付くのに1週間程度掛かりました。。

*15:flannelからアサインされるIP

*16:これの理由は未だによくわかっていません。。

*17:即ち、ixgbevfを使用する設定

*18:この辺りの仕様が正直よくわかっていません。。

*19:構成図右側のスイッチ

*20:何度か作り直しをしているためIPやMACなどが一部異なっていると思います。