Rook-Ceph OSD on PVC(後半)
この記事はRookだらけの Advent Calendar 2019 7日目の記事です。
昨日はOSD on PVCのCephクラスタをデプロイできたのはいいものの、OSDがうまく散らばってくれないという問題に直面しました。
しかも織り込み済みの問題だという。今回はこの問題を回避する方法の一例を紹介します。
そういうわけで、今の時点ではマニュアルで3nodeの散らばるように書く必要がありそうです。
3nodeに散らばるようにする方法
Kubernetesにまかせてカッコよくosdを散らすのは難しそうなので、マニュアルで散らせます。
色々失敗した検討した結果、こんな感じでNode Affinityを使ったら3osdずつ散らばりました。
[utubo@tutsunom ceph]$ cat my-cluster-on-pvc.yaml apiVersion: ceph.rook.io/v1 kind: CephCluster metadata: name: rook-ceph namespace: rook-ceph spec: dataDirHostPath: /var/lib/rook mon: count: 3 allowMultiplePerNode: false volumeClaimTemplate: spec: storageClassName: my-gp2 resources: requests: storage: 10Gi cephVersion: image: ceph/ceph:v14.2.4-20190917 allowUnsupported: false dashboard: enabled: true ssl: true network: hostNetwork: false storage: topologyAware: true storageClassDeviceSets: - name: set-1a count: 3 portable: true placement: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: failure-domain.beta.kubernetes.io/zone operator: In values: - us-east-1a resources: volumeClaimTemplates: - metadata: name: data spec: resources: requests: storage: 10Gi storageClassName: my-gp2 volumeMode: Block accessModes: - ReadWriteOnce - name: set-1b count: 3 portable: true placement: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: failure-domain.beta.kubernetes.io/zone operator: In values: - us-east-1b resources: volumeClaimTemplates: - metadata: name: data spec: resources: requests: storage: 10Gi storageClassName: my-gp2 volumeMode: Block accessModes: - ReadWriteOnce - name: set-1c count: 3 portable: true placement: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: failure-domain.beta.kubernetes.io/zone operator: In values: - us-east-1c resources: volumeClaimTemplates: - metadata: name: data spec: resources: requests: storage: 10Gi storageClassName: my-gp2 volumeMode: Block accessModes: - ReadWriteOnce disruptionManagement: managePodBudgets: false osdMaintenanceTimeout: 30 manageMachineDisruptionBudgets: false machineDisruptionBudgetNamespace: openshift-machine-api
どうですか。ダサい、ダサすぎる。と風が語りかけてくるようです。
恥ずかしい思いをグッと我慢して解説すると、storageClassDeviceSets:
はOSDのセットを指定します。
サンプルのyamlだと、9個のOSDを1つのセットにするからPod AntiAffinity ruleでうまく分けろって言っているのです。
しかし、9個の唐揚げを1枚の皿に乗せて3人の子供達に仲良く分けて食えと言ってもうまくいかないように、(今のルールだと)意図するように分けるのはなかなか難しい。
そこで3個のOSDのセットを3つ用意してそれぞれのzoneにあるworker nodeに分けさせるruleにしてるのが、上のyamlです。
3枚の皿に3個ずつ唐揚げを乗せて1皿ずつ取れって形にしたら話早いやん、という感じです。
めっちゃhard codedなんで結局これかい感満々ですが、これでもう一回クラスタをデプロイしてみましょう。
[utubo@tutsunom ceph]$ kubectl create -f my-cluster-on-pvc.yaml [utubo@tutsunom ceph]$ kubectl -n rook-ceph get pod NAME READY STATUS RESTARTS AGE csi-cephfsplugin-5cp9g 3/3 Running 0 5m44s csi-cephfsplugin-fhr5b 3/3 Running 0 5m44s csi-cephfsplugin-l6556 3/3 Running 0 5m44s csi-cephfsplugin-provisioner-974b566d9-frwz6 4/4 Running 0 5m44s csi-cephfsplugin-provisioner-974b566d9-tqswk 4/4 Running 0 5m44s csi-rbdplugin-4qxgj 3/3 Running 0 5m44s csi-rbdplugin-crc8t 3/3 Running 0 5m44s csi-rbdplugin-h5dkw 3/3 Running 0 5m44s csi-rbdplugin-provisioner-579c546f5-sfx72 5/5 Running 0 5m44s csi-rbdplugin-provisioner-579c546f5-z7rmk 5/5 Running 0 5m44s rook-ceph-mgr-a-7cf896f748-m7czk 1/1 Running 0 3m37s rook-ceph-mon-a-568dc96b5f-xl9x4 1/1 Running 0 4m49s rook-ceph-mon-b-5cbc9dcd9d-gxnjp 1/1 Running 0 4m32s rook-ceph-mon-c-647db6d4b5-wmkzg 1/1 Running 0 4m1s rook-ceph-operator-fb8b96548-n9jv6 1/1 Running 0 5m52s rook-ceph-osd-0-8cbc88874-k6dlj 1/1 Running 0 2m2s rook-ceph-osd-1-5b746b478f-fzs8z 1/1 Running 0 2m3s rook-ceph-osd-2-64c746c558-mwzn2 1/1 Running 0 2m7s rook-ceph-osd-3-86f8984495-b7b7n 1/1 Running 0 2m5s rook-ceph-osd-4-67889676b9-wjswz 1/1 Running 0 2m2s rook-ceph-osd-5-687847c9f-6z7ps 1/1 Running 0 2m3s rook-ceph-osd-6-f8997d5b7-bmblb 1/1 Running 0 114s rook-ceph-osd-7-65dc45747c-fckw7 1/1 Running 0 111s rook-ceph-osd-8-76bf67bcf8-s9vmm 1/1 Running 0 114s rook-ceph-osd-prepare-set-1a-0-data-kx7k4-n4wrn 0/1 Completed 0 3m10s rook-ceph-osd-prepare-set-1a-1-data-kdwjp-n4fm9 0/1 Completed 0 3m9s rook-ceph-osd-prepare-set-1a-2-data-j5fm6-7vjwg 0/1 Completed 0 3m8s rook-ceph-osd-prepare-set-1b-0-data-tsv5x-mcx9q 0/1 Completed 0 3m8s rook-ceph-osd-prepare-set-1b-1-data-gdpqg-9knxk 0/1 Completed 0 3m7s rook-ceph-osd-prepare-set-1b-2-data-jfwcn-d67gp 0/1 Completed 0 3m7s rook-ceph-osd-prepare-set-1c-0-data-9tcl4-87946 0/1 Completed 0 3m6s rook-ceph-osd-prepare-set-1c-1-data-h4wk7-c9d76 0/1 Completed 0 3m5s rook-ceph-osd-prepare-set-1c-2-data-svq64-9sdzb 0/1 Completed 0 3m5s rook-discover-fmprp 1/1 Running 0 5m51s rook-discover-m895f 1/1 Running 0 5m51s rook-discover-wnpj7 1/1 Running 0 5m51s
[utubo@tutsunom ceph]$ kubectl -n rook-ceph get pod -l app=rook-ceph-mon -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES rook-ceph-mon-a-568dc96b5f-xl9x4 1/1 Running 0 5m19s 100.96.6.111 ip-172-20-93-28.ec2.internal <none> <none> rook-ceph-mon-b-5cbc9dcd9d-gxnjp 1/1 Running 0 5m2s 100.96.7.178 ip-172-20-42-193.ec2.internal <none> <none> rook-ceph-mon-c-647db6d4b5-wmkzg 1/1 Running 0 4m31s 100.96.8.99 ip-172-20-102-19.ec2.internal <none> <none> [utubo@tutsunom ceph]$ kubectl -n rook-ceph get pod -l app=rook-ceph-osd -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES rook-ceph-osd-0-8cbc88874-k6dlj 1/1 Running 0 2m37s 100.96.6.118 ip-172-20-93-28.ec2.internal <none> <none> rook-ceph-osd-1-5b746b478f-fzs8z 1/1 Running 0 2m38s 100.96.8.104 ip-172-20-102-19.ec2.internal <none> <none> rook-ceph-osd-2-64c746c558-mwzn2 1/1 Running 0 2m42s 100.96.8.103 ip-172-20-102-19.ec2.internal <none> <none> rook-ceph-osd-3-86f8984495-b7b7n 1/1 Running 0 2m40s 100.96.6.116 ip-172-20-93-28.ec2.internal <none> <none> rook-ceph-osd-4-67889676b9-wjswz 1/1 Running 0 2m37s 100.96.6.117 ip-172-20-93-28.ec2.internal <none> <none> rook-ceph-osd-5-687847c9f-6z7ps 1/1 Running 0 2m38s 100.96.7.182 ip-172-20-42-193.ec2.internal <none> <none> rook-ceph-osd-6-f8997d5b7-bmblb 1/1 Running 0 2m29s 100.96.8.105 ip-172-20-102-19.ec2.internal <none> <none> rook-ceph-osd-7-65dc45747c-fckw7 1/1 Running 0 2m26s 100.96.7.184 ip-172-20-42-193.ec2.internal <none> <none> rook-ceph-osd-8-76bf67bcf8-s9vmm 1/1 Running 0 2m29s 100.96.7.183 ip-172-20-42-193.ec2.internal <none> <none>
お、3個ずつ散らばってますね。(そらそうやろ
Cephの観点で見た注意点
ここでちょっとCephクラスタを見てみましょう。
[utubo@tutsunom ceph]$ kubectl create -f toolbox.yaml [utubo@tutsunom ceph]$ kubectl -n rook-ceph exec -it `kubectl get pod -l app=rook-ceph-tools -o 'jsonpath={.items[].metadata.name}'` ceph osd tree ID CLASS WEIGHT TYPE NAME STATUS REWEIGHT PRI-AFF -1 0.07910 root default -5 0.07910 region us-east-1 -14 0.02637 zone us-east-1a -13 0.00879 host set-1a-0-data-kx7k4 5 ssd 0.00879 osd.5 up 1.00000 1.00000 -27 0.00879 host set-1a-1-data-kdwjp 7 ssd 0.00879 osd.7 up 1.00000 1.00000 -23 0.00879 host set-1a-2-data-j5fm6 8 ssd 0.00879 osd.8 up 1.00000 1.00000 -10 0.02637 zone us-east-1b -9 0.00879 host set-1b-0-data-tsv5x 3 ssd 0.00879 osd.3 up 1.00000 1.00000 -17 0.00879 host set-1b-1-data-gdpqg 0 ssd 0.00879 osd.0 up 1.00000 1.00000 -19 0.00879 host set-1b-2-data-jfwcn 4 ssd 0.00879 osd.4 up 1.00000 1.00000 -4 0.02637 zone us-east-1c -3 0.00879 host set-1c-0-data-9tcl4 2 ssd 0.00879 osd.2 up 1.00000 1.00000 -25 0.00879 host set-1c-1-data-h4wk7 6 ssd 0.00879 osd.6 up 1.00000 1.00000 -21 0.00879 host set-1c-2-data-svq64 1 ssd 0.00879 osd.1 up 1.00000 1.00000
なんか3つずつに分かれて良さそうに見えますね。しかしこのトポロジーではCephのデフォルトのCRUSH ruleを使う場合に落とし穴があります。
各zoneの中に3つのhostがありますね。この3つのhostは実際は同一のworker nodeなんですが、Cephとしては異なるhostが3つあるトポロジーとして捉えます。そして各hostが1つosdを持ちます。
デフォルトのreplicated_ruleは「regionとかzoneとか無視して、とにかく1つhostを選んでその中のosdを1つ選ぶ」というルールになっています。
[utubo@tutsunom ceph]$ kubectl -n rook-ceph exec -it `kubectl get pod -l app=rook-ceph-tools -o 'jsonpath={.items[].metadata.name}'` sh sh-4.2# ceph osd getcrushmap -o crushmap.bin sh-4.2# crushtool -d crushmap.bin -o crushmap.txt sh-4.2# grep -A 10 rules crushmap.txt # rules rule replicated_rule { id 0 type replicated min_size 1 max_size 10 step take default step chooseleaf firstn 0 type host step emit }
つまり、上記のようなトポロジーのCephクラスタでreplicated_ruleを使って3x Replicated Poolを作ると、同一のzoneから3つのOSDを選んだPGが作られる可能性があります。
実際やって見てみましょう。面倒なんでCephで直接コマンド打ってPoolを作ります。すんません。
[utubo@tutsunom ceph]$ kubectl -n rook-ceph exec -it `kubectl get pod -l app=rook-ceph-tools -o 'jsonpath={.items[].metadata.name}'` ceph osd pool create test 256 256 pool 'test' created [utubo@tutsunom ceph]$ kubectl -n rook-ceph exec -it `kubectl get pod -l app=rook-ceph-tools -o 'jsonpath={.items[].metadata.name}'` ceph pg ls-by-pool test PG OBJECTS DEGRADED MISPLACED UNFOUND BYTES OMAP_BYTES* OMAP_KEYS* LOG STATE SINCE VERSION REPORTED UP ACTING SCRUB_STAMP DEEP_SCRUB_STAMP 1.0 0 0 0 0 0 0 0 0 active+clean 26s 0'0 26:10 [0,1,2]p0 [0,1,2]p0 2019-12-05 03:57:01.395376 2019-12-05 03:57:01.395376 1.1 0 0 0 0 0 0 0 0 active+clean 26s 0'0 26:10 [5,2,0]p5 [5,2,0]p5 2019-12-05 03:57:01.395376 2019-12-05 03:57:01.395376 1.2 0 0 0 0 0 0 0 0 active+clean 26s 0'0 26:10 [4,1,2]p4 [4,1,2]p4 2019-12-05 03:57:01.395376 2019-12-05 03:57:01.395376 1.3 0 0 0 0 0 0 0 0 active+clean 26s 0'0 26:10 [7,2,4]p7 [7,2,4]p7 2019-12-05 03:57:01.395376 2019-12-05 03:57:01.395376 1.4 0 0 0 0 0 0 0 0 active+clean 26s 0'0 26:10 [7,4,2]p7 [7,4,2]p7 2019-12-05 03:57:01.395376 2019-12-05 03:57:01.395376 1.5 0 0 0 0 0 0 0 0 active+clean 26s 0'0 26:10 [8,1,4]p8 [8,1,4]p8 2019-12-05 03:57:01.395376 2019-12-05 03:57:01.395376 ... 1.34 0 0 0 0 0 0 0 0 active+clean 26s 0'0 26:10 [6,2,1]p6 [6,2,1]p6 2019-12-05 03:57:01.395376 2019-12-05 03:57:01.395376 ... 1.97 0 0 0 0 0 0 0 0 active+clean 26s 0'0 26:10 [5,7,8]p5 [5,7,8]p5 2019-12-05 03:57:01.395376 2019-12-05 03:57:01.395376 ... 1.c2 0 0 0 0 0 0 0 0 active+clean 26s 0'0 26:10 [0,3,4]p0 [0,3,4]p0 2019-12-05 03:57:01.395376 2019-12-05 03:57:01.395376 ...
ceph osd pool create
コマンドでruleを指定せずにpoolを作るとデフォルトのreplicated_ruleが使われるのですが、どうでしょう。
1.34
に割り当てられたOSD[6,2,1]
は、ceph osd tree
を見ると3つともzone us-east-1c
から取られています。が、実際は1つのworker nodeにattachされてるOSDです。1.97
も1.c2
も同様です。
つまりworker nodeに障害が起きるとこれらのPGは完全にアウトとなり、データロスとなります。全然冗長化されてないpoolなので、これはアカン。
だから何かしら対応しなくてはいけません。
トポロジーを変えるのはRook-Ceph側の扱いになって難しそうなので、適切なCRUSH ruleを作って指定する方がよいでしょう。例えばこんな感じのruleを作ったらいけるはずです。
# rules rule spread_across_all_zones { ruleset 1 type replicated min_size 1 max_size 10 step take default step choose firstn 0 type zone step chooseleaf firstn 1 type host step emit }
まとめ
前後半にわたって、OSD on PVCについて説明しました。worker nodeにブロックデバイスをぶら下げる作業がしなくていいのでKubernetes側でデバイス管理でき、Cloud Native Infra感出ますよね。本当に便利だと思います。
でも個人的にOSD on PVCで一番うれしいのは、Cephクラスタを作り直すたびにworker nodeに入ってMONが使うCephの構成ファイル(/var/lib/rook
以下)をいちいち消さなくてよかったり、OSDになるデバイスをきれいに消してLVMのエントリーを消したりしなくていいところだったりします。クラスタ消したら全部自動的にPVCも消えてきれいさっぱり無くなるからです。本番のKubernetesクラスタでは全然関係ないけれど、テストする上ではスムーズにクラスタのdelete/createが繰り返しができるので、ホントーーーーにありがたい。
OSDをきれいに散らす方法は、Kubernetes一年生のうつぼではNode Affinityを使ったダサい方法しか思いつかなかったけど、やっぱりPod (Anti)Affinityをうまく使ったやりかたがあるんでしょうね。それかPod Topology Spread Constraintsを待つんでしょう。
こちらのissueを見ると、Rook 1.2にはPod Topology Spread Constraintsに対応するとのことです。
Pod Topology Spread Constraintsについてはsatさんが追究されています。やはり今日本で一番のRook-Cepherだと思います。最敬礼。
というわけで、今回はおしまい。
BGMは境界の彼方でした。