kubernetesのDevice Plugin "intel device plugins for kubernetes" を試す

## はじめに

> Note:
> ~~pluginのデモはR640にFPGA1枚刺し環境では動いたのだが，~~
> ~~R740にFPGA2枚刺し環境だとnlb3（コンテナ内の実行ファイルの名前）がFPGAデバイスを見つけられなかった~~
> ~~`./nlb3 -B 0x86` みたいにちゃんとデバイス指定をすれば動くのでデモの`test_fpga.sh`で２枚刺しを想定した呼び出し方をしていないのが問題っぽい~~
> もう一度確認してみたところちゃんと動きました


> 追記:
> [openvino_container_for_PAC](https://github.com/khrd/openvino_container_for_PAC)
> このプラグインを用いてOpenVINOのJobをFPGAで動かす方法をgithubにまとめてupしておきました(結構雑ですが)

kubernetesの[公式ページに](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/#examples)Device Pluginsがいくつか載っていますが，

![dev_examples.png](https://qiita-image-store.s3.amazonaws.com/0/80918/c6369712-d207-a6a7-7e19-947c2a1601a1.png)

その中でも「intel device plugins for kubernetes」を動かすまでのエントリです．

## 環境　(準備物)

今回はこのプラグインを使用してFPGAがスケジューリングされるかを確認したいので，
Intel PACが刺さったPowerEdge R640をk8sノードとして追加し，PACのドライバを入れておきます．
また，デバイスプラグインをコンパイルするためにGoをインストールしておきます．


- R640 (with Intel PAC)
    - ↑をk8sのノードとして追加
    - Acceleration Stack環境をインストール
    - Go言語環境をインストール

## プラグインのインストール

準備ができたらプラグインのインストールを進めていきます．

プラグインを動かすためには下記の3つが必要となります．
CRD (Custom Resource Definition) を使っているらしい．

1. FPGA device plugin
2. FPGA admission controller webhook
3. FPGA prestart CRI-O hook

### 1. FPGA device plugin

**ここでの作業は，FPGAサーバで行う**
device pluginの設定は全てのFPGAサーバで行う必要がある（★1 or ★2あたり）．
imageの配布もお忘れなく．

#### ソースコードを入手
```
$ mkdir -p $GOPATH/src/github.com/intel/
$ cd $GOPATH/src/github.com/intel/
$ git clone https://github.com/intel/intel-device-plugins-for-kubernetes.git
```

#### device pluginのビルド
Goでビルドを行うためGo自体のインストールと`GOPATH`の設定を予めしておく必要がある．

```
$ cd $GOPATH/src/github.com/intel/intel-device-plugins-for-kubernetes
$ make fpga_plugin
```

#### kubelet のソケットが `/var/lib/kubelet/device-plugins/` ディレクトリに存在するか確認する

```
$ ls /var/lib/kubelet/device-plugins/kubelet.sock
/var/lib/kubelet/device-plugins/kubelet.sock
```

#### device pluginの動作モードを選ぶ

FPGA device pluginは `af` モードと `region` モードがあり，どちらかを選択する必要がある．

`af` モードは予めAFUを書き込んだFPGAを公開するモード．AFU IDをリソースとして登録することができる．
`region` モードはFPGAのPA(Partial reconfiguration Area)そのものを公開するモード．FPGAのインタフェースIDをリソースとして登録できる．
という認識...間違ってたら直します:sweat:

`af` にしたい人は★1へ， `region` にしたい人は★2へお進み下さい．
自分は，demo (test-fpga-region.yaml) を動かしたいので★1に進みました．
(regionと名前がついているが，afリソースを指定していた)

##### ★1(Case1) Run FPGA device plugin in af mode

1.k8sのadmin権限でプラグインを実行する

```
$ export KUBE_CONF=/var/run/kubernetes/admin.kubeconfig # path to kubeconfig with admin's credentials
$ export NODE_NAME="<node name>" # if the node's name was overridden and differs from hostname
$ sudo -E $GOPATH/src/github.com/intel/intel-device-plugins-for-kubernetes/cmd/fpga_plugin/fpga_plugin -mode af -kubeconfig $KUBE_CONF
FPGA device plugin started in af mode
device-plugin start server at: /var/lib/kubelet/device-plugins/fpga.intel.com-af-f7df405cbd7acf7222f144b0b93acd18.sock
device-plugin registered
```

2.FPGAサーバにデバイスプラグインが登録されたかを確認する

プラグインを導入した時点でnodeのcapacityとallocatableにFPGAリソースが現れ，FPGAの利用可能な数が表示されるようになります．

```
$ kubectl describe node <node name> | grep fpga.intel.com
 fpga.intel.com/af-f7df405cbd7acf7222f144b0b93acd18:  1
 fpga.intel.com/af-f7df405cbd7acf7222f144b0b93acd18:  1
```

##### ★2(Case2) Run FPGA device plugin in region mode

1.k8sのadmin権限でプラグインを実行する

```
$ export KUBE_CONF=/var/run/kubernetes/admin.kubeconfig # path to kubeconfig with admin's credentials
$ export NODE_NAME="<node name>" # if the node's name was overridden and differs from hostname
$ sudo -E $GOPATH/src/github.com/intel/intel-device-plugins-for-kubernetes/cmd/fpga_plugin/fpga_plugin -mode region -kubeconfig $KUBE_CONF
FPGA device plugin started in region mode
device-plugin start server at: /var/lib/kubelet/device-plugins/fpga.intel.com-region-ce48969398f05f33946d560708be108a.sock
device-plugin registered
Check if FPGA device plugin is registered on master:
```

2.FPGAサーバにデバイスプラグインが登録されたかを確認する

```
$ kubectl describe node <node name> | grep fpga.intel.com
 fpga.intel.com/region-ce48969398f05f33946d560708be108a:  1
 fpga.intel.com/region-ce48969398f05f33946d560708be108a:  1
```

3.`region` の場合はnodeにアノテーション設定する

```
kubectl annotate node mynode "fpga.intel.com/device-plugin-mode=region
```

#### Deploy FPGA device plugin as DaemonSet

1.プラグイン用のサービスアカウントを作成する

```
$ kubectl create -f deployments/fpga_plugin/fpga_plugin_service_account.yaml
serviceaccount/intel-fpga-plugin-controller created
clusterrole.rbac.authorization.k8s.io/node-getter created
clusterrolebinding.rbac.authorization.k8s.io/get-nodes created
```

2.デーモンセットの作成

```
$ kubectl create -f deployments/fpga_plugin/fpga_plugin.yaml
daemonset.apps/intel-fpga-plugin created
```

3.イメージの作成

```
$ make intel-fpga-plugin
```

このイメージはデフォルトで `af` モードとして動作するので， `region` モードで動かしたいときは，先程のアノテーション設定が必要．

### 2. FPGA admission controller webhook


#### webhookイメージの作成

```
$ export SRC=$GOPATH/src/github.com/intel/intel-device-plugins-for-kubernetes
$ cd $SRC
$ make intel-fpga-admissionwebhook
$ docker images
REPOSITORY                    TAG                                        IMAGE ID            CREATED          SIZE
intel-fpga-admissionwebhook   089ab82d18cceed0217e36a1600d9462018f44d5   267bd3a2524f        7 sec ago        25.2MB
intel-fpga-admissionwebhook   devel                                      267bd3a2524f        7 sec ago        37.7MB
```

#### webhooサービスのデプロイ

`cfssl` と `jq` が必要らしいのでインストールしてない人は先にインストールしてから `scripts/webhook-deploy.sh` 実行しましょう．

```
$ cd $SRC
$ ./scripts/webhook-deploy.sh
Create secret including signed key/cert pair for the webhook
Creating certs in /tmp/tmp.Ebb77GBKqm
certificatesigningrequest.certificates.k8s.io/intel-fpga-webhook-svc.default created
NAME                             AGE       REQUESTOR      CONDITION
intel-fpga-webhook-svc.default   0s        system:admin   Pending
certificatesigningrequest.certificates.k8s.io/intel-fpga-webhook-svc.default approved
secret/intel-fpga-webhook-certs created
Removing /tmp/tmp.Ebb77GBKqm
Create FPGA CRDs
customresourcedefinition.apiextensions.k8s.io/acceleratorfunctions.fpga.intel.com created
customresourcedefinition.apiextensions.k8s.io/fpgaregions.fpga.intel.com created
acceleratorfunction.fpga.intel.com/arria10-nlb0 created
acceleratorfunction.fpga.intel.com/arria10-nlb3 created
fpgaregion.fpga.intel.com/arria10 created
clusterrole.rbac.authorization.k8s.io/fpga-reader created
clusterrolebinding.rbac.authorization.k8s.io/default-fpga-reader created
Create webhook deployment
deployment.extensions/intel-fpga-webhook-deployment created
Create webhook service
service/intel-fpga-webhook-svc created
Register webhook
mutatingwebhookconfiguration.admissionregistration.k8s.io/fpga-mutator-webhook-cfg created
```

CRDのリソース定義については `af` が `./deployment/fpga_admissionwebhook/af-crd.yaml` ，
`region` が `./deployment/fpga_admissionwebhook/region-crd.yaml` にあります．
また， AFU ID/Interface ID のマッピングには`mappings-collection.yaml` を使います．

例えば，podが要求するリソースに `fpga.intel.com/arria10-nlb3` と書くと，
`fpga.intel.com/af-f7df405cbd7acf7222f144b0b93acd18 ` に翻訳され，
`fpga.intel.com/af-f7df405cbd7acf7222f144b0b93acd18 ` をAllocatableにもつノードにpodが立てられます．

図に起こすとこんな感じ．
![map.png](https://qiita-image-store.s3.amazonaws.com/0/80918/49b766ef-a2e2-b31a-859f-ccb779879ef0.png)



### 3. FPGA prestart CRI-O hook


#### CRI-O hookのビルド
```
$ cd $GOPATH/src/github.com/intel/intel-device-plugins-for-kubernetes
$ make fpga_crihook
```

#### 'Acceleration Stack for Runtime'　のダウンロード

`a10_gx_pac_ias_1_1_pv_rte_installer.tar.gz` を[ココ](https://www.intel.com/content/www/us/en/programmable/solutions/acceleration-hub/downloads.html)からダウンロードして `$GOPATH/src/github.com/intel/intel-device-plugins-for-kubernetes/deployments/fpga_plugin` ディレクトリに保存する

#### CRI hookとその依存関係など含む　init コンテナをビルド

```
$ cd $GOPATH/src/github.com/intel/intel-device-plugins-for-kubernetes/deployments/fpga_plugin
$ ./build-initcontainer-image.sh
...
$ docker images
REPOSITORY                    TAG                                        IMAGE ID            CREATED             SIZE
intel-fpga-initcontainer      devel                                      8d9c9adca145        6 days ago          19MB
intel-fpga-initcontainer      e30f8763c7209577fbaa720addd80271214e8bec   8d9c9adca145        6 days ago          19MB
```

### 4. check plugin

プラグインが正しくインストールされたかを確認します．
うまく動いていない場合，動作に必要なimageがノードに配布されているかを確認しましょう．

#### kubectl get all

うまく動いていれば， `pod/intel-fpga-plugin-xxxxx` と `xxx/intel-fpga-webhook-xxxx` のステータスがrunningやavailableになっているはずです．

```
$ k get all --all-namespaces

## POD
NAMESPACE     NAME                                                 READY   STATUS      RESTARTS   AGE
default       pod/intel-fpga-webhook-deployment-7688655c78-9q4kp   1/1     Running     3          6d6h
kube-system   pod/intel-fpga-plugin-zlzn9                          1/1     Running     0          5d21h
...

## Service
NAMESPACE     NAME                             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)         AGE
default       service/intel-fpga-webhook-svc   ClusterIP   10.109.230.217   <none>        443/TCP         6d6h
...

## deployment
NAMESPACE     NAME                                            DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
default       deployment.apps/intel-fpga-webhook-deployment   1         1         1            1           6d6h
...

## replicaset
NAMESPACE     NAME                                                       DESIRED   CURRENT   READY   AGE
default       replicaset.apps/intel-fpga-webhook-deployment-7688655c78   1         1         1       6d6h

```
#### kubectl describe <fpgaserver>

FPGAノードの状態確認

```
$ k describe no r640-2
Name:               r640-2
Roles:              <none>
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    kubernetes.io/hostname=r640-2
Annotations:        flannel.alpha.coreos.com/backend-data: {"VtepMAC":"**:**:**:**:**:**"}
                    flannel.alpha.coreos.com/backend-type: vxlan
                    flannel.alpha.coreos.com/kube-subnet-manager: true
                    flannel.alpha.coreos.com/public-ip: *.*.*.*
                    fpga.intel.com/device-plugin-mode: region
                    kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock
                    node.alpha.kubernetes.io/ttl: 0
                    volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp:  Wed, 28 Nov 2018 16:48:15 +0900
Taints:             <none>
Unschedulable:      false
Conditions:
  Type             Status  LastHeartbeatTime                 LastTransitionTime                Reason                       Message
  ----             ------  -----------------                 ------------------                ------                       -------
  OutOfDisk        False   Wed, 05 Dec 2018 17:02:18 +0900   Sat, 01 Dec 2018 12:15:51 +0900   KubeletHasSufficientDisk     kubelet has sufficient disk space available
  MemoryPressure   False   Wed, 05 Dec 2018 17:02:18 +0900   Sat, 01 Dec 2018 12:15:51 +0900   KubeletHasSufficientMemory   kubelet has sufficient memory available
  DiskPressure     False   Wed, 05 Dec 2018 17:02:18 +0900   Sat, 01 Dec 2018 12:15:51 +0900   KubeletHasNoDiskPressure     kubelet has no disk pressure
  PIDPressure      False   Wed, 05 Dec 2018 17:02:18 +0900   Wed, 28 Nov 2018 16:48:15 +0900   KubeletHasSufficientPID      kubelet has sufficient PID available
  Ready            True    Wed, 05 Dec 2018 17:02:18 +0900   Sat, 01 Dec 2018 12:15:51 +0900   KubeletReady                 kubelet is posting ready status
Addresses:
  InternalIP:  *.*.*.*
  Hostname:    r640-2
Capacity:
 cpu:                                                     16
 ephemeral-storage:                                       51171Mi
 fpga.intel.com/af-d8424dc4a4a3c413f89e433683f9040b:      0
 fpga.intel.com/af-f7df405cbd7acf7222f144b0b93acd18:      0
 fpga.intel.com/region-9926ab6d6c925a68aabca7d84c545738:  1
 hugepages-1Gi:                                           0
 hugepages-2Mi:                                           40Mi
 memory:                                                  131484456Ki
 pods:                                                    110
Allocatable:
 cpu:                                                     16
 ephemeral-storage:                                       48291014167
 fpga.intel.com/af-d8424dc4a4a3c413f89e433683f9040b:      0
 fpga.intel.com/af-f7df405cbd7acf7222f144b0b93acd18:      0
 fpga.intel.com/region-9926ab6d6c925a68aabca7d84c545738:  1
 hugepages-1Gi:                                           0
 hugepages-2Mi:                                           40Mi
 memory:                                                  131341096Ki
 pods:                                                    110
System Info:
 Machine ID:                 *
 System UUID:                *
 Boot ID:                    *
 Kernel Version:             3.10.0-693.el7.x86_64
 OS Image:                   CentOS Linux 7 (Core)
 Operating System:           linux
 Architecture:               amd64
 Container Runtime Version:  docker://18.9.0
 Kubelet Version:            v1.12.3
 Kube-Proxy Version:         v1.12.3
PodCIDR:                     10.244.2.0/24
Non-terminated Pods:         (3 in total)
  Namespace                  Name                           CPU Requests  CPU Limits  Memory Requests  Memory Limits
  ---------                  ----                           ------------  ----------  ---------------  -------------
  kube-system                intel-fpga-plugin-bhswp        0 (0%)        0 (0%)      0 (0%)           0 (0%)
  kube-system                kube-flannel-ds-amd64-4cdnl    100m (0%)     100m (0%)   50Mi (0%)        50Mi (0%)
  kube-system                kube-proxy-4v7x8               0 (0%)        0 (0%)      0 (0%)           0 (0%)
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource                                                Requests   Limits
  --------                                                --------   ------
  cpu                                                     100m (0%)  100m (0%)
  memory                                                  50Mi (0%)  50Mi (0%)
  fpga.intel.com/af-d8424dc4a4a3c413f89e433683f9040b      0          0
  fpga.intel.com/af-f7df405cbd7acf7222f144b0b93acd18      0          0
  fpga.intel.com/region-9926ab6d6c925a68aabca7d84c545738  0          0
Events:                                                   <none>


```

## Running demo

### Run demo

ここでは NLB (ネイティブループバック)　のAFUが書き込まれたAFリソースを指定してうごかすデモを行います．

※実際には `test-fpga-region.yml` というファイルですが，
リポジトリ内は `yml` と `yaml` が混在していたので，Qiitaでは `yaml` で統一してます．

```
cd $GOPATH/src/github.com/intel/intel-device-plugins-for-kubernetes/demo
kubectl apply -f test-fpga-region.yaml
```

### Check Status

実行後， `Completed` となっていればスケジューリングは正しく動いています．

```
$ kubectl get po
NAME                                             READY   STATUS      RESTARTS   AGE
test-fpga-region                                 0/1     Completed   0          81m
```

### Check log

また，コンテナの標準出力を確認して，問題なくFPGAが動いているかも確認．

```
$ kubectl logs test-fpga-region
Running nlb3

Cachelines Read_Count Write_Count Cache_Rd_Hit Cache_Wr_Hit Cache_Rd_Miss Cache_Wr_Miss   Eviction 'Clocks(@200 MHz)'   Rd_Bandwidth   Wr_Bandwidth
         1          1           0            0            0             0             0          0              142     0.090 GB/s     0.000 GB/s

VH0_Rd_Count VH0_Wr_Count VH1_Rd_Count VH1_Wr_Count VL0_Rd_Count VL0_Wr_Count
           1            1            0            0            0            0

nlb3 succeeded as expected
Running nlb0 sample
Error: device enumeration failed.
Please make sure that the driver is loaded and that a bitstream for
AFU id: D8424DC4-A4A3-C413-F89E-433683F9040B is programmed.
nlb0 failed as expected
SUCCESS
```

もし以下のようにnlb3が実行できないと怒られたらPACに書き込まれているbitstreamを確認する．

```
kubectl logs test-fpga-region
Running nlb3
Error: device enumeration failed.
Please make sure that the driver is loaded and that a bitstream for
AFU id: F7DF405C-BD7A-CF72-22F1-44B0B93ACD18 is programmed.
nlb3 failed
FAILURE: unexpeced nlb3 failure
```
#### bitstream の書き換え

以下は，AFU IDの確認方法と，nlb3の書き込み方法です．

```
$ cat /sys/class/fpga/intel-fpga-dev.0/intel-fpga-port.0/afu_id
d8424dc4a4a3c413f89e433683f9040b
$ sudo fpgaconf $OPAE_PLATFORM_ROOT/hw/samples/nlb_mode_3/bin/nlb_mode_3.gbs
$ cat /sys/class/fpga/intel-fpga-dev.0/intel-fpga-port.0/afu_id
f7df405cbd7acf7222f144b0b93acd18
```

## おわりに

今回はk8sでFPGAをスケジューリングを試してみましたが，
今度はOpenVINOのコンテナイメージを用意していろいろ試したいなーと思います．

以下model optimizerをk8s上で動かしたという内容
https://ai.intel.com/openvino-model-server-boosts-ai-inference-operations/

## Appendix. AFU ID

| filename                  | afuid                                 |
| ------------------------- | ------------------------------------- |
| hello_afu.gbs             | 850adcc2-6ceb-4b22-9722-d43375b61c66  |
| hello_intr_afu.gbs        | 850adcc2-6ceb-4b22-9722-d43375b61c66  |
| hello_mem_afu.gbs         | 35f9452b-25c2-434c-93d5-6f8c60db361c  |
| dma_afu.gbs               | 331DB30C-9885-41EA-9081-F88B8F655CAA  |
| streaming_dma_afu.gbs     | eb59bf9d-b211-4a4e-b3e3-753ce68634ba  |
| eth_e2e_e10.gbs           | 05189fe4-0676-dd24-b74f-291af34e1783  |
| eth_e2e_e40.gbs           | 26b40788-034b-4389-b3c1-51a1b62ed6c2  |
| nlb_mode_0.gbs            | d8424dc4-a4a3-c413-f89e-433683f9040b  |
| nlb_mode_3.gbs            | f7df405c-bd7a-cf72-22f1-44b0b93acd18  |