takutakahashi.dev

Rook Ceph を ZFS で動かす

ZFS が好きなんですが、Kubernetes との組み合わせを見ると、
クラウドネイティブな仕組みでいい感じにマルチノード冗長してくれる仕組みがありません。

そのへんは Rook が人気っぽいので、Rook を利用して Ceph を動かしてみました。
ZFS 上にファイル保存領域を作成し、 OSD に 割り当てる方式としました。

免責

僕はストレージに全く詳しくないので、動いた、以上のものが書けません。
徐々に学習を進めようと思っておりますが。。。

方式

Rook のストレージ割当方式は、数種類あるみたいです。

  1. ディレクトリを指定する方式 (非推奨?)
  2. ブロックデバイスを直接割り当てる方式
  3. pvc を切り出して OSD に割り当てる方式 (OSD on PVC)

OSD on PVC が、クラスタ利用者と管理者との間の責務の分離ができて
Kubernetes Native っぽくて良さそうです。

zfs の領域の一部を pvc として切り出す方法があれば、この方式が使えそうですね。

storageclass を追加する

zfs から pvc を dynamic provisioning する storageclass が結構出回っています。
その中で、openebs が出している zfs-localpv が、メンテもされていて使えそうでした。

https://github.com/openebs/zfs-localpv/

そもそも openebs 自体が Cloud Native SDS というか Rook とド競合なのですが、こっちはおいおい。

zvol を利用する方式にする

注意として、Rook は /dev 以下のファイルしか使うことができません。
サイボウズさんの資料に書いてあったのと、検証中に気づきました。

https://blog.cybozu.io/entry/2019/12/03/114746

zfs-localpv は、切り出す方式を dataset か zvol かを選択することができます。
zvol として切り出せば、/dev/pool 以下にスペシャルファイルが生成されるため、
OSD が認識できます。

導入

1. zfs-localpv を導入する

こちらは簡単です。

https://github.com/openebs/zfs-localpv/

kubectl apply -f https://raw.githubusercontent.com/openebs/zfs-localpv/v0.8.0/deploy/zfs-operator.yaml

ドキュメントには master が指定してあったんですが、Release Version の 0.8.0 を入れることにしました。

controller と node ds が動けば OK です。

% kubectl get pod -n kube-system
NAME                                     READY   STATUS    RESTARTS   AGE
openebs-zfs-controller-0                 5/5     Running   0          7h10m
openebs-zfs-node-2stzq                   2/2     Running   0          7h10m

2. StorageClass を定義する

zvol を切り出すようにします。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: openebs-zfspv
parameters:
  compression: "off"
  dedup: "off"
  fstype: ext4
  poolname: tank/pv
  recordsize: 4k
provisioner: zfs.csi.openebs.io
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer

fsType: ext4 とすると、zvol で切り出して ext4 でフォーマットしてくれるそうです。すごいな。

3. Rook インストール

こちらを参考に、Rook を入れます。

https://rook.io/docs/rook/v1.3/ceph-examples.html

git clone https://github.com/rook/rook.git
cd rook/cluster/examples/kubernetes/ceph/
kubectl create -f common.yaml
kubectl create -f operator.yaml

4. cephcluster のインストール

cluster-on-pvc.yaml を使います。
storageClass を openebs-zfspv に書き換えます。

kubectl create -f cluster-on-pvc.yaml

5. 動作確認

pvc が作成されます。

% kubectl get pvc
NAME                STATUS    VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS    AGE
rook-ceph-mon-a     Bound     pvc-fd0655ec-dfdb-4cbf-9f90-2fe1542d143c   10Gi       RWO            openebs-zfspv   3m21s
rook-ceph-mon-b     Bound     pvc-dff19762-e45d-4af4-9071-e0f39ff95974   10Gi       RWO            openebs-zfspv   3m16s
rook-ceph-mon-c     Bound     pvc-1c1ad478-ebf9-4a64-8eb5-e75326f02a87   10Gi       RWO            openebs-zfspv   3m11s
set1-data-0-x9cts   Bound     pvc-e8968dcc-47d0-49b4-9f4d-abc73af0515f   10Gi       RWO            openebs-zfspv   2m10s
set1-data-1-dl7l7   Bound     pvc-1cb6b604-41e2-43a4-849f-63a0cccb868a   10Gi       RWO            openebs-zfspv   2m9s
set1-data-2-sqsvk   Pending                                                                        openebs-zfspv   2m9s

このような感じで zvol が切り出されます。

owner@node01:~$ zfs list
NAME                                               USED  AVAIL  REFER  MOUNTPOINT
tank/pv/pvc-1c1ad478-ebf9-4a64-8eb5-e75326f02a87  10.3G   640G   368M  -
tank/pv/pvc-dff19762-e45d-4af4-9071-e0f39ff95974  10.3G   640G   367M  -
tank/pv/pvc-e8968dcc-47d0-49b4-9f4d-abc73af0515f  10.3G   641G  89.5K  -
tank/pv/pvc-fd0655ec-dfdb-4cbf-9f90-2fe1542d143c  10.3G   640G   367M  -

OSD, MON が動き出します。

