ここは今からストレージです。

Cephクラスタのupgrade

f:id:ututaq:20191125022735p:plain

この記事はRookだらけの Advent Calendar 2019 8日目の記事です。

今日は超軽くRook-Cephで作ったCephクラスタのupgradeを説明します。

Cephのupgrade

Cephは基本的にコンポーネント冗長化されて動いているので、1つずつrolling upgradeすることでストレージ的にはNon-disruptiveなupgradeができます。
Rook-Cephではoperatorがその辺りを自動的にやってくれるので、人がやることは超少ないです。というわけでやってみましょう。

いつもどおり3master+3workerで各workerにそれぞれ2つOSDをぶら下げたCephクラスタを作ります。

[utubo@tutsunom ceph]$ kubectl get node --sort-by=".metadata.creationTimestamp"
NAME                             STATUS   ROLES    AGE   VERSION
ip-172-20-110-218.ec2.internal   Ready    master   38h   v1.15.5
ip-172-20-58-245.ec2.internal    Ready    master   38h   v1.15.5
ip-172-20-68-55.ec2.internal     Ready    master   38h   v1.15.5
ip-172-20-125-18.ec2.internal    Ready    node     38h   v1.15.5
ip-172-20-60-20.ec2.internal     Ready    node     12h   v1.15.5
ip-172-20-81-195.ec2.internal    Ready    node     12h   v1.15.5
[utubo@tutsunom ceph]$ kubectl -n rook-ceph get pod
NAME                                                        READY   STATUS      RESTARTS   AGE
csi-cephfsplugin-provisioner-974b566d9-8svhf                4/4     Running     0          6h21m
csi-cephfsplugin-provisioner-974b566d9-hxfx2                4/4     Running     0          6h21m
csi-cephfsplugin-q7m66                                      3/3     Running     0          6h21m
csi-cephfsplugin-r58wx                                      3/3     Running     0          6h21m
csi-cephfsplugin-w4pfg                                      3/3     Running     0          6h21m
csi-rbdplugin-fhbqj                                         3/3     Running     0          6h21m
csi-rbdplugin-jf5c9                                         3/3     Running     0          6h21m
csi-rbdplugin-provisioner-579c546f5-9nh7z                   5/5     Running     0          6h21m
csi-rbdplugin-provisioner-579c546f5-pvf76                   5/5     Running     0          6h21m
csi-rbdplugin-v9p2k                                         3/3     Running     0          6h21m
rook-ceph-mgr-a-649dcd8f8-z4bf9                             1/1     Running     0          6h14m
rook-ceph-mon-a-fb9dc8bf7-7tpfh                             1/1     Running     0          6h14m
rook-ceph-mon-b-7cbdf8b8bf-q8m89                            1/1     Running     0          6h15m
rook-ceph-mon-c-764f6b7c48-crlhc                            1/1     Running     0          6h15m
rook-ceph-operator-fb8b96548-cjzsp                          1/1     Running     0          6h21m
rook-ceph-osd-0-54cc869884-s7lx7                            1/1     Running     0          6h13m
rook-ceph-osd-1-69bb658d69-2x72t                            1/1     Running     0          6h12m
rook-ceph-osd-2-6886955f66-xxmjk                            1/1     Running     0          6h13m
rook-ceph-osd-3-c9fd5bdb6-l7n6r                             1/1     Running     0          6h13m
rook-ceph-osd-4-6fdc7dbb45-vvmqb                            1/1     Running     0          6h12m
rook-ceph-osd-5-66cbbf745-f6dwx                             1/1     Running     0          6h13m
rook-ceph-osd-prepare-ip-172-20-125-18.ec2.internal-h2rxc   0/1     Completed   0          6h14m
rook-ceph-osd-prepare-ip-172-20-60-20.ec2.internal-fm8h5    0/1     Completed   0          6h14m
rook-ceph-osd-prepare-ip-172-20-81-195.ec2.internal-rd9zk   0/1     Completed   0          6h14m
rook-ceph-tools-5bc668d889-44wq2                            1/1     Running     0          6h18m
rook-discover-dmgtp                                         1/1     Running     0          6h21m
rook-discover-p94l5                                         1/1     Running     0          6h21m
rook-discover-x97hw                                         1/1     Running     0          6h21m
[utubo@tutsunom ceph]$ kubectl exec -it `kubectl get pod -l app=rook-ceph-tools -o 'jsonpath={.items[].metadata.name}'` ceph version
ceph version 14.2.2 (4f8fa0a0024755aae7d95567c63f11d6862d55be) nautilus (stable)

はいはい。

それではこのCephクラスタを14.2.2から14.2.4にupgradeします。やることは一つです。CephClusterリソースのimageのタグを書き換えるだけ。

[utubo@tutsunom ceph]$ kubectl -n rook-ceph patch CephCluster rook-ceph --type=merge -p "{\"spec\": {\"cephVersion\": {\"image\": \"ceph/ceph:v14.2.4-20191204\"}}}"

これだけで後はRook-Ceph Operatorが全部やってくれます。その様子を追っかけてみましょう。
upgrade中、別窓で↓のようにDeploymentの"READY/UP-TO-DATE/AVAILABLE"とceph-versionをwatchで見ておくとどうなるか。 ※出力が長くなるので注目する部分だけ書きます。

[utubo@tutsunom ceph]$ watch --exec kubectl -n rook-ceph get deploy -l rook_cluster=rook-ceph -o jsonpath='{range .items[*]}{.metadata.name}{"  \treq/upd/avl: "}{.spec.replicas}{"/"}{.status.updatedReplicas}{"/"}{.status.readyReplicas}{"  \tceph-version="}{.metadata.labels.ceph-version}{"\n"}{end}'
rook-ceph-mgr-a         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-mon-a         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-mon-b         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-mon-c         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-0         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-1         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-2         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-3         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-4         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-5         req/upd/avl: 1/1/1  ceph-version=14.2.2

↓

rook-ceph-mon-a         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-mon-b         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-mon-c         req/upd/avl: 1//    ceph-version=14.2.4

↓

rook-ceph-mon-a         req/upd/avl: 1/1/   ceph-version=14.2.4
rook-ceph-mon-b         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-mon-c         req/upd/avl: 1/1/1  ceph-version=14.2.4

↓

rook-ceph-mon-a         req/upd/avl: 1/1/1  ceph-version=14.2.4
rook-ceph-mon-b         req/upd/avl: 1/1/   ceph-version=14.2.4
rook-ceph-mon-c         req/upd/avl: 1/1/1  ceph-version=14.2.4

まず初めにMONがupgradeされ。今回はmon-c -> mon-a -> mon-bの順序でしたが、別の機会はまた違った順序だったので特に順番は決まって居ないようです。1つずつupgradeするのは間違いないです。

続き、

↓

rook-ceph-mgr-a         req/upd/avl: 1//    ceph-version=14.2.4

↓

rook-ceph-mgr-a         req/upd/avl: 1/1/1  ceph-version=14.2.4

次にMGRがupgrade。続いて、

rook-ceph-osd-0         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-1         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-2         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-3         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-4         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-5         req/upd/avl: 1/1/   ceph-version=14.2.4

↓

rook-ceph-osd-0         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-1         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-2         req/upd/avl: 1//    ceph-version=14.2.4
rook-ceph-osd-3         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-4         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-5         req/upd/avl: 1/1/1  ceph-version=14.2.4

↓

rook-ceph-osd-0         req/upd/avl: 1/1/   ceph-version=14.2.4
rook-ceph-osd-1         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-2         req/upd/avl: 1/1/1  ceph-version=14.2.4
rook-ceph-osd-3         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-4         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-5         req/upd/avl: 1/1/1  ceph-version=14.2.4

↓

