ストレージオーケストレーター Rook : 第5話 CephFSの脅威

f:id:ututaq:20191125022735p:plain

まいど。レッドハットでストレージを中心にクラウドインフラを生業にしている宇都宮です。

Red Hat Forum Tokyoの準備にかまけてすっかり間があいてしまいました(汗) 去る11/15にRed Hat Forum Tokyoがありました。足を運んでくださった皆さん、ありがとうございました。
今年のForumではROOK CHALLENGEという展示ブースの一角を担当させていただきました。中身はほぼDIY工作だったのでめっちゃ疲れましたが、めっちゃ楽しかったです。とにかく目立つ異様な一角を提供できたかなーと思います。
ROOK CHALLENGEは12/09開催予定のJapan Rook Meetup#1と、12/20開催予定のOpenShift.RUNの両方で展示する予定です。

引き続き、RookとOpenShift Japan CommunityのTwitterアカウント、@japan_rook@openshiftjpのフォローお願いしまーす!

前回はRook-Ceph CSIのアーキテクチャのお話でした。今回はこのCeph CSIドライバを使ってCephFSを使ってみましょうね。

CephFSってなによ

はいそれでは本題に入ります。Cephは本体部分がRADOSというオブジェクトストアなんですが、ファイルアクセスのインターフェースを持ってます。それをCephFSと呼んだりします。
ファイルアクセスのインターフェースと言っても、Linux kernelが持ってるクライアントか、独自のFUSEクライアントを使ってマウントします。一般的なNFSやCIFSのようなファイルプロトコルでクライアントからマウントすることはできません。(Cephの上にNFS serverやSambaが稼働するサーバーを置いて、いわゆるNAS GatewayのようにすることでNFS,CIFSに対応することは可能)

そんなCephFSですが、もちろんRook-Ceph上でも使うことができます。RWXのPVが必要な時はCephFS使うことになるので、使い方を覚えておくと便利ですよ。

KubernetesクラスタとCephクラスタの作成

いつもどおりAWS上に3master+6workerで作って、3workerをストレージ専用に使います。3workerにrole=app, 3workerはrole=storageとlabelを付けます。ストレージ専用のworkerには、それぞれ20GBのEBSを1つずつattachしておきます。

[utubo@tutsunom ceph]$ kubectl get node --sort-by=".metadata.creationTimestamp" -L role
NAME                             STATUS   ROLES    AGE     VERSION   ROLE
ip-172-20-55-215.ec2.internal    Ready    master   2d18h   v1.15.5   
ip-172-20-123-190.ec2.internal   Ready    master   2d18h   v1.15.5   
ip-172-20-87-105.ec2.internal    Ready    master   2d18h   v1.15.5   
ip-172-20-58-75.ec2.internal     Ready    node     2d18h   v1.15.5   app
ip-172-20-106-226.ec2.internal   Ready    node     2d18h   v1.15.5   app
ip-172-20-90-59.ec2.internal     Ready    node     2d18h   v1.15.5   app
ip-172-20-76-87.ec2.internal     Ready    node     12m     v1.15.5   storage
ip-172-20-102-73.ec2.internal    Ready    node     12m     v1.15.5   storage
ip-172-20-44-151.ec2.internal    Ready    node     12m     v1.15.5   storage

次はRook-Ceph operatorでCephクラスタを作ります。
何気にこれまで、githubのrookリポジトリをcloneしてやるスタンダードな方法を紹介してなかったので、今回は簡単にやりますね。

[utubo@tutsunom ~]$ git clone -b release-1.1 https://github.com/rook/rook.git
[utubo@tutsunom ~]$ cd rook/cluster/examples/kubernetes/ceph
[utubo@tutsunom ceph]$ kubectl create -f common.yaml

好きなブランチでgit cloneしましょう。masterでもいいですがstableな方がいいでしょう。最新のstableなのは1.1なので、これを使います。
で、奥深く掘り進んで行って、common.yamlkubectl createします。ちょっと試すぶんにはこの辺は何も考えずにやっちゃっていいです。

はい、次にkubectl create -f operator.yamlでoperatorを入れます。
何も考えずにやっちゃってもいいんですが、そうするとappノードにもrook-discover podが上がってしまいます。
rook-discoverは、ノードにHDDやSSDみたいなデバイスがattachされたかどうかをずっと見張ってる奴です。今回デバイスはstorageノードにしか付けません。つまりappノードにrook-discoverが居ても意味がない。だからstorageノードにだけrook-discoverが上がるようにNodeAffinityを設定しておきます。元のoperator.yamlの中に、グレーアウトしてるDISCOVER_AGENT_NODE_AFFINITYというオプションがあるので、こいつをいじるのが話早いと思います。役に立つかもしれないので後段で使ったoperator.yamlを置いておきます。 https://gist.github.com/1be5a50b52a1cb293f8086199023abc7