% kubectl get pod
NAME                                                              READY   STATUS      RESTARTS   AGE
csi-cephfsplugin-65kdg                                            3/3     Running     0          8h
csi-cephfsplugin-provisioner-7469b99d4b-22vrt                     5/5     Running     0          8h
csi-cephfsplugin-provisioner-7469b99d4b-28m89                     5/5     Running     0          8h
csi-rbdplugin-m2ktt                                               3/3     Running     0          8h
csi-rbdplugin-provisioner-865f4d8d-vfzhp                          6/6     Running     0          8h
csi-rbdplugin-provisioner-865f4d8d-zffxj                          6/6     Running     0          8h
rook-ceph-crashcollector-6e9b63cba5b2954bdd5c4b0c27ab309e-d4m7q   1/1     Running     0          102s
rook-ceph-mgr-a-7d5c7b7f4d-z62rv                                  1/1     Running     0          63s
rook-ceph-mon-a-567dd8f7b8-p672h                                  1/1     Running     0          102s
rook-ceph-mon-b-cc54d68dd-stp89                                   1/1     Running     0          93s
rook-ceph-mon-c-5d9b66bb66-44fqg                                  1/1     Running     0          77s
rook-ceph-operator-5ff5c45d49-w5prp                               1/1     Running     0          2m26s
rook-ceph-osd-0-57ffd8f6d5-69scr                                  1/1     Running     0          35s
rook-ceph-osd-1-5cd6844466-g2rmx                                  0/1     Pending     0          14s
rook-ceph-osd-prepare-set1-data-0-x9cts-46hgc                     0/1     Completed   0          59s
rook-ceph-osd-prepare-set1-data-1-dl7l7-jmvmh                     0/1     Completed   0          58s
rook-ceph-osd-prepare-set1-data-2-sqsvk-vl9mh                     0/1     Pending     0          58s
rook-discover-2nxvd                                               1/1     Running     0          8h

残念ながら

affinity が効いているため、同じノードでは 1つの OSD しか動きません。
そのため、現在の自構成では、 ceph を動かすことができません。。。

affinity を解除するか、ノードを追加するかして、
ceph を動かせたらパフォーマンスを見たり色々やろうかなと思います。

追記 2020/07/12

Twitter で記事を共有したら、色々な方から反応をいただいて、
1 node で 3 OSD を動かす方法についてアドバイスをもらいました。

affinity の他に、TopologySpreadConstraints という概念を突破する必要がありました。

TopologySpreadConstraints とは

Pod をドメインごとに分散配置するための仕組みです。
https://kubernetes.io/docs/concepts/workloads/pods/pod-topology-spread-constraints/

実装の経緯や issue, PR などは、サイボウズさんの記事に記載があります。
https://blog.cybozu.io/entry/2019/12/03/114746

@tenzen さんのスライドに Rook/Ceph で利用可能な障害ドメインの一覧があり、
このドメインごとに許容差を指定することで、ドメインごとに偏りなく Pod を配置できます。

https://speakerdeck.com/y_iwai/ceph?slide=27

ドメインを見ると、rack, pdu, datacenter, zone, region など、
なるほど分けたくなるな、というドメインが列挙されています。

数の指定を、絶対数ではなく MaxSkew: 許容するドメインごとの個数差としたことにより、
Pod の数が増えたとしても kube-scheduler が配置済みの Pod の配置を考慮して、
適切なドメインにスケジューリングしてくれるところがスケーラブルで良い仕組みだなと感じました。

TopologySpreadConstraints の挙動

今回、ドメインは最小の kubernetes.io/hostname とします。
デフォルトの設定では、以下のように指定してありました。

        topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - rook-ceph-osd
              - rook-ceph-osd-prepare

これは、hostname ラベルを持つノードごとの個数差を1とする設定です。
OSD を配置できるノードは node01 しか持ち合わせていません。
Master は Taint で原則 Pod が配置されないようにしてあります。

% kubectl get node
NAME                                          STATUS   ROLES    AGE     VERSION
2dff6fc0.lab.takutakahashi.dev                Ready    master   7d23h   v1.18.4+k3s1
dd03570a.lab.takutakahashi.dev                Ready    master   7d23h   v1.18.4+k3s1
f903c77f.lab.takutakahashi.dev                Ready    master   7d23h   v1.18.4+k3s1
node01-064eb176-e5a0-486e-9aaa-511f105151d9   Ready    <none>   7d23h   v1.18.4+k3s1

そのため、まずスケジューラは node01 に OSD Pod を配置します。
この時点で、node01 と他のノードの個数差: Skew は1となります。(1-0)

再び、スケジューラは OSD Pod を配置するノードを探します。

MaxSkew が 1 に設定してあるため、2つ目の Pod を node01 に配置することはできません。
そのため 、Pod はスケジューリングする場所が存在せずに、Pending となります。

今回は、合計で3つの OSD Pod を同一のノードに起動する必要があります。
以下のように設定を施しました。

        topologySpreadConstraints:
        - maxSkew: 3
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - rook-ceph-osd
              - rook-ceph-osd-prepare

こうすることで、他のノードで起動する Pod が0個でも、最大3つの個数差を許容できます。(3-0)
無事、1つのノードに3つの Pod を起動することができるようになりました。

課題

トポロジを設定する方法は理解できました。
今度は、ワークロードに最適なトポロジはどんなものなのか?というのを設計する必要があります。
Ceph に関しては、OSD の冗長をどのように取るべきなのか、許容するダウン数はいくつで、
同じクラスタの OSD を同一ノードにいくつまで載せて良いのか?というのをコストとにらめっこしながら決める必要がありそうです。
そのためには、Ceph についてよく知る必要がありますね。これは課題です。

追記まとめ

自宅トポロジを作りたくなりました。
電源、部屋、実家でトポロジを作ろうかな。