OpenEBS の Benchmark してみました (cStor Data Plane)

# はじめに
世の中には Ceph 等、汎用サーバを束ねてスケーラビリティの高いストレージを作るための Open Source ソフトウェアが沢山あります。しかしながら、スケーラビリティを高めるためのトレードオフとして、コンポーネントが大規模化して運用管理のハードルがやや高めになってしまいます。
とはいえ、(Linstor コントローラを含まない)DRBD のような、シンプルな構成だけでは沢山の Volume を管理するのが辛くなってしまいます。

この問題は Public Cloud を使う人にとっては今まで問題にはならなかった内容ですが、Kubernetes  (k8s) が広く普及してきたことで、次のような形で問題が顕在化しました。

- k8s 単体でも簡単に Pod が抱えるデータを永続化する方法として、Statefulset や Persistent Volume が利用できるようになった
- Public Cloud であれば Persistent Volume が利用できるが、自前で オンプレミス環境に k8s クラスタを構築する場合は Persistent Volume を別途用意する必要があり、運用管理も大変
- Public Cloud で Persistent Volume を使った場合でも、例えば Node が稼働している仮想サーバホストで障害が発生すると、EBS の切り離し・再接続等の回復処理に時間を要するため、Pod が再び利用できる状態になるまでに、少なくとも数分-10分単位での Down Time が発生する可能性がある

もし Node 上に存在する Local Disk (SSD, NVMe 等)を Persistent Volume として簡単に利用できれば、上記の課題はスッキリと解決します。OpenEBS は、まさにこのような課題に向き合うために開発が進んでいる模様です。

- https://www.openebs.io

少々前置きが長くなりましたが、OpenEBS を使えば本当に期待通りに課題が解決できそうなのか、アーキテクチャや実装を調査しつつベンチマークを取得しましたので、Qiita にて情報共有をさせていただきます。

# OpenEBS の凄いところ
今回の調査で OpenEBS が非常に面白いと思ったこととしては、S3 等の Object Storage に増分バックアップを出来る点があります。

例えば、AWS の EBS はスナップショットを取得すると実際のデータは S3 上に保存されます。S3 上に保存されたデータは増分バックアップに対応しているため、それぞれのスナップショットデータは(変更ブロックが少ない限りは)小さくて済むため、取得にかかる時間やコストが小さくなります。

OpenEBS も EBS と同様のことが、Jiva / cStor 共に velero を組み合わせることで実現できそうです。ただし、EBS にはある Lazy Loading のような仕掛けは無いため、大容量の Volume を Backup から復旧させる場合はダウンロード等に要する時間が長くなると考えられます。


# アーキテクチャ
OpenEBS は公式サイトにて沢山のドキュメントが提供されています。次のドキュメントにて、大まかなアーキテクチャについて解説されています。

https://docs.openebs.io/docs/next/architecture.html

また、サイボウズさんが、とても詳しい分析まで含めた調査情報をまとめられていますので、合わせて参照すると理解が深まると思います。ただし、評価対象の Data Plane は Jiva のみとなっており、cStore が含まれていません。性能要件等がある場合は、Jiva だけはなく cStore についても評価するのが良さそうです。

- https://blog.cybozu.io/entry/2018/03/29/080000

OpenEBS は、大きく分けると次の3コンポーネントで構成されています。

- Control Plane Components
    - OpenEBS の Control Plane
    - Provisioner, API Server, Volume Exports, Volume Sidecar
- Data Plane Components
    - OpenEBS の Data Plane
    - LocalPV, Jiva 又は cStor が利用できる (併用も可能)
- Node Disk Manager
    -  Disk の Discovery, Monitoring, k8s node に attach された volume の管理


## cStor
cStor は OpenEBS の推奨 Storage Engine になりますが、非常に独創的なアイデアを元に作られています。基本的な着想ポイントについては、次のドキュメントにまとまった情報があります。

https://www.slideshare.net/OpenEBS/introduction-to-cstor-replica-contributors-meet-5th-oct-2018

上記ドキュメントやコードを読んだ、私のザックリとした解釈では、cStor は次のような仕掛けで動作しています。

- Initiator から受け取った Volume I/O は iSCSI Target Daemon(istgt) でレプリケーション処理が施される
- istgt はユーザ空間で稼働する ZFS の DMU API (zrepl)に I/O 処理を依頼することで、 Local で動かす ZFS とほぼ互換性を保つ形で ZFS へのアクセスを実現している
- cStor では Backend Storage に ZFS を使いつつ、複数ホストでのレプリケーションを実現している。これにより、ZFSが持っている堅牢性や増分バックアップ機能 とレプリケーションによる冗長性が同時に実現できている

