Rook Ceph を ZFS で動かす
ZFS が好きなんですが、Kubernetes との組み合わせを見ると、
クラウドネイティブな仕組みでいい感じにマルチノード冗長してくれる仕組みがありません。
そのへんは Rook が人気っぽいので、Rook を利用して Ceph を動かしてみました。
ZFS 上にファイル保存領域を作成し、 OSD に 割り当てる方式としました。
免責
僕はストレージに全く詳しくないので、動いた、以上のものが書けません。
徐々に学習を進めようと思っておりますが。。。
方式
Rook のストレージ割当方式は、数種類あるみたいです。
- ディレクトリを指定する方式 (非推奨?)
- ブロックデバイスを直接割り当てる方式
- 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 についてよく知る必要がありますね。これは課題です。
追記まとめ
自宅トポロジを作りたくなりました。
電源、部屋、実家でトポロジを作ろうかな。