Cloud NativeなストレージRookの検証

# Rook とは
　[Rook](https://rook.io)は，Cloud Native環境向けのストレージとして2018年1月に[CNCF](https://www.cncf.io)に加わりました．
　Rookの現在のバージョン(v0.7)では，Software Defined Storage(SDS)の[Ceph](https://ceph.com)をコンテナ化し,Kubernetes上にPodとして実行しています．これにより，Cephがもともと備えるブロックストレージ，ファイルストレージ，オブジェクトストレージの3つのタイプのストレージを提供しています．また，データの格納場所については，KubernetesのFlexVolumeを使っています．

# Rook の主な特徴
RookはCloud Nativeなストレージとして次のような特徴をもっています．

- KubernetesのSelf-healingにより，Rook自身もSelf-managing serviceを実現
- Rookはアプリケーションと合わせてスケールできるHyper-convergedのストレージクラスタを構築可能
- RookのもつCephにより，ブロックストレージ，ファイルストレージ，オブジェクトストレージを提供
- 少ないデータロスのリスクで複数のクラスタへデータを分散
- Cephによりストレージ機能のSnapshot, Cloning, Versioningが利用可能
- Kubernetes のreplicaの機能によりCephのノードを簡単にTBオーダのサイズまでスケール可能
- コモディティなハードウェアで実行可能
- Rookの監視をPrometheusで容易に実現

# Minikube を使った検証
今回の検証では Minikube(Kubernetes v1.9.0)を利用しています．
Minikube特有の設定は，後述のRook Clusterの`dataDirHostPath`の設定変更だけですので，他の環境で試す場合には，本設定項目を読み飛ばしてください．

## Rook のダウンロード
[GitHub のRootのレポジトリ](https://github.com/rook/rook) からダウンロードします．

```
$ git clone git@github.com:rook/rook.git
Cloning into 'rook'...
<snip>
$ cd rook/cluster/examples/kubernetes/
```
以下，`rook/cluster/examples/kubernetes/` ディレクトリで作業します．

## Rook Operator のデプロイ
Rook Operator のデプロイします．

```
$ kubectl create -f rook-operator.yaml 
namespace "rook-system" created
clusterrole "rook-operator" created
serviceaccount "rook-operator" created
clusterrolebinding "rook-operator" created
deployment "rook-operator" created

$ kubectl get pods -n rook-system
NAME                             READY     STATUS    RESTARTS   AGE
rook-agent-dw98q                 1/1       Running   0          9m
rook-operator-77cf655476-zpcdj   1/1       Running   0          10m
```
rook-systemネームスペースが新規に作成され，rook-agentと rook-operatorのPodが立ち上がります．

## Rook Clusterのデプロイ

次に Rook Cluster をデプロイします．
Minikubeの場合，viなどのエディタを使い，`rook-cluster.yaml`ファイルの`dataDirHostPath`を/data/rookに変更してください．
Minikubeでは，/data ディレクトリが永続的なファイルを格納するディレクトリとして用意されているため，この変更により，Rookに保存されたデータが永続化されるようになります．
以下に変更箇所を示します．

```diff
$ git diff
diff --git a/cluster/examples/kubernetes/rook-cluster.yaml b/cluster/examples/kubernetes/rook-cluster.yaml
index 368ec4f..3db4d28 100644
--- a/cluster/examples/kubernetes/rook-cluster.yaml
+++ b/cluster/examples/kubernetes/rook-cluster.yaml
@@ -14,7 +14,7 @@ spec:
   # The path on the host where configuration files will be persisted. If not specified, a kubernetes emptyDir will be created (not recommended).
   # Important: if you reinstall the cluster, make sure you delete this directory from each host or else the mons will fail to start on the new cluster.
   # In Minikube, the '/data' directory is configured to persist across reboots. Use "/data/rook" in Minikube environment.
-  dataDirHostPath: /var/lib/rook
+  dataDirHostPath: /data/rook
   # toggle to use hostNetwork
   hostNetwork: false
   # set the amount of mons to be started
```
`dataDirHostPath `の変更が完了後，kubectl を使ってRook Clusterをデプロイします.

```
$ kubectl create -f rook-cluster.yaml 
namespace "rook" created
cluster "rook" created
$ $ kubectl get pods -n rook
NAME                             READY     STATUS    RESTARTS   AGE
rook-api-848df956bf-s6mvl        1/1       Running   0          7m
rook-ceph-mgr0-cfccfd6b8-nm4dv   1/1       Running   0          7m
rook-ceph-mon0-jf4jm             1/1       Running   0          7m
rook-ceph-mon1-4g4dc             1/1       Running   0          7m
rook-ceph-mon2-s4kwg             1/1       Running   0          7m
rook-ceph-osd-b9ckh              1/1       Running   0          7m
```
rookネームスペースが新規に作成され，コンテナ化されたCephと，RookのAPIサーバのコンテナが立ち上がります．
Cephのノード数を変更したい人は`rook-cluster.yaml`の spec.monCount(デフォルト値はCephのモニタリングノードの最小数3) の値を変更してください．
以上で，Rook 自体のインストールは完了です．
続いて，提供するストレージの設定を行います．

## ストレージの設定
Rook ではブロックストレージ，ファイルストレージ，オブジェクトストレージの3つのタイプのストレージが利用出来ます．
以下に，それぞれの特徴をまとめます．用途に応じて使い分けてください．

- ブロックストレージ 
  - 1台のPodからのみ利用する場合に利用
  -  書き込み要求の排他制御により1台のPodからのみアクセス可能
  -  整合性を考慮するRDBなどの用途向き
- ファイルストレージ
  - Kubernetes Cluster内の複数Podからアクセスする場合に利用
  - 複数Podでファイルをシェア可能
- オブジェクトストレージ
  - Kubenetes Cluster内または外部からS3 API互換でアクセスする場合に利用
  - HTTP/HTTPS プロトコルのため，Kubernetesの外部へも提供可能

今回の検証では，ファイルストレージを使います．
他のタイプを使いたい方は，[公式ドキュメント](https://rook.io/docs/rook/master/storage.html)を参照ください．

### ファイルストレージの設定
kubectl を使って Ceph のファイルストレージ(CephFS: Ceph File System)をデプロイします．

```
$ kubectl create -f rook-filesystem.yaml 
filesystem "myfs" created
$ $ kubectl get pod -l app=rook-ceph-mds -n rook 
NAME                                  READY     STATUS    RESTARTS   AGE
rook-ceph-mds-myfs-7d59fdfcf4-8g278   1/1       Running   0          7m
rook-ceph-mds-myfs-7d59fdfcf4-8nm9v   1/1       Running   0          7m
```
新たに，rook-ceph-mds-myfs のPodが立ち上がります．
これで，Rookでファイルストレージが利用できるようになります．
以下は，今回作成したCephの設定です．

```rook-filesystem.yaml
apiVersion: rook.io/v1alpha1
kind: Filesystem
metadata:
  name: myfs
  namespace: rook
spec:
  # The metadata pool spec
  metadataPool:
    replicated:
      # Increase the replication size if you have more than one osd
      size: 1
  # The list of data pool specs
  dataPools:
    - failureDomain: osd
      replicated:
        size: 1
      # If you have at least three osds, erasure coding can be specified
      # erasureCoded:
      #   dataChunks: 2
      #   codingChunks: 1
  # The metadata service (mds) configuration
  metadataServer:
    # The number of active MDS instances
    activeCount: 1
    # Whether each active MDS instance will have an active standby with a warm metadata cache for faster failover.
    # If false, standbys will be available, but will not have a warm cache.
    activeStandby: true
    # The affinity rules to apply to the mds deployment
    placement:
    #  nodeAffinity:
    #    requiredDuringSchedulingIgnoredDuringExecution:
    #      nodeSelectorTerms:
    #      - matchExpressions:
    #        - key: role
    #          operator: In
    #          values:
    #          - mds-node
    #  tolerations:
    #  - key: mds-node
    #    operator: Exists
    #  podAffinity:
    #  podAntiAffinity:
    resources:
    # The requests and limits set here, allow the filesystem MDS Pod(s) to use half of one CPU core and 1 gigabyte of memory
    #  limits:
    #    cpu: "500m"
    #    memory: "1024Mi"
    #  requests:
    #    cpu: "500m"
    #    memory: "1024Mi"
```

## Pod からのファイルストレージの利用
試しに，nginxのPodを作成し，Rookのファイルストレージをマウントします．
vi などのテキストエディタで以下のYAMLファイルを作成します．
ここでは，my-nginx-pod.yamlというファイル名で保存します．

```my-nginx-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: my-nginx-pod
spec:
  containers:
  - name: nginx
    image: nginx
    ports:
    - containerPort: 80
    volumeMounts:
    - mountPath: "/usr/share/nginx/html"
      name: cephfs
  volumes:
  - name: cephfs
    flexVolume:
      driver: rook.io/rook
      fsType: ceph
      options:
        fsName: myfs
        clusterName: rook
```
RookはKubernetesのFlexVolume を使っているため，volumesのタイプはflexVolume, fsTypeはcephとして設定します．
また，volumes.flexVolume.options に次の値を指定します．

- fsName: rook-filesystem.yamlで設定したFileSystemのmetadata.nameを指定 (eg. myfs)
- clusterName: Rook Clusterの Namespaceを指定 (eg. rook)

my-nginx-pod.yamlが作成出来たら，nginxをデプロイします．

```
$ kubectl create -f my-nginx-pod.yaml
pod "my-nginx-pod" created          
```

RookのCephFSがマウント出来ているかnginxのPodにログインし確認します．

```
$ kubectl get pods 
NAME           READY     STATUS    RESTARTS   AGE
my-nginx-pod   1/1       Running   0          2m
$ kubectl exec -ti my-nginx-pod bash
root@my-nginx-pod:/# df -h
Filesystem                                                Size  Used Avail Use% Mounted on
overlay                                                    17G  1.6G   14G  11% /
tmpfs                                                      64M     0   64M   0% /dev
tmpfs                                                    1001M     0 1001M   0% /sys/fs/cgroup
/dev/sda1                                                  17G  1.6G   14G  11% /etc/hosts
shm                                                        64M     0   64M   0% /dev/shm
10.111.0.5:6790,10.110.199.110:6790,10.100.191.29:6790:/   18G  2.1G   16G  12% /usr/share/nginx/html
tmpfs                                                    1001M   12K 1001M   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs                                                    1001M     0 1001M   0% /sys/firmware
root@my-nginx-pod:/# exit
exit

```
/usr/share/nginx/html がCephのモニタリングノードが指定され，マウントに成功しているのが確認できます．
このように，Rookで提供されたファイルストレージ(CephFS)をPodから利用することが出来ます．

## Rookの便利コマンドが格納されたToolBox
Rook では ToolBoxとして，Rookの運用に役立つコマンド群が用意されています．
代表的なものとしてはRookの管理コマンド`rookctl`, Cephの管理コマンド`ceph`，CephのバックエンドのRADOS管理コマンド`rados`が格納されています
まずは，ToolBoxをセットアップします．

```
$ kubectl create -f rook-tools.yaml 
pod "rook-tools" created
$ kubectl get pod rook-tools -n rook
NAME         READY     STATUS    RESTARTS   AGE
rook-tools   1/1       Running   0          2m
```

ToolBoxのコマンドを利用するには，rook-toolsのPodへログインします．

```
$ kubectl -n rook exec -it rook-tools bash
root@rook-tools:/# hostname
rook-tools
```
あとは，rookctl, ceph, rados のコマンドを実行するだけです．

rookctlコマンドの実行例

```
root@rook-tools:/# rookctl status
OVERALL STATUS: OK

USAGE:
TOTAL       USED       DATA       AVAILABLE
17.11 GiB   2.00 GiB   2.19 KiB   15.10 GiB

MONITORS:
NAME             ADDRESS                 IN QUORUM   STATUS
rook-ceph-mon0   10.100.191.29:6790/0    true        OK
rook-ceph-mon2   10.110.199.110:6790/0   true        OK
rook-ceph-mon1   10.111.0.5:6790/0       true        OK

MGRs:
NAME             STATUS
rook-ceph-mgr0   Active

OSDs:
TOTAL     UP        IN        FULL      NEAR FULL
1         1         1         false     false

PLACEMENT GROUPS (200 total):
STATE          COUNT
active+clean   200

root@rook-tools:/# rookctl filesystem list
NAME      METADATA POOL   DATA POOLS
myfs      myfs-metadata   myfs-data0
```

cephコマンドの実行例

```
root@rook-tools:/# ceph status
  cluster:
    id:     451d6fe3-6982-4745-8a4b-3fa99a58f7ab
    health: HEALTH_OK
 
  services:
    mon: 3 daemons, quorum rook-ceph-mon0,rook-ceph-mon2,rook-ceph-mon1
    mgr: rook-ceph-mgr0(active)
    mds: myfs-1/1/1 up  {0=m8g278=up:active}, 1 up:standby-replay
    osd: 1 osds: 1 up, 1 in
 
  data:
    pools:   2 pools, 200 pgs
    objects: 21 objects, 2246 bytes
    usage:   2051 MB used, 15465 MB / 17516 MB avail
    pgs:     200 active+clean
 
  io:
    client:   852 B/s rd, 1 op/s rd, 0 op/s wr
 
root@rook-tools:/# ceph df
GLOBAL:
    SIZE       AVAIL      RAW USED     %RAW USED 
    17516M     15465M        2051M         11.71 
POOLS:
    NAME              ID     USED     %USED     MAX AVAIL     OBJECTS 
    myfs-metadata     1      2246         0        14589M          21 
    myfs-data0        2         0         0        14589M           0 
```

rados コマンドの実行例

```
root@rook-tools:/# rados df
POOL_NAME     USED OBJECTS CLONES COPIES MISSING_ON_PRIMARY UNFOUND DEGRADED RD_OPS RD    WR_OPS WR   
myfs-data0       0       0      0      0                  0       0        0      0     0      0    0 
myfs-metadata 2246      21      0     21                  0       0        0   2890 1449k     42 8192 

total_objects    21
total_used       2051M
total_avail      15465M
total_space      17516M

root@rook-tools:/# rados lspools
myfs-metadata
myfs-data0
```

## クリーンアップ
今回の検証環境を削除します．
作成した my-nginx-podを削除します．

```
$ kubectl delete pods my-nginx-pod
pod "my-nginx-pod" deleted
```
続いて，Rook Cluster, Rook Operatorを削除します．

```
$ kubectl delete -f rook-cluster.yaml 
namespace "rook" deleted
cluster "rook" deleted

$ kubectl delete -f rook-operator.yaml 
namespace "rook-system" deleted
clusterrole "rook-operator" deleted
serviceaccount "rook-operator" deleted
clusterrolebinding "rook-operator" deleted
deployment "rook-operator" deleted
```

最後に，Minikube 上に保存されたデータを削除します．

```
$ minikube ssh
                         _             _            
            _         _ ( )           ( )           
  ___ ___  (_)  ___  (_)| |/')  _   _ | |_      __  
/' _ ` _ `\| |/' _ `\| || , <  ( ) ( )| '_`\  /'__`\
| ( ) ( ) || || ( ) || || |\`\ | (_) || |_) )(  ___/
(_) (_) (_)(_)(_) (_)(_)(_) (_)`\___/'(_,__/'`\____)

$ cd /data
$ sudo rm -rf rook/
$ exit
```

# 感想
　現時点のRookは，一言で言うとKubernetes上のCephです。Kubernetes上にのせることで，基本的なストレージの管理はKubernetesの操作で利用できます．そのため，管理者の学習コストは低いかと思います．一方で，ストレージベンダーから見たときには，メリットは少ないかもしれません．
　管理系のコントロールプレーンは上述のようにKubernetesでカバーされてしまいます．独自の管理操作をKubernetesの世界観に追加することは，管理者からベンダーロックインの印象を持たれるリスクがあります．Cloud Nativeな世界に近づけば近づくだけロックインを避けたいと思う人々は多く，このリスクを払拭するほどのメリットがないと厳しいのではないでしょうか．
　また、I/Oを司るデータプレーンに関しては、コンテナからみたブロック、ファイル、オブジェクトストレージのインターフェースは全てCeph に握られています．そのため、ストレージベンダーは，Ceph 配下のディスクを提供以外の参入は，ハードルが高い印象を受けました．現在のRookはKubernetesのFlexVolumeを利用しているため、コンテナから直接外部ストレージにI/Oが届くわけでなく、Nodeのサーバを経由して届くことになります．つまり、どれだけ外部ストレージのLatencyやThroughputが良くても，Nodeのサーバがボトルネックになり100%の性能を発揮出来ないかと思います．また，KubernetesのNodeはベアメタルのサーバよりもVMのものが多く，スケールアウトの特徴を活かすためにパススルーでのストレージ割り当てを行わない環境が多いかと思います．このような環境において，アプリケーションが載っているコンテナは，複数Nodeを自由に移動し実行されています．そのため，VM/Hypervisor/CPU(VT-x technologyなど)で，どのVMから発行されたI/Oなのか外部ストレージ側で知りうることが出来たとしても，どのコンテナ上のアプリケーションが発行したのか特定するのは難しく，少しトリッキーな方法を取らざるを得ません．やり方次第では，ベンダーロックインのリスクを抱えてしまうことになります．
　上述のように，アプリケーションのコンテナ化が進んでいる今日において，Rookが成熟してくるとストレージベンダーにとって，頭の痛い存在になると思います．とはいえ，ストレージベンダーも黙っておらずコンテナ対応を加速させるでしょうから，これからの対応が楽しみです．

# 参考
- [Rook](https://rook.io)
- [Ceph](https://ceph.com)
- [CNCF to Host the Rook Project to Further Cloud-Native Storage Capabilities](https://www.cncf.io/blog/2018/01/29/cncf-host-rook-project-cloud-native-storage-capabilities/)
- [Kubernetes Flexvolume](https://kubernetes.io/docs/concepts/storage/volumes/#flexvolume)