# インストール方法
OpenEBS は k8s クラスタ上で稼働することを前提に設計されており、Helm Chart も含めて提供されているので、デプロイがとても簡単に出来ます。

https://docs.openebs.io/docs/next/prerequisites.html

基本的にはドキュメントの通りにコマンドを実行するだけで簡単にデプロイが完了しますが、下記にも作業記録をまとめておきます。

## k8s node 事前準備
OpenEBS では Node 上で iSCSI Initiator を稼働させる必要があるため、予めパッケージをインストールしておきます。

```
# Ubuntu の場合
sudo cat /etc/iscsi/initiatorname.iscsi
systemctl status iscsid
sudo systemctl enable iscsid && sudo systemctl start iscsid

sudo apt-get update
sudo apt-get install -y open-iscsi
sudo service open-iscsi restart
```

```
# EKS (amazon linux) の場合
sudo yum install iscsi-initiator-utils -y
sudo systemctl enable iscsid && sudo systemctl start iscsid
systemctl status iscsid
```

## OpenEBS デプロイ
OpenEBS を k8s クラスタにデプロイします。

```
curl https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash
kubectl -n kube-system create serviceaccount tiller
kubectl create clusterrolebinding tiller --clusterrole cluster-admin --serviceaccount=kube-system:tiller

kubectl label nodes k8s-node1 node=openebs
kubectl label nodes k8s-node2 node=openebs
kubectl label nodes k8s-node3 node=openebs

# カスタマイズが不要な場合は、values.yaml は不要なので、次のコマンドでデプロイできます
helm install --namespace openebs --name openebs stable/openebs --version 1.1.0
```

コンポーネントの配置先を固定する等、カスタマイズをしたい場合は values.yaml を作成して helm install の際にオプションとして指定します

```
curl -O https://raw.githubusercontent.com/helm/charts/master/stable/openebs/values.yaml
vi values.yaml

helm install --namespace openebs --name openebs stable/openebs -f values.yaml --version 1.1.0
```

```values.yaml
apiserver:
  nodeSelector:
    node: openebs

provisioner:
  nodeSelector:
    node: openebs

localprovisioner:
  nodeSelector:
    node: openebs

snapshotOperator:
  nodeSelector:
    node: openebs

ndm:
  nodeSelector:
    node: openebs

# XXX c.f. https://github.com/helm/charts/pull/15768
#ndmOperator:
#  nodeSelector:
#    node: openebs

webhook:
  nodeSelector:
    node: openebs
```

## 動作確認
OpenEBS のデプロイが完了すると、Pod の稼働状況は次のようになります。

```
kubectl get pod -n openebs
NAME                                           READY   STATUS    RESTARTS   AGE
openebs-admission-server-5c48d4854c-xfpzr      1/1     Running   0          71s
openebs-apiserver-67b45d9788-vnrrx             1/1     Running   3          71s
openebs-localpv-provisioner-859546f846-p7fnj   1/1     Running   0          71s
openebs-ndm-csb4p                              1/1     Running   0          71s
openebs-ndm-operator-877dc9bbf-9scks           1/1     Running   1          71s
openebs-ndm-xfmpb                              1/1     Running   0          71s
openebs-provisioner-66f8544f5-4th8w            1/1     Running   0          71s
openebs-snapshot-operator-96b8cb6cb-2q6ql      2/2     Running   0          71s
```

OpenEBS のデプロイが完了すると複数の Custom Resource が追加されます。
'kubectl get blockdevice' を実行することで、blockdevice 一覧が出力されます。

```
kubectl get blockdevice -n openebs
NAME                                           SIZE           CLAIMSTATE   STATUS   AGE
blockdevice-02fab1d465201930101c182a6595af85   475000000000   Unclaimed    Active   9s
blockdevice-0f9d16af7f76455ab7bfc73a56bb1194   475000000000   Unclaimed    Active   11s
sparse-3ba5aa462e1fff8a5af1680b28f3a8e1        10737418240    Unclaimed    Active   11s
sparse-d6e914598fe77b0d45291d27d0f45c9d        10737418240    Unclaimed    Active   9s
```

## Storage Pool Claim (spc) の作成
続いて、Storage Pool Claim (spc) を作成します。OpenEBS が正常に稼働していると、Node に存在する Disk が自動的に検出され、blockdevice として登録されます。

spc リソースを作成することで、Storage Pool -> blockdevice の mapping がされ、k8s の Storage Class として利用できる状態になります。

```
vi cstor-pool1-config.yaml
```