rook-ceph-osd-0         req/upd/avl: 1/1/1  ceph-version=14.2.4
rook-ceph-osd-1         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-2         req/upd/avl: 1/1/1  ceph-version=14.2.4
rook-ceph-osd-3         req/upd/avl: 1//    ceph-version=14.2.4
rook-ceph-osd-4         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-5         req/upd/avl: 1/1/1  ceph-version=14.2.4

↓

rook-ceph-osd-0         req/upd/avl: 1/1/1  ceph-version=14.2.4
rook-ceph-osd-1         req/upd/avl: 1/1/   ceph-version=14.2.4
rook-ceph-osd-2         req/upd/avl: 1/1/1  ceph-version=14.2.4
rook-ceph-osd-3         req/upd/avl: 1/1/1  ceph-version=14.2.4
rook-ceph-osd-4         req/upd/avl: 1/1/1  ceph-version=14.2.2
rook-ceph-osd-5         req/upd/avl: 1/1/1  ceph-version=14.2.4

↓

rook-ceph-osd-0         req/upd/avl: 1/1/1  ceph-version=14.2.4
rook-ceph-osd-1         req/upd/avl: 1/1/1  ceph-version=14.2.4
rook-ceph-osd-2         req/upd/avl: 1/1/1  ceph-version=14.2.4
rook-ceph-osd-3         req/upd/avl: 1/1/1  ceph-version=14.2.4
rook-ceph-osd-4         req/upd/avl: 1/1/1  ceph-version=14.2.4
rook-ceph-osd-5         req/upd/avl: 1/1/1  ceph-version=14.2.4

という感じで最後にOSDがupgradeされます。

ここではosdが5,2,0,3,1,4という順序でupgradeされました。Cephのosd treeを見てみるとworker nodeごとにupgradeをかけていることがわかります。

[utubo@tutsunom ceph]$ kubectl 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.16974 root default                                                           
 -5       0.16974     region us-east-1                                                   
 -4       0.05658         zone us-east-1a                                                
 -3       0.05658             host ip-172-20-60-20-ec2-internal                          
  0   ssd 0.02829                 osd.0                              up  1.00000 1.00000 
  3   ssd 0.02829                 osd.3                              up  1.00000 1.00000 
-10       0.05658         zone us-east-1b                                                
 -9       0.05658             host ip-172-20-81-195-ec2-internal                         
  1   ssd 0.02829                 osd.1                              up  1.00000 1.00000 
  4   ssd 0.02829                 osd.4                              up  1.00000 1.00000 
-14       0.05658         zone us-east-1c                                                
-13       0.05658             host ip-172-20-125-18-ec2-internal                         
  2   ssd 0.02829                 osd.2                              up  1.00000 1.00000 
  5   ssd 0.02829                 osd.5                              up  1.00000 1.00000 

最後にCephクラスタでバージョンを確認すると、

[utubo@tutsunom ceph]$ kubectl exec -it `kubectl get pod -l app=rook-ceph-tools -o 'jsonpath={.items[].metadata.name}'` ceph version
ceph version 14.2.4 (75f4de193b3ea58512f204623e6c5a16e6c1e1ba) nautilus (stable)

はいおわり。

コマンド一個打つだけでMON -> MGR -> OSDとそれぞれ1つずつupgradeしてくれるのだからRook-Ceph operatorは偉大です。

まとめ

今日は軽くCephクラスタのupgradeについて説明しました。
今回は同一メジャーバージョン間のupgradeでしたが、メジャーバージョンをまたぐとOSDメタデータの持ち方とかが変わってデバイスをattachできないとか(特にBlueStoreの場合)があるかもしれないので注意が必要かもです(適当) ※13->14のupgradeにことごとく失敗して、原因がまだ調査できていない(汗)
あと、本当はRook-Ceph operator自身のupgradeも説明すべきですが、時間がなくて検証できていないので続きは赤帽エンジニアブログで書きます。

Rookは対応するSDSやDBのoperatorで、ストレージの構築から運用まで強力に手助けしてくれる革命的なオーケストレーションツールです。
みなさんぜひ試してみて下さいね。

それでは今日でRookだらけのAdvent Calendar 2019の私のパートは終わりです。8日連続でお付き合いいただきありがとうございました。
引き続きtzkobaさんとmakotowさんという豪華顔ぶれの説明をお楽しみください!

Rook-Ceph OSD on PVC(後半)

f:id:ututaq:20191125022735p:plain

この記事は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

ここまではええねん。MONとOSDは?