[utubo@tutsunom ceph]$ kubectl create -f operator.yaml
[utubo@tutsunom ceph]$ kubectl create -f cluster.yaml
[utubo@tutsunom ceph]$ kubectl create -f toolbox.yaml

はい、これで5,6分ほどコーヒーを飲んで待てばCephクラスタができてるはずです。

[utubo@tutsunom ceph]$ kubectl get pod
NAME                                                         READY   STATUS      RESTARTS   AGE
csi-cephfsplugin-2chlm                                       3/3     Running     0          112m
csi-cephfsplugin-7zm85                                       3/3     Running     0          112m
csi-cephfsplugin-pjmrc                                       3/3     Running     0          112m
csi-cephfsplugin-provisioner-7565878587-4v5zc                4/4     Running     0          112m
csi-cephfsplugin-provisioner-7565878587-tq9vv                4/4     Running     0          112m
csi-cephfsplugin-qwcxs                                       3/3     Running     0          112m
csi-cephfsplugin-rmld5                                       3/3     Running     0          112m
csi-cephfsplugin-tq5d7                                       3/3     Running     0          112m
csi-rbdplugin-bfc9s                                          3/3     Running     0          112m
csi-rbdplugin-dsvlg                                          3/3     Running     0          112m
csi-rbdplugin-fd7dr                                          3/3     Running     0          112m
csi-rbdplugin-jbblr                                          3/3     Running     0          112m
csi-rbdplugin-provisioner-d6bf7b9d9-fk2qp                    5/5     Running     0          112m
csi-rbdplugin-provisioner-d6bf7b9d9-kl5vx                    5/5     Running     0          112m
csi-rbdplugin-twt8d                                          3/3     Running     0          112m
csi-rbdplugin-vb4p5                                          3/3     Running     0          112m
rook-ceph-mgr-a-69c747f894-9blsd                             1/1     Running     0          111m
rook-ceph-mon-a-5cf48c58b5-xg5zg                             1/1     Running     0          112m
rook-ceph-mon-b-5f7cb997bd-f9gjs                             1/1     Running     0          112m
rook-ceph-mon-c-7c4dbc56ff-fxrsz                             1/1     Running     0          111m
rook-ceph-operator-7fcd9b6cc9-jrjpg                          1/1     Running     0          115m
rook-ceph-osd-0-855d7c96c-n6pkw                              1/1     Running     0          110m
rook-ceph-osd-1-856d8b8f94-jgrxz                             1/1     Running     0          110m
rook-ceph-osd-2-657c4b68bc-vl5cn                             1/1     Running     0          110m
rook-ceph-osd-prepare-ip-172-20-102-73.ec2.internal-dwhgv    0/1     Completed   0          111m
rook-ceph-osd-prepare-ip-172-20-106-226.ec2.internal-vzx9f   0/1     Completed   0          111m
rook-ceph-osd-prepare-ip-172-20-44-151.ec2.internal-srsgj    0/1     Completed   0          111m
rook-ceph-osd-prepare-ip-172-20-58-75.ec2.internal-ltnnx     0/1     Completed   0          111m
rook-ceph-osd-prepare-ip-172-20-76-87.ec2.internal-4q8zf     0/1     Completed   0          111m
rook-ceph-osd-prepare-ip-172-20-90-59.ec2.internal-n2mj8     0/1     Completed   0          111m
rook-ceph-tools-5bc668d889-qcsw9                             1/1     Running     0          110m
rook-discover-26kcq                                          1/1     Running     0          115m
rook-discover-fthtd                                          1/1     Running     0          115m
rook-discover-h4glj                                          1/1     Running     0          115m
[utubo@tutsunom ceph]$ kubectl exec -it `kubectl get pod -l app=rook-ceph-tools -o 'jsonpath={.items[].metadata.name}'` \
> ceph status
  cluster:
    id:     a578ddea-9338-40ad-b50b-8acabfc6e8f1
    health: HEALTH_OK
 
  services:
    mon: 3 daemons, quorum a,b,c (age 107s)
    mgr: a(active, since 75s)
    osd: 3 osds: 3 up (since 34s), 3 in (since 34s)
 
  data:
    pools:   0 pools, 0 pgs
    objects: 0 objects, 0 B
    usage:   3.0 GiB used, 54 GiB / 57 GiB avail
    pgs:     

はい、クラスタできてます。

CephFS ファイルシステムを作る