```cstor-pool-config.yaml
#Use the following YAMLs to create a cStor Storage Pool.
apiVersion: openebs.io/v1alpha1
kind: StoragePoolClaim
metadata:
  name: cstor-disk-pool
  annotations:
    cas.openebs.io/config: |
      - name: PoolResourceRequests
        value: |-
            memory: 2Gi
      - name: PoolResourceLimits
        value: |-
            memory: 4Gi
spec:
  name: cstor-disk-pool
  type: disk
  poolSpec:
    poolType: striped
  blockDevices:
    blockDeviceList:
    - blockdevice-02fab1d465201930101c182a6595af85
    - blockdevice-0f9d16af7f76455ab7bfc73a56bb1194
```

上記で作成した StoragePoolClaim を kubectl コマンドを用いて作成します。

```
kubectl apply -f cstor-pool1-config.yaml
kubectl get spc
kubectl get csp
```

最後に、Storage Class を作成します。

```
vi  openebs-sc-rep1.yaml
kubectl apply -f openebs-sc-rep1.yaml
kubectl get sc
```

```openebs-sc-rep1.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: openebs-sparse-sc-statefulset
  annotations:
    openebs.io/cas-type: cstor
    cas.openebs.io/config: |
      - name: StoragePoolClaim
        value: "cstor-disk-pool"
      - name: ReplicaCount
        value: "2"
provisioner: openebs.io/provisioner-iscsi
```

PVC の作成方法は、通常の k8s の手順と同一になります。
次の例では、PVC(name: cstor-pvc-mysql-large) を storageClassName を指定して作成しています。

```
vi pvc-example.yaml
kubectl create -f pvc-example.yaml
```

```pvc-example.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: cstor-pvc-mysql-large
spec:
  storageClassName: openebs-sparse-sc-statefulset
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
```

## OpenEBS 動作テスト
OpenEBS の動作テストとして、Persistent Volume を attach した nginx Pod を作成して、問題なく動作することを確認します。

```
vi web-pvc-test.yaml
kubectl create -f web-pvc-test.yaml
```

```web-pvc-test.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: k8s.gcr.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
      nodeSelector:
        alpha.eksctl.io/nodegroup-name: compute-1
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      storageClassName: openebs-sparse-sc-statefulset
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 100Gi
```

# cStor Benchmark 結果
今回の benchmark では、AWS EKS 上に構築した OpenEBS について fio を用いて benchmark を取得した結果をまとめました。

## 測定環境
- AWS EKS
    - OpenEBS Nodes
        - i3.large 2台
    - Client Nodes
        - m5.xlarge 1台
    - AZ配置
        - 3台の Node それぞれ、異なる AZ に配置

## 測定条件
- fio
    - direct=1
    - size=10G
    - randrepeat=0
    - ioengine=libaio
    - group_reporting=1
    - iodepth=128
    - bs={1k, 512k, 1024k}
    - time_based=1
    - runtime=180


## 測定結果

大まかな結果としては、Read / Write, IOPS でバラツキはあるものの、RAW Disk と比べると 数分の1 - 10分の1 程度の性能低下となりました。
今回使用したインスタンスは NVMe のため、RAW Disk の性能がとても高いのに対して、OpenEBS で 同じ NVMe を使うと、Read / Write どちらも IOPS の観点では大幅な性能低下が発生しました。

期待値としては 50% 程度の性能低下だったので、思ったよりも cStor の性能が出ていない模様です。性能が大きく低下する原因としては、一旦 iSCSI の接続を k8s の service として Pod が受け付けた上で zrepl (ZFS のユーザ空間API)に Volume 通信をする2段構えにより、遅延が発生しているのかもしれません。性能のボトルネック箇所については、さらに調査をする必要がありそうです。

| RAW Disk  |   bs=1k  |   bs=512k | bs=1024k |
|:----------|----:|:----------:|:--------:|
| Read      |  103242 | 956   |   478     |
| Write     |  35012  | 382   |   191     |

| cStor Disk  |   bs=1k  |   bs=512k | bs=1024k |
|:----------|----:|:----------:|:--------:|
| Read      |  10706 | 108   |   55     |
| Write     |  989  | 71   |   39     |

![overall-disk-perf.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/96389/b2f66a93-bebd-1f0c-364c-087bcca348e3.png)


# まとめ
OpenEBS は、IOPS性能の観点ではまだまだ十分に速いとはいえないものの、k8s との親和性が抜群に高く、管理が容易であったり増分バックアップに対応していたり、際立った機能を持ち合わせています。Open Source としての開発が盛んに進んでいるため、今後の進化次第では大化けする可能性を感じました。