[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.971.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だと思います。最敬礼。

blog.cybozu.io

というわけで、今回はおしまい。

BGMは境界の彼方でした。

Rook-Ceph OSD on PVC(前半)

f:id:ututaq:20191125022735p:plain

この記事はRookだらけの Advent Calendar 2019 6日目の記事です。

Rook 1.1でできるようになった OSD on PVC の説明をします。

OSD on PVC ???

OSD on PVCというのは、ceph-osdをPVC から作ろうということです。

CephはブロックデバイスからOSDを作成するため、言うまでもなくceph-osdにはブロックデバイスが必要です。
osdのブロックデバイスはrook-ceph-osd Podが稼働するworker nodeから取ってくるので、事前に何らかの方法でworker nodeにブロックデバイスをattachしておかなくてはなりません。 しかしこのworker nodeにブロックデバイスをattachする作業は、Kubernetesが乗るプラットフォーム側の操作です。Kubernetesで何かしらやりようはあると思いますが、基本的にはKubernetes外の管理操作だと思います。
せっかくRookでKubernetes内で完結できるストレージシステムが作れるというのに、根本にある保存媒体はKubernetes外でmanageされる。それってなんかイケてないですよね。

そういうわけで、 「既存のPV/PVCの仕組みでceph-osdのPodにブロックデバイスを直でattachしようや、そしたら何もないところから自動でOSDが作れるやろ」 を実現するのがOSD on PVCです。
この考えの元にあるのがKubernetes 1.11からでてきたRaw Block Volumeのfeatureです。(1.14でbetaながらデフォルトでenableになっているfeature) この技術を使ってPVをブロックデバイスとして見せることができるようになりました。

https://kubernetes.io/docs/concepts/storage/volumes/#csi-raw-block-volume-support

これってまるでCloud(Container) Native Storageのためにあるようなfeature。ええやん、ステキやん。

実際にやってみる

OSDはベースのPVC-baseのブロックデバイスにするとし、MONの構成ファイルをどうするかという選択肢もあります。
これまでMONの構成ファイルはworker nodeの/var/lib/rookなどyamlで記載されたディレクトリに置かれていたのですが、これもPVC-baseのファイルPVに置くというパターンが選べます。せっかくだからこっちでやってみましょう。

前回までと同じように3master+3workerのクラスタです。

[utubo@tutsunom ceph]$ kubectl get node --sort-by=".metadata.creationTimestamp" --show-labels
NAME                            STATUS   ROLES    AGE   VERSION   LABELS
ip-172-20-91-64.ec2.internal    Ready    master   25h   v1.15.5   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=t2.medium,beta.kubernetes.io/os=linux,failure-domain.beta.kubernetes.io/region=us-east-1,failure-domain.beta.kubernetes.io/zone=us-east-1b,kops.k8s.io/instancegroup=master-us-east-1b,kubernetes.io/arch=amd64,kubernetes.io/hostname=ip-172-20-74-29.ec2.internal,kubernetes.io/os=linux,kubernetes.io/role=master,node-role.kubernetes.io/master=
ip-172-20-53-29.ec2.internal    Ready    master   25h   v1.15.5   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=t2.medium,beta.kubernetes.io/os=linux,failure-domain.beta.kubernetes.io/region=us-east-1,failure-domain.beta.kubernetes.io/zone=us-east-1a,kops.k8s.io/instancegroup=master-us-east-1a,kubernetes.io/arch=amd64,kubernetes.io/hostname=ip-172-20-63-169.ec2.internal,kubernetes.io/os=linux,kubernetes.io/role=master,node-role.kubernetes.io/master=
ip-172-20-113-40.ec2.internal   Ready    master   25h   v1.15.5   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=t2.medium,beta.kubernetes.io/os=linux,failure-domain.beta.kubernetes.io/region=us-east-1,failure-domain.beta.kubernetes.io/zone=us-east-1c,kops.k8s.io/instancegroup=master-us-east-1c,kubernetes.io/arch=amd64,kubernetes.io/hostname=ip-172-20-121-240.ec2.internal,kubernetes.io/os=linux,kubernetes.io/role=master,node-role.kubernetes.io/master=
ip-172-20-93-28.ec2.internal    Ready    node     25h   v1.15.5   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=t2.large,beta.kubernetes.io/os=linux,failure-domain.beta.kubernetes.io/region=us-east-1,failure-domain.beta.kubernetes.io/zone=us-east-1a,kops.k8s.io/instancegroup=nodes,kubernetes.io/arch=amd64,kubernetes.io/hostname=ip-172-20-52-182.ec2.internal,kubernetes.io/os=linux,kubernetes.io/role=node,node-role.kubernetes.io/node=
ip-172-20-42-193.ec2.internal   Ready    node     25h   v1.15.5   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=t2.large,beta.kubernetes.io/os=linux,failure-domain.beta.kubernetes.io/region=us-east-1,failure-domain.beta.kubernetes.io/zone=us-east-1c,kops.k8s.io/instancegroup=nodes,kubernetes.io/arch=amd64,kubernetes.io/hostname=ip-172-20-118-92.ec2.internal,kubernetes.io/os=linux,kubernetes.io/role=node,node-role.kubernetes.io/node=
ip-172-20-102-19.ec2.internal   Ready    node     25h   v1.15.5   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=t2.large,beta.kubernetes.io/os=linux,failure-domain.beta.kubernetes.io/region=us-east-1,failure-domain.beta.kubernetes.io/zone=us-east-1b,kops.k8s.io/instancegroup=nodes,kubernetes.io/arch=amd64,kubernetes.io/hostname=ip-172-20-74-57.ec2.internal,kubernetes.io/os=linux,kubernetes.io/role=node,node-role.kubernetes.io/node=

まず、ベースの元になるPV/PVCのStorageClassをチェックしましょう。
ベースのStorageClassがvolumeBindingMode: WaitForFirstConsumerで定義されていることが大事です。volumeBindingMode: Immediateだと動きません。ダメです。ここちょっとハマりました。

[utubo@tutsunom ceph]$ cat my-sc-gp2.yaml 
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: my-gp2
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: kubernetes.io/aws-ebs
volumeBindingMode: WaitForFirstConsumer
[utubo@tutsunom ceph]$ kubectl create -f my-sc-gp2.yaml 

それではgithubにあるサンプルのcluster-on-pvc.yamlから、StorageClassと(gp2 --> my-gp2)とosdの数(count:3 --> 9)だけ変更して、Cephクラスタを作ってみます。(長いのでコメント行は省略)
yamlの中のmon:storage:(=osd)にあるvolumeClaimTemplateがPVC-baseの証。

[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: set1
      count: 9
      portable: true
      placement:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - rook-ceph-osd
                - key: app
                  operator: In
                  values:
                  - rook-ceph-osd-prepare
              topologyKey: kubernetes.io/hostname
      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
[utubo@tutsunom ceph]$
[utubo@tutsunom ceph]$ kubectl create -f common.yaml
[utubo@tutsunom ceph]$ kubectl create -f operator.yaml
[utubo@tutsunom ceph]$ kubectl create -f my-cluster-on-pvc.yaml
[utubo@tutsunom ceph]$
[utubo@tutsunom ceph]$ kubectl -n rook-ceph get pod
NAME                                            READY   STATUS      RESTARTS   AGE
csi-cephfsplugin-jbs7g                          3/3     Running     0          4m53s
csi-cephfsplugin-lvtg9                          3/3     Running     0          4m53s
csi-cephfsplugin-provisioner-974b566d9-b24zs    4/4     Running     0          4m53s
csi-cephfsplugin-provisioner-974b566d9-wgx4l    4/4     Running     0          4m53s
csi-cephfsplugin-rpg6v                          3/3     Running     0          4m53s
csi-rbdplugin-4pvjd                             3/3     Running     0          4m53s
csi-rbdplugin-hw59q                             3/3     Running     0          4m53s
csi-rbdplugin-mrq2g                             3/3     Running     0          4m53s
csi-rbdplugin-provisioner-579c546f5-k4x78       5/5     Running     0          4m53s
csi-rbdplugin-provisioner-579c546f5-tlb8s       5/5     Running     0          4m53s
rook-ceph-mgr-a-8656964c64-d6rxd                1/1     Running     0          2m50s
rook-ceph-mon-a-5964fd74f7-fpqhl                1/1     Running     0          4m3s
rook-ceph-mon-b-858958c794-mwfzz                1/1     Running     0          3m36s
rook-ceph-mon-c-576b4d7687-94ts9                1/1     Running     0          3m14s
rook-ceph-operator-fb8b96548-f5dl5              1/1     Running     0          5m1s
rook-ceph-osd-0-66db6b5bbc-6v82x                1/1     Running     0          59s
rook-ceph-osd-1-7b76f55b96-bqknl                1/1     Running     0          49s
rook-ceph-osd-2-57f696464b-jlzjr                1/1     Running     0          74s
rook-ceph-osd-3-7b975f57c7-6snsr                1/1     Running     0          78s
rook-ceph-osd-4-5d485c8997-8fq7r                1/1     Running     0          75s
rook-ceph-osd-5-6f4f87db77-b52z6                1/1     Running     0          43s
rook-ceph-osd-6-6999896cdf-7d4mv                1/1     Running     0          26s
rook-ceph-osd-7-c66d49fcb-5lrwr                 1/1     Running     0          29s
rook-ceph-osd-8-6bdf79c57c-prnzq                1/1     Running     0          28s
rook-ceph-osd-prepare-set1-0-data-bhmrj-rjbnk   0/1     Completed   0          2m22s
rook-ceph-osd-prepare-set1-1-data-dcr5r-gcdl8   0/1     Completed   0          2m21s
rook-ceph-osd-prepare-set1-2-data-klbr2-bnlqv   0/1     Completed   0          2m21s
rook-ceph-osd-prepare-set1-3-data-7b2pk-7n6wl   0/1     Completed   0          2m21s
rook-ceph-osd-prepare-set1-4-data-249gz-b2z79   0/1     Completed   0          2m20s
rook-ceph-osd-prepare-set1-5-data-znf49-l7xbz   0/1     Completed   0          2m19s
rook-ceph-osd-prepare-set1-6-data-57bft-qtnwt   0/1     Completed   0          2m19s
rook-ceph-osd-prepare-set1-7-data-sm244-v546c   0/1     Completed   0          2m18s
rook-ceph-osd-prepare-set1-8-data-cd5hb-zt2gk   0/1     Completed   0          2m18s
rook-discover-5jzcr                             1/1     Running     0          5m
rook-discover-glvgr                             1/1     Running     0          5m
rook-discover-jh9pr                             1/1     Running     0          5m

おっ、イケるやん。worker nodeにあらかじめブロックデバイスをつけることなくCephクラスタが作れました。

[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-5964fd74f7-fpqhl   1/1     Running   0          4m56s   100.96.6.128   ip-172-20-93-28.ec2.internal    <none>           <none>
rook-ceph-mon-b-858958c794-mwfzz   1/1     Running   0          4m29s   100.96.7.198   ip-172-20-42-193.ec2.internal   <none>           <none>
rook-ceph-mon-c-576b4d7687-94ts9   1/1     Running   0          4m7s    100.96.8.117   ip-172-20-102-19.ec2.internal   <none>           <none>

MONはしっかり3ノードに分かれています。OSDは、

[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-66db6b5bbc-6v82x   1/1     Running   0          119s    100.96.6.136   ip-172-20-93-28.ec2.internal    <none>           <none>
rook-ceph-osd-1-7b76f55b96-bqknl   1/1     Running   0          109s    100.96.6.137   ip-172-20-93-28.ec2.internal    <none>           <none>
rook-ceph-osd-2-57f696464b-jlzjr   1/1     Running   0          2m14s   100.96.7.204   ip-172-20-42-193.ec2.internal   <none>           <none>
rook-ceph-osd-3-7b975f57c7-6snsr   1/1     Running   0          2m18s   100.96.7.202   ip-172-20-42-193.ec2.internal   <none>           <none>
rook-ceph-osd-4-5d485c8997-8fq7r   1/1     Running   0          2m15s   100.96.7.203   ip-172-20-42-193.ec2.internal   <none>           <none>
rook-ceph-osd-5-6f4f87db77-b52z6   1/1     Running   0          103s    100.96.6.138   ip-172-20-93-28.ec2.internal    <none>           <none>
rook-ceph-osd-6-6999896cdf-7d4mv   1/1     Running   0          86s     100.96.6.141   ip-172-20-93-28.ec2.internal    <none>           <none>
rook-ceph-osd-7-c66d49fcb-5lrwr    1/1     Running   0          89s     100.96.6.139   ip-172-20-93-28.ec2.internal    <none>           <none>
rook-ceph-osd-8-6bdf79c57c-prnzq   1/1     Running   0          88s     100.96.6.140   ip-172-20-93-28.ec2.internal    <none>           <none>

ええー!osd全然3ノードにバラけてないやん!!一応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                                       
 -4       0.02637         zone us-east-1a                                    
 -9       0.00879             host set1-0-data-bhmrj                         
  4   ssd 0.00879                 osd.4                  up  1.00000 1.00000 
-11       0.00879             host set1-2-data-klbr2                         
  2   ssd 0.00879                 osd.2                  up  1.00000 1.00000 
 -3       0.00879             host set1-6-data-57bft                         
  3   ssd 0.00879                 osd.3                  up  1.00000 1.00000 
-14       0.05273         zone us-east-1b                                    
-13       0.00879             host set1-1-data-dcr5r                         
  0   ssd 0.00879                 osd.0                  up  1.00000 1.00000 
-19       0.00879             host set1-3-data-7b2pk                         
  5   ssd 0.00879                 osd.5                  up  1.00000 1.00000 
-17       0.00879             host set1-4-data-249gz                         
  1   ssd 0.00879                 osd.1                  up  1.00000 1.00000 
-23       0.00879             host set1-5-data-znf49                         
  6   ssd 0.00879                 osd.6                  up  1.00000 1.00000 
-21       0.00879             host set1-7-data-sm244                         
  8   ssd 0.00879                 osd.8                  up  1.00000 1.00000 
-25       0.00879             host set1-8-data-cd5hb                         
  7   ssd 0.00879                 osd.7                  up  1.00000 1.00000 

(´・ω・`)
yamlosd Affinity ruleを見ると、

      placement:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - rook-ceph-osd
                - key: app
                  operator: In
                  values:
                  - rook-ceph-osd-prepare
              topologyKey: kubernetes.io/hostname

うーん…確かに3osdより多くなるとうまく散らばらなそうやけど3nodeにすら散らばらんのはどうなっとるんや…

実はコレ想定内の問題なんです。サンプルのcluster-on-pvc.yamlで使われているpodAntiAffinity ruleではうまく散らばらないのは織り込み済みです。yamlのコメントをよく読んでみると次のことが書かれています。

      # Since the OSDs could end up on any node, an effort needs to be made to spread the OSDs
      # across nodes as much as possible. Unfortunately the pod anti-affinity breaks down
      # as soon as you have more than one OSD per node. If you have more OSDs than nodes, K8s may
      # choose to schedule many of them on the same node. What we need is the Pod Topology
      # Spread Constraints, which is alpha in K8s 1.16. This means that a feature gate must be
      # enabled for this feature, and Rook also still needs to add support for this feature.
      # Another approach for a small number of OSDs is to create a separate device set for each
      # zone (or other set of nodes with a common label) so that the OSDs will end up on different

なるほど。要約すると、

  • いまのPod Anti-Affinityだと上手くosdをバラけさせられない。まだ努力が必要やわ。
  • Pod Topology Spread Constraints(k8s 1.16 alpha)を使えばピチッとバラけさせられるけど、Rookがまだ対応してないねん。これからの子やねん。
  • Node Affinityも使った組み合わせたらイケるかも。それでどないや?

ということです。そうか、この分野ではRookはまだまだこれからの子なんか。それならしゃーない。

そういうわけで、今の時点ではマニュアルで3nodeの散らばるように書く必要がありそうですね。

まとめ

今回はOSD on PVCのコンセプトの紹介をし、実際にやってみて問題に直面しました。
この悲しき現実に立ち向かう方法たるやいかに。次回バジリスク甲賀忍法帖…というノリはやめといて、次回はこの問題を回避する方法を紹介します。

BGMは甲賀忍法帖でした。(やっぱり)

Rook-CephでSSDとHDDを使い分ける

f:id:ututaq:20191125022735p:plain

この記事はRookだらけの Advent Calendar 2019 3日目の記事です。

Rook-CephでSSDとHDDの使い分けの例を説明します。

SSDOSDメタデータを分けて置く

Cephをご存知の方だと、『OSDのユーザーデータとメタデータ領域は分けたほうがいい』というプラクティスを知っていると思います。
メタデータはsmall I/Oがバンバン来るのでFlashバイスの方がいいということです。そのとおりで、特にsmall I/Oに強くないHDDにとっては性能的に望ましくないです。

CephではユーザーデータはHDDへ、メタデータだけSSDやNVMeへと分けることができるので、Rook-Cephでやってみます。

3台のworkerにそれぞれ5つのデバイスをぶら下げます。

[utubo@tutsunom ceph]$ ssh xxxxxx.compute-1.amazonaws.com lsblk
NAME    MAJ:MIN RM    SIZE RO TYPE MOUNTPOINT
xvda    202:0    0    128G  0 disk 
├─xvda1 202:1    0 1007.5K  0 part 
└─xvda2 202:2    0    128G  0 part /
xvdf    202:80   0      5G  0 disk  ← ssd(metadata用) 
xvdg    202:96   0     10G  0 disk  ← ssd(osd用) 
xvdh    202:112  0     10G  0 disk  ← ssd(osd用) 
xvdi    202:128  0     20G  0 disk  ← hdd(osd用) 
xvdj    202:144  0     20G  0 disk  ← hdd(osd用) 

何も考えずにkubectl create -f cluster.yamlをやってしまうと、

[utubo@tutsunom ceph]$ kubectl exec -it `kubectl get pod -l app=rook-ceph-tools -o 'jsonpath={.items[].metadata.name}'` ceph osd df tree
ID  CLASS WEIGHT  REWEIGHT SIZE    RAW USE DATA    OMAP META   AVAIL   %USE  VAR  PGS STATUS TYPE NAME                                      
 -1       0.19061        - 195 GiB  15 GiB  15 MiB  0 B 15 GiB 180 GiB  7.70 1.00   -        root default                                   
 -5       0.19061        - 195 GiB  15 GiB  15 MiB  0 B 15 GiB 180 GiB  7.70 1.00   -            region us-east-1                           
 -4       0.06354        -  65 GiB 5.0 GiB 4.8 MiB  0 B  5 GiB  60 GiB  7.70 1.00   -                zone us-east-1a                        
 -3       0.06354        -  65 GiB 5.0 GiB 4.8 MiB  0 B  5 GiB  60 GiB  7.70 1.00   -                    host ip-172-20-42-193-ec2-internal 
  0   ssd 0.01859  1.00000  19 GiB 1.0 GiB 992 KiB  0 B  1 GiB  18 GiB  5.27 0.68   0     up                 osd.0                          
  3   ssd 0.00879  1.00000   9 GiB 1.0 GiB 992 KiB  0 B  1 GiB 8.0 GiB 11.12 1.44   0     up                 osd.3                          
  6   ssd 0.00879  1.00000   9 GiB 1.0 GiB 992 KiB  0 B  1 GiB 8.0 GiB 11.12 1.44   0     up                 osd.6                          
  9   ssd 0.01859  1.00000  19 GiB 1.0 GiB 992 KiB  0 B  1 GiB  18 GiB  5.27 0.68   0     up                 osd.9                          
 12   ssd 0.00879  1.00000   9 GiB 1.0 GiB 992 KiB  0 B  1 GiB 8.0 GiB 11.12 1.44   0     up                 osd.12                         
-14       0.06354        -  65 GiB 5.0 GiB 4.8 MiB  0 B  5 GiB  60 GiB  7.70 1.00   -                zone us-east-1b                        
-13       0.06354        -  65 GiB 5.0 GiB 4.8 MiB  0 B  5 GiB  60 GiB  7.70 1.00   -                    host ip-172-20-93-28-ec2-internal  
  1   ssd 0.00879  1.00000   9 GiB 1.0 GiB 992 KiB  0 B  1 GiB 8.0 GiB 11.12 1.44   0     up                 osd.1                          
  4   ssd 0.01859  1.00000  19 GiB 1.0 GiB 992 KiB  0 B  1 GiB  18 GiB  5.27 0.68   0     up                 osd.4                          
  7   ssd 0.00879  1.00000   9 GiB 1.0 GiB 992 KiB  0 B  1 GiB 8.0 GiB 11.12 1.44   0     up                 osd.7                          
 11   ssd 0.00879  1.00000   9 GiB 1.0 GiB 992 KiB  0 B  1 GiB 8.0 GiB 11.12 1.44   0     up                 osd.11                         
 14   ssd 0.01859  1.00000  19 GiB 1.0 GiB 992 KiB  0 B  1 GiB  18 GiB  5.27 0.68   0     up                 osd.14                         
-10       0.06354        -  65 GiB 5.0 GiB 4.8 MiB  0 B  5 GiB  60 GiB  7.70 1.00   -                zone us-east-1c                        
 -9       0.06354        -  65 GiB 5.0 GiB 4.8 MiB  0 B  5 GiB  60 GiB  7.70 1.00   -                    host ip-172-20-102-19-ec2-internal 
  2   ssd 0.01859  1.00000  19 GiB 1.0 GiB 992 KiB  0 B  1 GiB  18 GiB  5.27 0.68   0     up                 osd.2                          
  5   ssd 0.00879  1.00000   9 GiB 1.0 GiB 992 KiB  0 B  1 GiB 8.0 GiB 11.12 1.44   0     up                 osd.5                          
  8   ssd 0.01859  1.00000  19 GiB 1.0 GiB 992 KiB  0 B  1 GiB  18 GiB  5.27 0.68   0     up                 osd.8                          
 10   ssd 0.00879  1.00000   9 GiB 1.0 GiB 992 KiB  0 B  1 GiB 8.0 GiB 11.12 1.44   0     up                 osd.10                         
 13   ssd 0.00879  1.00000   9 GiB 1.0 GiB 992 KiB  0 B  1 GiB 8.0 GiB 11.12 1.44   0     up                 osd.13                         

という風に全部のデバイスosdとなってしまいました。あー。ノードではこんな感じでLVが作られる。

[utubo@tutsunom ceph]$ ssh xxxxxx.compute-1.amazonaws.com lsblk
NAME                                                                                                 MAJ:MIN RM    SIZE RO TYPE MOUNTPOINT
xvda                                                                                                 202:0    0    128G  0 disk 
├─xvda1                                                                                              202:1    0 1007.5K  0 part 
└─xvda2                                                                                              202:2    0    128G  0 part /
xvdf                                                                                                 202:80   0     10G  0 disk 
└─ceph--8e673497--53a0--457f--9b7a--b48ea1f6e6d8-osd--data--e7021f4d--29f2--4c49--ba75--d242b0288181 254:2    0      9G  0 lvm  
xvdg                                                                                                 202:96   0     10G  0 disk 
└─ceph--0698a316--b2c8--4301--b076--0ed7c854649a-osd--data--a4f8a81f--1b17--483f--8ed6--7c3c299cc394 254:4    0      9G  0 lvm  
xvdh                                                                                                 202:112  0     10G  0 disk 
└─ceph--77603f3c--1bf5--4a22--8e98--4e90a06863af-osd--data--91c9407d--4b7f--4975--b1ae--2a2fffd8330e 254:1    0      9G  0 lvm  
xvdi                                                                                                 202:128  0     20G  0 disk 
└─ceph--0d73aa24--76db--4993--a455--07de990a7032-osd--data--67b5ef67--40d4--486e--a7f4--a92a974c19a4 254:3    0     19G  0 lvm  
xvdj                                                                                                 202:144  0     20G  0 disk 
└─ceph--e33ac0e4--633c--4af7--8fbf--f65a125bb1a3-osd--data--e952dada--1dea--4f24--8efd--2a4884914ea1 254:0    0     19G  0 lvm  

各デバイスのLVでユーザー領域とメタデータ領域が同居することになってしまってます。 メタデータ専用のデバイスを指定できればできればいいですね。cluster.yamlファイルのstorageのセクションで、

  storage: # cluster level storage configuration and selection
    useAllNodes: true
    useAllDevices: true
    topologyAware: true
    deviceFilter:
    location:
    config:
      storeType: bluestore
      metadataDevice: "xvdf"
      databaseSizeMB: "1024" # uncomment if the disks are smaller than 100 GB

みたいな感じにconfig:以下を指定してあげましょう。

[utubo@tutsunom ceph]$ kubectl exec -it `kubectl get pod -l app=rook-ceph-tools -o 'jsonpath={.items[].metadata.name}'` ceph osd df tree
ID  CLASS WEIGHT  REWEIGHT SIZE    RAW USE DATA    OMAP META   AVAIL   %USE  VAR  PGS STATUS TYPE NAME                                      
 -1       0.17569        - 180 GiB  24 GiB  10 MiB  0 B 12 GiB 156 GiB 13.34 1.00   -        root default                                   
 -5       0.17569        - 180 GiB  24 GiB  10 MiB  0 B 12 GiB 156 GiB 13.34 1.00   -            region us-east-1                           
 -4       0.05856        -  60 GiB 8.0 GiB 3.5 MiB  0 B  4 GiB  52 GiB 13.34 1.00   -                zone us-east-1a                        
 -3       0.05856        -  60 GiB 8.0 GiB 3.5 MiB  0 B  4 GiB  52 GiB 13.34 1.00   -                    host ip-172-20-42-193-ec2-internal 
  0   ssd 0.01949  1.00000  20 GiB 2.0 GiB 896 KiB  0 B  1 GiB  18 GiB 10.00 0.75   0     up                 osd.0                          
  3   ssd 0.00980  1.00000  10 GiB 2.0 GiB 896 KiB  0 B  1 GiB 8.0 GiB 20.01 1.50   0     up                 osd.3                          
  6   ssd 0.01949  1.00000  20 GiB 2.0 GiB 896 KiB  0 B  1 GiB  18 GiB 10.00 0.75   0     up                 osd.6                          
  9   ssd 0.00980  1.00000  10 GiB 2.0 GiB 896 KiB  0 B  1 GiB 8.0 GiB 20.01 1.50   0     up                 osd.9                          
-14       0.05856        -  60 GiB 8.0 GiB 3.5 MiB  0 B  4 GiB  52 GiB 13.34 1.00   -                zone us-east-1b                        
-13       0.05856        -  60 GiB 8.0 GiB 3.5 MiB  0 B  4 GiB  52 GiB 13.34 1.00   -                    host ip-172-20-93-28-ec2-internal  
  2   ssd 0.01949  1.00000  20 GiB 2.0 GiB 896 KiB  0 B  1 GiB  18 GiB 10.00 0.75   0     up                 osd.2                          
  5   ssd 0.00980  1.00000  10 GiB 2.0 GiB 896 KiB  0 B  1 GiB 8.0 GiB 20.01 1.50   0     up                 osd.5                          
  8   ssd 0.01949  1.00000  20 GiB 2.0 GiB 896 KiB  0 B  1 GiB  18 GiB 10.00 0.75   0     up                 osd.8                          
 11   ssd 0.00980  1.00000  10 GiB 2.0 GiB 896 KiB  0 B  1 GiB 8.0 GiB 20.01 1.50   0     up                 osd.11                         
-10       0.05856        -  60 GiB 8.0 GiB 3.5 MiB  0 B  4 GiB  52 GiB 13.34 1.00   -                zone us-east-1c                        
 -9       0.05856        -  60 GiB 8.0 GiB 3.5 MiB  0 B  4 GiB  52 GiB 13.34 1.00   -                    host ip-172-20-102-19-ec2-internal 
  1   ssd 0.01949  1.00000  20 GiB 2.0 GiB 896 KiB  0 B  1 GiB  18 GiB 10.00 0.75   0     up                 osd.1                          
  4   ssd 0.00980  1.00000  10 GiB 2.0 GiB 896 KiB  0 B  1 GiB 8.0 GiB 20.01 1.50   0     up                 osd.4                          
  7   ssd 0.01949  1.00000  20 GiB 2.0 GiB 896 KiB  0 B  1 GiB  18 GiB 10.00 0.75   0     up                 osd.7                          
 10   ssd 0.00980  1.00000  10 GiB 2.0 GiB 896 KiB  0 B  1 GiB 8.0 GiB 20.01 1.50   0     up                 osd.10                         
                     TOTAL 180 GiB  24 GiB  10 MiB  0 B 12 GiB 156 GiB 13.34                                                                
MIN/MAX VAR: 0.75/1.50  STDDEV: 5.27

ノードではこうなってます。希望通り/dev/xvdfメタデータ(dbs; rocksDB)領域のLVがまとまっています。

[utubo@tutsunom ceph]$ ssh xxxxxx.compute-1.amazonaws.com lsblk
NAME                                                                                                                  MAJ:MIN RM    SIZE RO TYPE MOUNTPOINT
xvda                                                                                                                  202:0    0    128G  0 disk 
├─xvda1                                                                                                               202:1    0 1007.5K  0 part 
└─xvda2                                                                                                               202:2    0    128G  0 part /
xvdf                                                                                                                  202:80   0     10G  0 disk 
├─ceph--block--dbs--3d572391--5d90--4499--9777--1882fdb2d032-osd--block--db--0a9417fd--1f1e--4847--901b--abc1bc7a6f76 254:1    0      1G  0 lvm  
├─ceph--block--dbs--3d572391--5d90--4499--9777--1882fdb2d032-osd--block--db--209b10e9--9712--462a--a354--e1e74321252c 254:3    0      1G  0 lvm  
├─ceph--block--dbs--3d572391--5d90--4499--9777--1882fdb2d032-osd--block--db--5743ece9--2e47--4a80--bd65--85a5e8ded997 254:5    0      1G  0 lvm  
└─ceph--block--dbs--3d572391--5d90--4499--9777--1882fdb2d032-osd--block--db--69a7ef92--0c0f--4921--9276--754b1eb0aade 254:7    0      1G  0 lvm  
xvdg                                                                                                                  202:96   0     10G  0 disk 
└─ceph--block--690c01e5--5df9--4f76--a0b7--5cda604029f3-osd--block--438cc4b8--acc4--4bc2--844f--4169f7d15eb4          254:6    0      9G  0 lvm  
xvdh                                                                                                                  202:112  0     10G  0 disk 
└─ceph--block--63789610--ea4f--4c4d--b7d3--3a9cc61aab7b-osd--block--f00ca520--0ce4--4458--b2ca--8836ec16719f          254:2    0      9G  0 lvm  
xvdi                                                                                                                  202:128  0     20G  0 disk 
└─ceph--block--2df2e114--9da7--44e6--ab0b--971511841130-osd--block--fad745f3--d7f6--4734--a0ab--081456857402          254:4    0     19G  0 lvm  
xvdj                                                                                                                  202:144  0     20G  0 disk 
└─ceph--block--071efb65--60b2--4a97--94b4--da0a60fc2022-osd--block--fc1da0af--0c9a--4f12--b31b--6ac9438d9e1c          254:0    0     19G  0 lvm  

Device Classを指定してPoolを作る

この状態で素直にPoolを作ってしまうと、SSDとHDDがごちゃまぜになったPoolになってしまって、SSDの性能がHDDに引っ張られてしまってもったいないことになります。だからSSDとHDDは分けて使いましょう。
それはそうと、SSDとHDDが混在する環境なのにCLASSのカラムが全部ssdってのはいただけません。クラウドではありがちな感じでしょうね。github見てると近いうちにデバイス毎にdeviceClassが指定できるようになるでしょう。いまのところは手で変更しときます。CRUSH mapをいじくってもいいです。

[utubo@tutsunom ceph]$ kubectl exec -it `kubectl get pod -l app=rook-ceph-tools -o 'jsonpath={.items[].metadata.name}'` ceph osd crush rm-device-class 0 1 2 6 7 8
done removing class of osd(s): 0,1,2,6,7,8
[utubo@tutsunom ceph]$ kubectl exec -it `kubectl get pod -l app=rook-ceph-tools -o 'jsonpath={.items[].metadata.name}'` ceph osd crush set-device-class hdd 0 1 2 6 7 8
set osd(s) 0,1,2,6,7,8 to class 'hdd'
[utubo@tutsunom ceph]$ kubectl exec -it `kubectl get pod -l app=rook-ceph-tools -o 'jsonpath={.items[].metadata.name}'` ceph osd df tree
ID  CLASS WEIGHT  REWEIGHT SIZE    RAW USE DATA    OMAP META   AVAIL   %USE  VAR  PGS STATUS TYPE NAME                                      
 -1       0.17569        - 180 GiB  24 GiB  11 MiB  0 B 12 GiB 156 GiB 13.34 1.00   -        root default                                   
 -5       0.17569        - 180 GiB  24 GiB  11 MiB  0 B 12 GiB 156 GiB 13.34 1.00   -            region us-east-1                           
 -4       0.05856        -  60 GiB 8.0 GiB 3.8 MiB  0 B  4 GiB  52 GiB 13.34 1.00   -                zone us-east-1a                        
 -3       0.05856        -  60 GiB 8.0 GiB 3.8 MiB  0 B  4 GiB  52 GiB 13.34 1.00   -                    host ip-172-20-42-193-ec2-internal 
  0   hdd 0.01949  1.00000  20 GiB 2.0 GiB 960 KiB  0 B  1 GiB  18 GiB 10.00 0.75   0     up                 osd.0                          
  6   hdd 0.01949  1.00000  20 GiB 2.0 GiB 960 KiB  0 B  1 GiB  18 GiB 10.00 0.75   0     up                 osd.6                          
  3   ssd 0.00980  1.00000  10 GiB 2.0 GiB 960 KiB  0 B  1 GiB 8.0 GiB 20.01 1.50   0     up                 osd.3                          
  9   ssd 0.00980  1.00000  10 GiB 2.0 GiB 960 KiB  0 B  1 GiB 8.0 GiB 20.01 1.50   0     up                 osd.9                          
-14       0.05856        -  60 GiB 8.0 GiB 3.8 MiB  0 B  4 GiB  52 GiB 13.34 1.00   -                zone us-east-1b                        
-13       0.05856        -  60 GiB 8.0 GiB 3.8 MiB  0 B  4 GiB  52 GiB 13.34 1.00   -                    host ip-172-20-93-28-ec2-internal  
  2   hdd 0.01949  1.00000  20 GiB 2.0 GiB 960 KiB  0 B  1 GiB  18 GiB 10.00 0.75   0     up                 osd.2                          
  8   hdd 0.01949  1.00000  20 GiB 2.0 GiB 960 KiB  0 B  1 GiB  18 GiB 10.00 0.75   0     up                 osd.8                          
  5   ssd 0.00980  1.00000  10 GiB 2.0 GiB 960 KiB  0 B  1 GiB 8.0 GiB 20.01 1.50   0     up                 osd.5                          
 11   ssd 0.00980  1.00000  10 GiB 2.0 GiB 960 KiB  0 B  1 GiB 8.0 GiB 20.01 1.50   0     up                 osd.11                         
-10       0.05856        -  60 GiB 8.0 GiB 3.8 MiB  0 B  4 GiB  52 GiB 13.34 1.00   -                zone us-east-1c                        
 -9       0.05856        -  60 GiB 8.0 GiB 3.8 MiB  0 B  4 GiB  52 GiB 13.34 1.00   -                    host ip-172-20-102-19-ec2-internal 
  1   hdd 0.01949  1.00000  20 GiB 2.0 GiB 960 KiB  0 B  1 GiB  18 GiB 10.00 0.75   0     up                 osd.1                          
  7   hdd 0.01949  1.00000  20 GiB 2.0 GiB 960 KiB  0 B  1 GiB  18 GiB 10.00 0.75   0     up                 osd.7                          
  4   ssd 0.00980  1.00000  10 GiB 2.0 GiB 960 KiB  0 B  1 GiB 8.0 GiB 20.01 1.50   0     up                 osd.4                          
 10   ssd 0.00980  1.00000  10 GiB 2.0 GiB 960 KiB  0 B  1 GiB 8.0 GiB 20.01 1.50   0     up                 osd.10                         
                     TOTAL 180 GiB  24 GiB  11 MiB  0 B 12 GiB 156 GiB 13.34                                                                
MIN/MAX VAR: 0.75/1.50  STDDEV: 5.27

それではDeviceClassを指定してPoolを作りましょう。

[utubo@tutsunom ceph]$ cat pool-ssd-hdd.yaml
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: ssd-pool
  namespace: rook-ceph
spec:
  failureDomain: host
  replicated:
    size: 3
  deviceClass: ssd
---
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: hdd-pool
  namespace: rook-ceph
spec:
  failureDomain: host
  replicated:
    size: 3
  deviceClass: hdd
[utubo@tutsunom ceph]$ kubectl create -f pool-ssd-hdd.yaml 
cephblockpool.ceph.rook.io/ssd-pool created
cephblockpool.ceph.rook.io/hdd-pool created
[utubo@tutsunom ceph]$ kubectl exec -it `kubectl get pod -l app=rook-ceph-tools -o 'jsonpath={.items[].metadata.name}'` ceph df
RAW STORAGE:
    CLASS     SIZE        AVAIL       USED        RAW USED     %RAW USED 
    hdd       120 GiB     108 GiB     6.0 GiB       12 GiB         10.01 
    ssd        60 GiB      48 GiB     6.0 GiB       12 GiB         20.01 
    TOTAL     180 GiB     156 GiB      12 GiB       24 GiB         13.34 
 
POOLS:
    POOL         ID     STORED     OBJECTS     USED     %USED     MAX AVAIL 
    ssd-pool      1        0 B           0      0 B         0        15 GiB 
    hdd-pool      2        0 B           0      0 B         0        34 GiB 
[utubo@tutsunom ceph]$ kubectl exec -it `kubectl get pod -l app=rook-ceph-tools -o 'jsonpath={.items[].metadata.name}'` ceph pg ls-by-pool ssd-pool
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   26m     0'0    36:14 [11,9,10]p11 [11,9,10]p11 2019-12-02 03:55:47.245350 2019-12-02 03:55:47.245350 
1.1       0        0         0       0     0           0          0   0 active+clean   26m     0'0    36:14   [3,5,10]p3   [3,5,10]p3 2019-12-02 03:55:47.245350 2019-12-02 03:55:47.245350 
1.2       0        0         0       0     0           0          0   0 active+clean   26m     0'0    36:14   [9,4,11]p9   [9,4,11]p9 2019-12-02 03:55:47.245350 2019-12-02 03:55:47.245350 
1.3       0        0         0       0     0           0          0   0 active+clean   26m     0'0    36:14 [11,10,3]p11 [11,10,3]p11 2019-12-02 03:55:47.245350 2019-12-02 03:55:47.245350 
1.4       0        0         0       0     0           0          0   0 active+clean   26m     0'0    36:14   [3,5,10]p3   [3,5,10]p3 2019-12-02 03:55:47.245350 2019-12-02 03:55:47.245350 
1.5       0        0         0       0     0           0          0   0 active+clean   26m     0'0    36:14   [3,11,4]p3   [3,11,4]p3 2019-12-02 03:55:47.245350 2019-12-02 03:55:47.245350 
1.6       0        0         0       0     0           0          0   0 active+clean   26m     0'0    36:14  [3,10,11]p3  [3,10,11]p3 2019-12-02 03:55:47.245350 2019-12-02 03:55:47.245350 
1.7       0        0         0       0     0           0          0   0 active+clean   26m     0'0    36:14    [5,4,9]p5    [5,4,9]p5 2019-12-02 03:55:47.245350 2019-12-02 03:55:47.245350 

* NOTE: Omap statistics are gathered during deep scrub and may be inaccurate soon afterwards depending on utilisation. See http://docs.ceph.com/docs/master/dev/placement-group/#omap-statistics for further details.
[utubo@tutsunom ceph]$ kubectl exec -it `kubectl get pod -l app=rook-ceph-tools -o 'jsonpath={.items[].metadata.name}'` ceph pg ls-by-pool hdd-pool
PG  OBJECTS DEGRADED MISPLACED UNFOUND BYTES OMAP_BYTES* OMAP_KEYS* LOG STATE        SINCE VERSION REPORTED UP        ACTING    SCRUB_STAMP                DEEP_SCRUB_STAMP           
2.0       0        0         0       0     0           0          0   0 active+clean   26m     0'0    36:10 [7,8,0]p7 [7,8,0]p7 2019-12-02 03:55:51.450649 2019-12-02 03:55:51.450649 
2.1       0        0         0       0     0           0          0   0 active+clean   26m     0'0    36:10 [7,8,0]p7 [7,8,0]p7 2019-12-02 03:55:51.450649 2019-12-02 03:55:51.450649 
2.2       0        0         0       0     0           0          0   0 active+clean   26m     0'0    36:10 [8,1,0]p8 [8,1,0]p8 2019-12-02 03:55:51.450649 2019-12-02 03:55:51.450649 
2.3       0        0         0       0     0           0          0   0 active+clean   26m     0'0    36:10 [8,0,1]p8 [8,0,1]p8 2019-12-02 03:55:51.450649 2019-12-02 03:55:51.450649 
2.4       0        0         0       0     0           0          0   0 active+clean   26m     0'0    36:10 [6,2,1]p6 [6,2,1]p6 2019-12-02 03:55:51.450649 2019-12-02 03:55:51.450649 
2.5       0        0         0       0     0           0          0   0 active+clean   26m     0'0    36:10 [7,0,8]p7 [7,0,8]p7 2019-12-02 03:55:51.450649 2019-12-02 03:55:51.450649 
2.6       0        0         0       0     0           0          0   0 active+clean   26m     0'0    36:10 [6,8,1]p6 [6,8,1]p6 2019-12-02 03:55:51.450649 2019-12-02 03:55:51.450649 
2.7       0        0         0       0     0           0          0   0 active+clean   26m     0'0    36:10 [6,7,8]p6 [6,7,8]p6 2019-12-02 03:55:51.450649 2019-12-02 03:55:51.450649 

* NOTE: Omap statistics are gathered during deep scrub and may be inaccurate soon afterwards depending on utilisation. See http://docs.ceph.com/docs/master/dev/placement-group/#omap-statistics for further details.

ssd-poolのPG割り当てられているのはDevice ClassがssdOSDで、hdd-poolのそれはDevice Classがhddのものと確認できますね。
あとはStorageClassを別々に作って使い分けていくことができます。

まとめ

Cephはいろんなデバイスクラスタにごった煮に入れて使えるので、有効な使い方ができるように多少の設計は必要ですが、最初のうちはそんなに細かく考えなくていいと思います。

まずはこれだけ留意しておきましょう。

Cephの紹介

f:id:ututaq:20191125022735p:plain

この記事はRookだらけの Advent Calendar 2019 2日目の記事です。

Rookとは少し離れますが、Cephの説明をしたいと思います。

Ceph

Cephの特徴はググればたくさん出てくるので、詳しく知りたい人はググって下さい。ざっくり簡単に書きます。

RADOS

RADOSとはReliable Autonomous Distributed Object Storeの頭字語です。RADOSはCephの核となるオブジェクトストアで、Cephに書かれるデータは全てオブジェクトの形式でRADOSに格納されます。
RADOSを構成する要素として、OSDとMONとMGRがあります。

f:id:ututaq:20191201232106p:plain

OSD

OSDはObject Storage Deviceの頭字語です。ざっくり言うと、Cephクラスタの保存媒体であるデバイスを抽象化した姿です。

  • OSD = device(HDD, SDD) + ロケーションを管理するシステム + read/writeのリクエストを受ける daemon
  • ロケーションを管理するシステムは、FileStoreの場合はファイルシステム、BlueStoreの場合はrocksDB
  • 1デバイス = 1OSDの割り当てが基本。NVMeなど高性能のFlashバイスの場合は1デバイス = 複数OSDに割り当てることもある
  • クラスタ内で一意の通しIDと名前を持つ

MON

MONはMonitorです。ざっくり言うと、Cephクラスタライフセーバーみたいな役割です。平常時は正直それほど働かないのであんまり目立たないんですが、超重用な役割なんです。

  • MONはクラスタを常時監視し、“Cluster Map”というクラスタ全体の状態を把握する情報を管理する
  • 障害や拡張によるクラスタトポロジーの変化を察知し、望ましい姿への復帰(下がった冗長性の復旧など)を司る
  • クラスタに通常複数存在し、分散したMON同士で単一のクラスタ状態を共有するためにPaxosアルゴリズムによる分散合意形成を行う
  • クラスターには3つ以上の奇数(2N+1)個のMONが必要で、N個までMONがDownしても合意形成が可能
  • クライアントの認証も行う

MGR

MGRはManagerです。ざっくり言うと、矢吹丈に対する丹下段平みたいにMONと常に一緒にいてサポートします。

  • CephのGUI(Dashboard)を提供する
  • Prometheus,Zabbixなど外部の管理/監視ツールへのAPIを提供する
  • 補助的にクラスタ監視の役割

あとCephFS使う場合はMDSというものも出てきますが今は置いときます。

Pool と Placement Group(PG)

PoolとPlacement Group(PG)はRADOSの中でも重要な概念です。

Pool

f:id:ututaq:20191201235421p:plain

Poolは論理的なデータの保存領域です。CephクライアントはPoolに対してデータをRead/Writeします。
PoolはObject, Block, Fileのクライアントの利用用途によらず共通で利用することができますが、用途が変われば冗長性や性能等の要件が異なることが多いので、基本的には用途ごとにPoolは分けるのが普通です。
Poolを作成する時には"CRUSH rule"を指定します。ruleを指定しなければ、Cephのconfigファイル(ceph.conf)に記載されるデフォルトのruleが適用されます。

Replicated Pool と Erasure Coded Pool

f:id:ututaq:20191201235425p:plain

指定したCRUSH ruleに従ってPoolのPGが作成されますが、ruleの書き方によってPoolは2つのタイプに大別されます。

  • Replicated Pool

    • オブジェクトを複製して冗長化
    • 容量効率は低いがI/O性能とリカバリの早さにはEC Poolより優れる
    • 一般的によく利用される標準のタイプ
  • Erasure Coded(EC) Pool

    • Erasure Codingの仕組みで冗長化
    • I/O性能とリカバリはReplicated Poolに劣るが容量効率は高い
    • 大容量が必要なケースで利用される

BlockやFileで使う場合はReplicated Poolの方がレスポンスがいいのでよいでしょう。Objectで使う場合はEC Poolの方が容量効率が高いのでいいかもしれません。

Placement Group(PG)

f:id:ututaq:20191202005121p:plain

前述の通り、PGはPool作成時に自動的に作成される、CRUSH ruleに沿って構成されるOSDのグループです。
例えば3x Replicated Poolを作った場合、1つのPGには3つのOSDがランダムに割り当たります。Pool作成時にpg-numで指定した数だけ、このPGが作られます。
通常PGはOSDよりも多く作られ、1つのOSDは複数のPGで重複して割り当てられます。1OSDは50〜100くらいのPGに割り当たるようにPool作成時にpg-numで調節するのが一般的な構成では良いようですが、OSDの数によって変わるのでPGCalcでシミュレートするのがよいです。

Ceph PGCalc - Ceph

CephクライアントがPoolにWriteすると、データは既定のサイズのオブジェクトにストライプされ、各ストライプでCRUSHアルゴリズムによって1つのPGが選択され、そのPGが持つ複数のOSDに書き込まれます。

PGのOSDには順序が存在します。例えば、[0,1,2]と[2,1,0]は、同じOSDの割り当てながら順序が違うため異なるPGとなります。
先頭のPrimary OSDがそのPGの代表として(ストライプされた)オブジェクトのRead/Write処理を行います。Read/Write時の動作はPoolのタイプによって異なります。

f:id:ututaq:20191202005212p:plain f:id:ututaq:20191202005233p:plain

また、PGはOSDの数に対して結構多く作られるので、全く同じOSD順序のPGが2つ以上作られることが普通にあります。
ですが、これ自身は全く問題ありません。別にどちらのPGが使われてもOSDに分散されることには違いないのですから。
それだとデバイスの利用率に偏りが出るのでは?という疑問が湧いてきますが、そもそもRADOSは、

「全部の玉のutilizationをきれいに揃える必要ないよ、何百何千発にもスケールした時そんな管理してたらオーバーヘッドなだけでしょ?ゆるく揃ってればいいじゃん。目立つ偏りがあったらアラート上げるからrebalanceかけるんだよ」

というコンセプトで作られています。なのでOSDの割り当てで発生する偏りは構わないのです。
またrebalanceはSDSで一番問題になりやすい強敵ですが、rebalanceの問題は『きれいに利用率を揃えようとして必要以上にデータの移動が発生し、結果ネットワークトラフィックが重い時間が長くなること』です。Cephではゆるく揃えばそれでいいので、必要最小限のデータ移動で済むようになってます。

CRUSH rule

CRUSH ruleは早い話が、PGに割り当てるOSDの決め方のルールです。
例えばruleなく1つのPGに3つのOSDをランダムに割り当てると、同一ノード上の3つのOSDを選択する可能性があります。これだとそのノードが壊れた時データロスになります。
しかし『ノードから1個ずつOSDを選択する』というruleを書いておけば、そのようにOSDが選択されるので、3つのノードにレプリカされることになります。 CRUSH ruleを駆使することで、任意のfailure domainでデータを冗長化することができます。

まとめ

今日はCephっていうかRADOSを説明しました。RADOSはとても良くできた仕組み(特にCRUSHが)で、うつぼはCephと出会ってこの仕組みに目から鱗が落ち、Cephが大好きになりました。

みなさんもCephが大好きになってくれたらうれしいです。(小並感)

BGMは回レ!雪月花でした。