それではCephでファイルシステムを作りましょう。簡単です。

[utubo@tutsunom ceph]$ kubectl create -f filesystem.yaml
cephfilesystem.ceph.rook.io/myfs created

これだけ。rook-ceph-mds-myfs-〜というpodが2つ上がってればオッケーです。

[utubo@tutsunom ceph]$ kubectl get pod -l app=rook-ceph-mds
NAME                                                         READY   STATUS      RESTARTS   AGE
rook-ceph-mds-myfs-a-55574457b-xn5c7                         1/1     Running     0          43m
rook-ceph-mds-myfs-b-86cbb9f884-9mtsl                        1/1     Running     0          43m

オッケーね。Ceph側ではどう見えるでしょうか。

[utubo@tutsunom ceph]$ kubectl exec -it `kubectl get pod -l app=rook-ceph-tools -o 'jsonpath={.items[].metadata.name}'` \
> ceph fs ls
name: myfs, metadata pool: myfs-metadata, data pools: [myfs-data0 ]
[utubo@tutsunom ceph]$ 
[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 
    ssd       57 GiB     54 GiB     2.1 MiB      3.0 GiB          5.27 
    TOTAL     57 GiB     54 GiB     2.1 MiB      3.0 GiB          5.27 
 
POOLS:
    POOL              ID     STORED      OBJECTS     USED        %USED     MAX AVAIL 
    myfs-metadata      1     2.2 KiB          22     384 KiB         0        17 GiB 
    myfs-data0         2         0 B           0         0 B         0        17 GiB 

Ceph側では、myfsというファイルシステムが1つできていて、myfs-metadataとmyfs-data0という2つのpoolができています。
先にも言った通り、CephはRADOSと呼ばれるオブジェクトストアが本体のデータ格納機構で、全てのデータをオブジェクトとして扱います。ファイルシステムとして利用する際には、ファイルの実体の他にメタデータも同じように保管しないといけません。
メタデータは参照される機会が多いし、ぶっ壊れたらファイルシステムそのものが使えなくなる可能性があります。なので実体よりも速くて冗長性が高いpoolに分ける方がオススメってことから、CephFSではファイルの実体とメタデータが別々のpoolに分けて保管されます。
ちなみに先にpodで上がってきたrook-ceph-mds〜の、"mds"は"metadata server"のことで、こいつがファイルのメタデータを扱います。CephFSを使う際にはceph-mdsは必須です。

アプリから使ってみる

まずお約束のstorageclassの作成です。さらに深いセントラルドグマにマニフェストファイルがあります。

[utubo@tutsunom ceph]$ cd csi/cephfs/
[utubo@tutsunom cephfs]$ kubectl create -f csi/cephfs/storageclass.yaml 
storageclass.storage.k8s.io/csi-cephfs created
[utubo@tutsunom cephfs]$ kubectl get sc
NAME            PROVISIONER                     AGE
csi-cephfs      rook-ceph.cephfs.csi.ceph.com   55s
default         kubernetes.io/aws-ebs           2d21h
gp2 (default)   kubernetes.io/aws-ebs           2d21h

はいできた。名前が気に入らない場合は適宜変えてね!
さあここからだ。とりあえず第2話で現れたdateをひたすら吐き出すダメMSappを改造して、3つのPodがappノードから同時に同じPVをRWXでマウントしdateを吐き出すappにしてみます。そう。ザクとは違うのだよ、ザ(略

[utubo@tutsunom cephfs]$ cat gouf.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: date
spec:
  replicas: 3
  selector:
    matchLabels:
      app: date
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: date
    spec:
      containers:
      - name: date
        image: busybox
        command: ["/bin/sh", "-c", "while true; do echo `date` by `hostname` >> /mnt/`hostname`; echo `date` by `hostname` >> /mnt/shared_file; sleep 1; done"]
        volumeMounts:
        - name: date-pv
          mountPath: "/mnt"
      volumes:
      - name: date-pv
        persistentVolumeClaim:
          claimName: cephfs-pvc
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: role
                operator: In
                values:
                - app
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: cephfs-pvc
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
  storageClassName: csi-cephfs

初めにそれぞれのpodが、自分専用のファイル /mnt/`hostname` にdateを吐いて、次に共有のファイル /mnt/shared_file にdateを吐く感じにしました。それじゃあこのグフを走らせてみます。

[utubo@tutsunom cephfs]$ kubectl create -f gouf.yaml
deployment.apps/date created
persistentvolumeclaim/cephfs-pvc created
[utubo@tutsunom cephfs]$ kubectl get pod,pvc,pv
NAME                       READY   STATUS    RESTARTS   AGE
pod/date-55b84bcff-jvxfv   1/1     Running   0          24s
pod/date-55b84bcff-mhclb   1/1     Running   0          24s
pod/date-55b84bcff-tlcwp   1/1     Running   0          24s

NAME                               STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/cephfs-pvc   Bound    pvc-f2b186f1-4947-46c4-9dd9-b6d3c86af3f0   1Gi        RWX            csi-cephfs     25s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS   REASON   AGE
persistentvolume/pvc-f2b186f1-4947-46c4-9dd9-b6d3c86af3f0   1Gi        RWX            Delete           Bound    default/cephfs-pvc   csi-cephfs              23s

よし。じゃあファイルの中身を見てみしょう。どれか1個のpodに入って見ます。

[utubo@tutsunom kubernetes]$ kubectl exec -it pod/date-55b84bcff-jvxfv sh
/ # ls -l /mnt
total 10
-rw-r--r--    1 root     root          1802 Nov 22 06:46 date-55b84bcff-jvxfv
-rw-r--r--    1 root     root          2014 Nov 22 06:46 date-55b84bcff-mhclb
-rw-r--r--    1 root     root          1855 Nov 22 06:46 date-55b84bcff-tlcwp
-rw-r--r--    1 root     root          3816 Nov 22 06:46 shared_file
/ # tail -n 5 /mnt/date-55b84bcff-jvxfv
Fri Nov 22 06:47:07 UTC 2019 by date-55b84bcff-jvxfv
Fri Nov 22 06:47:08 UTC 2019 by date-55b84bcff-jvxfv
Fri Nov 22 06:47:09 UTC 2019 by date-55b84bcff-jvxfv
Fri Nov 22 06:47:10 UTC 2019 by date-55b84bcff-jvxfv
Fri Nov 22 06:47:11 UTC 2019 by date-55b84bcff-jvxfv
/ # tail -n 5 /mnt/date-55b84bcff-mhclb 
Fri Nov 22 06:47:17 UTC 2019 by date-55b84bcff-mhclb
Fri Nov 22 06:47:18 UTC 2019 by date-55b84bcff-mhclb
Fri Nov 22 06:47:19 UTC 2019 by date-55b84bcff-mhclb
Fri Nov 22 06:47:20 UTC 2019 by date-55b84bcff-mhclb
Fri Nov 22 06:47:21 UTC 2019 by date-55b84bcff-mhclb

それぞれのpodのファイルには、それぞれのpodからしか書いてませんねー。(当たり前だ
じゃあ共有のファイルは、

/ # tail -f /mnt/shared_file 
Fri Nov 22 06:48:03 UTC 2019 by date-55b84bcff-tlcwp
Fri Nov 22 06:48:04 UTC 2019 by date-55b84bcff-mhclb
Fri Nov 22 06:48:04 UTC 2019 by date-55b84bcff-jvxfv
Fri Nov 22 06:48:04 UTC 2019 by date-55b84bcff-tlcwp
Fri Nov 22 06:48:05 UTC 2019 by date-55b84bcff-mhclb
Fri Nov 22 06:48:05 UTC 2019 by date-55b84bcff-jvxfv
Fri Nov 22 06:48:05 UTC 2019 by date-55b84bcff-tlcwp
Fri Nov 22 06:48:06 UTC 2019 by date-55b84bcff-mhclb
Fri Nov 22 06:48:06 UTC 2019 by date-55b84bcff-jvxfv
Fri Nov 22 06:48:06 UTC 2019 by date-55b84bcff-tlcwp
Fri Nov 22 06:48:07 UTC 2019 by date-55b84bcff-mhclb
Fri Nov 22 06:48:07 UTC 2019 by date-55b84bcff-jvxfv
Fri Nov 22 06:48:07 UTC 2019 by date-55b84bcff-tlcwp
Fri Nov 22 06:48:08 UTC 2019 by date-55b84bcff-mhclb
Fri Nov 22 06:48:08 UTC 2019 by date-55b84bcff-jvxfv
Fri Nov 22 06:48:08 UTC 2019 by date-55b84bcff-tlcwp
...

3pod全員がdateを吐いてます。RWXだとこんな感じで同じファイルに書き込んだりできますね。もちろんファイルにロックが掛かってる時は書けないのでご注意ね☆

CephFSではPVはどう見える?

はい今日最後です。CephFSでPVはどういう風に扱われるのか確認してみましょう。先のグフでPVが1個切られましたが、もう1個PVCを投げて切ってみます。

[utubo@tutsunom cephfs]$ cat pvc.yaml 
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: cephfs-pvc-2
spec:
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
  storageClassName: csi-cephfs
[utubo@tutsunom cephfs]$ 
[utubo@tutsunom cephfs]$ kubectl create -f pvc.yaml 
persistentvolumeclaim/cephfs-pvc-2 created
[utubo@tutsunom cephfs]$ kubectl get pvc,pv
NAME                                 STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/cephfs-pvc     Bound    pvc-f2b186f1-4947-46c4-9dd9-b6d3c86af3f0   1Gi        RWX            csi-cephfs     56m
persistentvolumeclaim/cephfs-pvc-2   Bound    pvc-2adef642-64ab-419a-b30c-abaa23fa7807   1Gi        RWX            csi-cephfs     46s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                  STORAGECLASS   REASON   AGE
persistentvolume/pvc-2adef642-64ab-419a-b30c-abaa23fa7807   1Gi        RWX            Delete           Bound    default/cephfs-pvc-2   csi-cephfs              44s
persistentvolume/pvc-f2b186f1-4947-46c4-9dd9-b6d3c86af3f0   1Gi        RWX            Delete           Bound    default/cephfs-pvc     csi-cephfs              56m

PVC,PVが2個ずつできました。Ceph側で確認してみましょう。

[utubo@tutsunom cephfs]$ kubectl -n rook-ceph exec -it `kubectl get pod -l app=rook-ceph-tools -o 'jsonpath={.items[].metadata.name}'` \
> ceph fs ls
name: myfs, metadata pool: myfs-metadata, data pools: [myfs-data0 ]

ファイルシステムは一つのままです。つまりCephFSでは、PVCごとに新しくファイルシステムが作られてPVとして出されるわけではないということですね。 ではPVはどこにあるのか、ファイルシステムを直でマウントして確認してみましょう。ここに書かれてる技を使えばtoolbox podにファイルシステムをマウントできるので、それにしたがってやってみます。

[utubo@tutsunom cephfs]$ kubectl -n rook-ceph exec -it `kubectl get pod -l app=rook-ceph-tools -o 'jsonpath={.items[].metadata.name}'` \
> sh
sh-4.2# mkdir /tmp/cephfs
sh-4.2# mon_endpoints=$(grep mon_host /etc/ceph/ceph.conf | awk '{print $3}')
sh-4.2# my_secret=$(grep key /etc/ceph/keyring | awk '{print $3}')
sh-4.2# mount -t ceph -o mds_namespace=myfs,name=admin,secret=$my_secret $mon_endpoints:/ /tmp/cephfs
sh-4.2# df -h /tmp/cephfs
Filesystem                                                   Size  Used Avail Use% Mounted on
100.70.103.105:6789,100.65.186.42:6789,100.70.45.225:6789:/   57G  3.3G   54G   6% /tmp/cephfs

マウントできたっぽい。じゃあそのまま中身を見ていきましょう!

sh-4.2# cd /tmp/cephfs/
sh-4.2# ls -l
total 0
drwxr-xr-x 1 root root 2 Nov 22 06:13 volumes
sh-4.2# cd volumes/ ; ls -l
total 0
drwx------ 1 root root 0 Nov 22 07:30 _deleting
drwxr-xr-x 1 root root 2 Nov 22 07:30 csi
sh-4.2# cd csi/ ; ls -l
total 0
drwxrwxrwx 1 root root 4 Nov 22 06:34 csi-vol-18d47467-0cf2-11ea-895d-a6f6772013bd
drwxrwxrwx 1 root root 0 Nov 22 07:30 csi-vol-efd2b71f-0cf9-11ea-895d-a6f6772013bd
sh-4.2# cd csi-vol-18d47467-0cf2-11ea-895d-a6f6772013bd/
sh-4.2# ls -l
total 1067
-rw-r--r-- 1 root root 198273 Nov 22 07:38 date-55b84bcff-jvxfv
-rw-r--r-- 1 root root 199386 Nov 22 07:38 date-55b84bcff-mhclb
-rw-r--r-- 1 root root 198750 Nov 22 07:38 date-55b84bcff-tlcwp
-rw-r--r-- 1 root root 494755 Nov 22 07:38 shared_file
sh-4.2# 

なるほど。Cephのファイルシステム的には、/filesystem_root/volumes/csi/csi-vol-xxx/ というディレクトリをPVとしてexportしていることがわかります。つまり、CephFSではデカいファイルシステムの中にサブディレクトリを切ってPVとしてユーザーに切り出すということですね。

まとめ

はい、今日はRook 1.1を使ってRook-Ceph CSIでCephFSを使ってみました。CephFSってちょっと頼んないのかなって思ってたけど、意外と普通に使えるもんですね。それじゃあ今回はここまで。

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。