Kubernetes NativeなストレージOpenEBSの検証

# はじめに

　本検証では、Kubernetes上にデプロイできるストレージのひとつであるOpenEBSについて動作検証を行います。OpenEBSは主に、ノード上のディスクを活用/集約しコンテナ向けにブロックストレージ(iSCSIボリューム)を提供するクラウドネイティブなストレージです。
　特徴としては、後述するCAS(Container Attached Storage)というアーキテクチャを採用し、[クラウドネイティブ](https://github.com/cncf/toc/blob/master/DEFINITION.md)なストレージを実現しています。さらに、OpenEBSは真のKubernetesネイティブ(`Truly Kubernetes native`)とも謳っています。

# 動作検証

本検証では、OpenEBS(Community Version)のデプロイからPVの作成からPodへのマウントまでを検証します。

## 検証環境

本検証の環境を示します。

- Kubernetes v1.15.2 (Node 3台)
- OpenEBS v1.5.0 (Community Version)

OpenEBSをデプロイする前に、Node上でiscsidを起動しておきます。

## セットアップ

OpenEBSをデプロイします。

```
$ kubectl apply -f https://openebs.github.io/charts/openebs-operator-1.5.0.yaml
```

Namespace(`openebs`)が作成され、OpenEBSのリソースがデプロイされます。
OpenEBSのPodが全て起動していることを確認します。

```
$ kubectl get pod -n openebs
NAME                                           READY   STATUS    RESTARTS   AGE
maya-apiserver-77d8f94b58-gsbnj                1/1     Running   0          5m56s
openebs-admission-server-77b88865d4-cvnmt      1/1     Running   0          5m55s
openebs-localpv-provisioner-6994dd88b8-4mkwx   1/1     Running   0          5m55s
openebs-ndm-6pvlx                              1/1     Running   0          5m55s
openebs-ndm-hfmvv                              1/1     Running   0          5m55s
openebs-ndm-operator-c69cd6666-r48pp           1/1     Running   1          5m55s
openebs-ndm-r2xdl                              1/1     Running   0          5m55s
openebs-provisioner-7b87d7956-bt6rw            1/1     Running   0          5m56s
openebs-snapshot-operator-6959d684cf-wz5lj     2/2     Running   0          5m55s
```

StorageClass(SC)も確認します。

```
$ kubectl get sc
NAME                            PROVISIONER                                                AGE
openebs-device                  openebs.io/local                                           7m8s
openebs-hostpath                openebs.io/local                                           7m8s
openebs-jiva-default            openebs.io/provisioner-iscsi                               7m11s
openebs-snapshot-promoter       volumesnapshot.external-storage.k8s.io/snapshot-promoter   7m9s
```

SCは以下の4つがデプロイされています。

SC Name | Description | Document |
--------|-------------|----------|
openebs-device |Beta. Local PV based on device のSC. Dynamic Provisioning 可能|[Provision OpenEBS Local PV Based on Device](https://docs.openebs.io/docs/next/uglocalpv.html#Provision-OpenEBS-Local-PV-based-on-Device)|
openebs-hostpath |Bata. Local PV based on hostpath のSC. Dynamic Provisioning 可能|[PV Based on hostpath](https://docs.openebs.io/docs/next/uglocalpv.html#Provision-OpenEBS-Local-PV-based-on-hostpath)|
openebs-jiva-default | Stable. Kubernetesのノード上のDiskを使い、複数ノードにデータの複製を作り、iSCSI Volumeとして提供するSC. Dynamic Provisioning可能| [Jiva User Guide](https://docs.openebs.io/docs/next/jivaguide.html)|
openebs-snapshot-promoter |Beta. cStorのSC. Snapshotが利用可能。 Kubernetesのノード上のDiskを使い、複数ノードにデータの複製を作り、iSCSI Volumeとして提供する。Dynamic Provisioning 可能 |[cStor User Guide/Cloning a cStor Snapshot](https://docs.openebs.io/docs/next/ugcstor.html#snapshot-and-clone-of-a-cStor-volume)|

以下の検証ではStableの`openebs-jiva-default`を利用します。
JivaのVolumeの生成元であるStoragePool(sp)を確認します。

```
$ kubectl get sp
NAME      AGE
default   18m

$ kubectl get sp default -o yaml
apiVersion: openebs.io/v1alpha1
kind: StoragePool
metadata:
  creationTimestamp: "2019-12-15T10:24:12Z"
  generation: 1
  name: default
  resourceVersion: "37980396"
  selfLink: /apis/openebs.io/v1alpha1/storagepools/default
  uid: 7bc223b4-abe1-4d62-ab8f-f64545c86d91
spec:
  path: /var/openebs
```

デプロイ直後の`default`のStoragePoolはNodeの`/var/openebs`を利用するように設定されています。
以上でOpenEBSのデプロイが完了です。

## PVC,PVの作成＆Podへのマウント

StorageClass(`openebs-jiva-default`)を使い、PersistentVolume(PV)を作成します。
作成に利用するPersistentVolumeClaim(PVC)のManifest(`pvc-jiva.yaml`)を以下に示します。

```pvc-jiva.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jiva-vol
spec:
  storageClassName: openebs-jiva-default
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
```

PVCのManifest(`pvc-jiva.yaml`)をデプロイします。

```
$ kubectl apply -f pvc-jiva.yaml
```

PVC,PVを確認します。

```
$ kubectl get pvc,pv
NAME                             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS           AGE
persistentvolumeclaim/jiva-vol   Bound    pvc-b95df193-bc9f-46eb-ac5e-be156805b449   1Gi        RWO            openebs-jiva-default   17m

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              STORAGECLASS           REASON   AGE
persistentvolume/pvc-b95df193-bc9f-46eb-ac5e-be156805b449   1Gi        RWO            Delete           Bound    openebs/jiva-vol   openebs-jiva-default            17m
```

PVC(`jiva-vol`)がデプロイされ、PV(`pvc-b95df193-bc9f-46eb-ac5e-be156805b449`)が作成されているのが確認できます。
また、PVが作成される際、PVに対応したDeploymentが2つデプロイされます。

```
$ kubectl get deploy
NAME                                            READY   UP-TO-DATE   AVAILABLE   AGE
...
pvc-b95df193-bc9f-46eb-ac5e-be156805b449-ctrl   1/1     1            1           19m
pvc-b95df193-bc9f-46eb-ac5e-be156805b449-rep    3/3     3            3           19m
```
この2つのDeploymentおよびDeploymentにより生成されたPodについては後述に[CAS](#cas)の解説にて詳しく説明します。
つづいて、作成したPVをPodにマウントします。
PodのManifest(`pod.yaml`)を以下に示します。

```pod.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    run: pod-pvc
  name: pod-pvc
spec:
  containers:
  - image: nginx
    name: pod-pvc
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: myvolume
  volumes:
  - name: myvolume
    persistentVolumeClaim:
      claimName: jiva-vol
```

Podをデプロイします。

```
$ kubectl apply -f pod.yaml
```

Podに正しくVolumeがマウントされているか確認します。

```
$ kubectl get pod
NAME                                                             READY   STATUS    RESTARTS   AGE
...
pod-pvc                                                          1/1     Running   0          110m
...

$ kubectl exec pod-pvc -- df -h
Filesystem                       Size  Used Avail Use% Mounted on
...
/dev/sdb                         976M  1.3M  959M   1% /usr/share/nginx/html
...
```

Pod(`pod-pvc`)の`/usr/share/nginx/html`ディレクトリに`/dev/sdb`がマウントされているのが確認できました。
以上、OpenEBSでPVを作成しPodにマウントできることを動作検証しました。

# CAS

OpenEBSはCAS(Coantianer Attached Storage)というアーキテクチャを特徴としています。
CASは、クラウドネイティブなストレージのアーキテクチャとしてFlexibilityとScalabilityを目指して、ストレージのコントローラとデータレプリカをPodとして実行されます。そのため、PVの生成時にコントローラのDeployment(`pvc-xxx-ctrl`)とデータレプリカのDeployment(`pvc-xxx-rep`)がデプロイされます。

![cas.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229880/0eabd3aa-c1f3-de00-beae-86a46bc2ac3e.png)

つまり、CASではストレージコントローラやデータレプリカも全てPodとして実行されます。
なお、データレプリカ数については、SC(`openebs-jiva-default`)の`RepicaCount`にて指定されています。

```
$ kubectl get sc openebs-jiva-default -o yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  annotations:
    cas.openebs.io/config: |
      - name: ReplicaCount
        value: "3"
      - name: StoragePool
        value: default
...
```

データの保存先について、データレプリカのPod(`pvc-xxx-rep-xxx`)の定義を確認します。

```
$ kubectl get pod pvc-b95df193-bc9f-46eb-ac5e-be156805b449-rep-fb88cb5d-jcj76 -o yaml
apiVersion: v1
kind: Pod
...
  containers:
  - args:
    - replica
    - --frontendIP
    - 10.103.59.125
    - --size
    - 1Gi
    - /openebs
    command:
    - launch
    image: quay.io/openebs/jiva:1.5.0
 ...
    volumeMounts:
    - mountPath: /openebs
      name: openebs
...
  volumes:
  - hostPath:
      path: /var/openebs/pvc-b95df193-bc9f-46eb-ac5e-be156805b449
      type: ""
    name: openebs
```

データレプリカのPodは、デプロイ時にデフォルトで生成されたStoragePool（`default`)で指定されている`/var/openebs`ディレクトリ(Node)が、hostPathで`/openebs`ディレクトリ(Container)にマウントされています。つまり、データの実体はデータレプリカのPodが配置されたNodeの`/var/openebs`ディレクトリ配下に格納されています。

# 感想

　これまで、Cephをはじめ多くのSDS(Storage Defined Storage)ではコントローラはストレージに1つ(1セット)の構成でした。つまり、コントローラに障害が発生した場合、全てのVolumeに影響がありました。そのため、コントローラを冗長化し、性能も全てのVolumeの要求性能を満たせるだけのスペックを用意する必要がありました。
　一方、OpenEBSは、CASというクラウドネイティブのストレージといえるアーキテクチャを採用しています。CASはPV(Volume)ごとにコントローラのPodを生成します。つまり、あるコントローラに障害が発生した場合、影響をうけるのは、そのコントローラが担当していた1 PVのみです。また、あるPVのコントローラ性能をスケールさせたい場合も、担当するコントローラのPodの性能をスケールさせるだけで可能です。さらに、Podとしてデプロイされているため、Kubernetesのセルフヒーリングにより障害が発生しても回復力があります。デメリットとしては、PV(Volume)ごとにコントローラとデータレプリカのPodを作成するため、Pod数が増えることです。たとえば、10個のPVを作った場合、データレプリカ数が3のケースでは40個(コントローラ10個,データレプリカ30個)のPodが生成されます。これにより、コントローラやデータレプリカのPodを起動しておくCPU/メモリやIPが消費されます。これにより、大規模環境では、運用上のボトルネックになるかもしれません。
　このように、クラウドネイティブなストレージでは、単純にSDSをコンテナ化し動作させるというアプローチだけでなく[「クラウドネイティブ」](https://github.com/cncf/toc/blob/master/DEFINITION.md#日本語版)らしく、CASのように障害時に回復力がありスケーラブルなアーキテクチャが登場してきています。従来、落ちないシステムとして設計されてきたアプライアンスやSDSのストレージアーキテクチャに対し、落ちることを前提に回復力を高めるアーキテクチャのクラウドネイティブなストレージはチャレンジな技術だと思います。今後もクラウドネイティブなアーキテクチャにチャレンジするストレージが増え、ユーザに選択肢が増えることを期待します。

# 参考情報

- [OpenEBS](https://openebs.io)
- [OpenEBS Docs](https://docs.openebs.io)
- [GitHub/openebs](https://github.com/openebs/openebs)