ストレージオーケストレーター Rook : 第7話 宿命のObject Bucket(Aパート)

f:id:ututaq:20191125022735p:plain

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

ども。レッドハットでストレージを中心にクラウドインフラを生業にしているウツノミヤです。
さあイッテンヨンまで後1ヶ月になりました。1.5のIWGPヘビーの方が格上なのにどうも1.4のIWGP ICの方が盛り上がってるんですよね…リマッチなのに。このあたりいつも饒舌な内藤選手が何も喋らないことでうまくメディアと世論をコントロールしてる感じがします。あとみんな二冠二冠と言い出したから、IWGPヘビー級のベルトが二冠のための手段に扱われてちょっと相場が下がってる感があるのも否めないかな。まあこれは個人的なアレですけど。

まあプロレスの話はこれくらいにしといて、Rookアドカレ、見ていただいていますか?
今年はじめてアドカレやってますが、もう4日目にしてヒイヒイ言っとるウツノミヤです。全部俺系の人は引き出しがヤバいですね。ほんとに敬服します。

qiita.com

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

Ceph RADOS Gateway (RGW)

はい、てなわけで今回はRook-Cephをオブジェクトストレージで使う方法を紹介します。
Cephは本質的にRADOSがオブジェクトストアなわけで、当然オブジェクトストレージとして使えます。libradosというライブラリを使うとRADOSに直接オブジェクトをPUT/GETできるんだけど、いやー無理っすな感じですよね。だってオブジェクトストレージはS3 (Compatible) APIがもうデファクトなんだから。
なのでCephにはRADOS Gateway(RGW)というクライアントからRADOSへS3でアクセスするためのインターフェースとなるコンポーネントがあります。

f:id:ututaq:20191201232106p:plain
左のオレンジ色のやつがRGW

それではこいつをRook-Cephでデプロイしてみましょう。KubernetesとRook-Cephの環境はざっくり次のとおりですが、詳細はこちらの12/1と12/3の記事をご覧ください。

[utubo@tutsunom ceph]$ kubectl get node --sort-by=".metadata.creationTimestamp" 
NAME                            STATUS   ROLES    AGE   VERSION
ip-172-20-91-64.ec2.internal    Ready    master   25h   v1.15.5
ip-172-20-53-29.ec2.internal    Ready    master   25h   v1.15.5
ip-172-20-113-40.ec2.internal   Ready    master   25h   v1.15.5
ip-172-20-93-28.ec2.internal    Ready    node     25h   v1.15.5
ip-172-20-42-193.ec2.internal   Ready    node     25h   v1.15.5
ip-172-20-102-19.ec2.internal   Ready    node     25h   v1.15.5
[utubo@tutsunom ceph]$
[utubo@tutsunom ceph]$ kubectl -n rook-ceph get pod 
NAME                                                        READY   STATUS             RESTARTS   AGE
csi-cephfsplugin-6sn69                                      3/3     Running            0          4h56m
csi-cephfsplugin-d78h4                                      3/3     Running            0          4h56m
csi-cephfsplugin-kx28b                                      3/3     Running            0          4h56m
csi-cephfsplugin-provisioner-974b566d9-6xkcj                4/4     Running            0          4h56m
csi-cephfsplugin-provisioner-974b566d9-bms5q                4/4     Running            0          4h56m
csi-rbdplugin-6mnjk                                         3/3     Running            0          4h56m
csi-rbdplugin-kmn5b                                         3/3     Running            0          4h56m
csi-rbdplugin-provisioner-579c546f5-69xh8                   5/5     Running            0          4h56m
csi-rbdplugin-provisioner-579c546f5-hsdbm                   5/5     Running            0          4h56m
csi-rbdplugin-tgbd9                                         3/3     Running            0          4h56m
rook-ceph-mgr-a-d45bf555f-fwjbv                             1/1     Running            0          4h55m
rook-ceph-mon-a-5f94d65bd-fzk8s                             1/1     Running            0          4h56m
rook-ceph-mon-b-5d9dd8778b-chmhw                            1/1     Running            0          4h56m
rook-ceph-mon-c-d8bd74999-2tm6x                             1/1     Running            0          4h55m
rook-ceph-operator-fb8b96548-pg6zv                          1/1     Running            0          4h57m
rook-ceph-osd-0-5fd45cb745-qcp2d                            1/1     Running            0          4h51m
rook-ceph-osd-1-7d54784df7-nzd7t                            1/1     Running            0          4h50m
rook-ceph-osd-10-56f589bc-pl7md                             1/1     Running            0          4h50m
rook-ceph-osd-11-58cc586588-ffxhk                           1/1     Running            0          4h50m
rook-ceph-osd-2-868497f7fd-knxjh                            1/1     Running            0          4h50m
rook-ceph-osd-3-78dc45bcdb-45f84                            1/1     Running            0          4h51m
rook-ceph-osd-4-577cb784b8-5tzcp                            1/1     Running            0          4h50m
rook-ceph-osd-5-6b6587fb8c-m5x6f                            1/1     Running            0          4h50m
rook-ceph-osd-6-65c76d4696-m5rgw                            1/1     Running            0          4h51m
rook-ceph-osd-7-5f5db64776-68qfk                            1/1     Running            0          4h50m
rook-ceph-osd-8-5c7fc696c-9gdrg                             1/1     Running            0          4h50m
rook-ceph-osd-9-b4c5b567d-ws8c6                             1/1     Running            0          4h51m
rook-ceph-osd-prepare-ip-172-20-102-19.ec2.internal-pxqh2   0/1     Completed          0          4h55m
rook-ceph-osd-prepare-ip-172-20-42-193.ec2.internal-n9jgc   0/1     Completed          0          4h55m
rook-ceph-osd-prepare-ip-172-20-93-28.ec2.internal-4rl68    0/1     Completed          0          4h55m
rook-ceph-tools-5bc668d889-2jd6c                            1/1     Running            0          4h48m
rook-discover-4955r                                         1/1     Running            0          4h57m
rook-discover-bddtk                                         1/1     Running            0          4h57m
rook-discover-w5ljg                                         1/1     Running            0          4h57m
[utubo@tutsunom ceph]$
[utubo@tutsunom ceph]$ kubectl -n rook-ceph 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

下みたいなyamlでRGWとオブジェクトストレージ用のPoolが作られます。

apiVersion: ceph.rook.io/v1
kind: CephObjectStore
metadata:
  name: objstore
  namespace: rook-ceph
spec:
  metadataPool:
    failureDomain: host
    replicated:
      size: 3
  dataPool:
    failureDomain: host
    replicated:
      size: 3
  gateway:
    type: s3
    sslCertificateRef:
    port: 80
    securePort:
    instances: 2
    placement:
    annotations:
    resources:
[utubo@tutsunom ceph]$ kubectl create -f my-object.yaml 
cephobjectstore.ceph.rook.io/objstore created
[utubo@tutsunom ceph]$ kubectl get pod -l app=rook-ceph-rgw
NAME                                        READY   STATUS    RESTARTS   AGE
rook-ceph-rgw-objstore-a-65cf59bbb7-6r8f8   1/1     Running   0          10s
rook-ceph-rgw-objstore-b-754d5f7cbd-v6mdk   1/1     Running   0          10s

rook-ceph-rgw-<objectstore name>〜というのが生まれました。これがRGWです。
RGWは1つでももちろん動くんですが、SPOF回避のためとに複数置くのがCephでのベストプラクティスです。またRGWがパフォーマンスボトルネックになることを回避するため複数で負荷分散する意味もあるので、Rook-Cephでも複数あったほうがいいんじゃないでしょうか奥さん、てことで2つ置きました。

次にPoolを見てみましょう。

[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.02 
    TOTAL     180 GiB     156 GiB      12 GiB       24 GiB         13.35 
 
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 
    objstore.rgw.control             3         0 B           8         0 B         0        45 GiB 
    objstore.rgw.meta                4         0 B           0         0 B         0        45 GiB 
    objstore.rgw.log                 5        50 B         178      48 KiB         0        45 GiB 
    objstore.rgw.buckets.index       6         0 B           0         0 B         0        45 GiB 
    objstore.rgw.buckets.non-ec      7         0 B           0         0 B         0        45 GiB 
    .rgw.root                        8     3.7 KiB          16     720 KiB         0        45 GiB 
    objstore.rgw.buckets.data        9         0 B           0         0 B         0        45 GiB 

おお、なにやらObject Store用にたくさんPoolが切られています。名前を見たら何にどんなデータが入るか何となく想像付くきますが、割とバシバシPoolが切られますね。
とにもかくにも、RGWもできたしPoolもできたのでオブジェクトストレージとしての基盤は完成です。

Object Bucket / Object Bucket Claim

Rook 1.1からObject Bucket(OB) / Object Bucket Claim(OBC)というリソースが使えるようになりました。これはPV/PVCと同じようにapp側のリクエストによって動的にオブジェクトストレージのBucketを提供するものです。
これを使ってPoolにBucketを作り、appから使ってみます。

PVと同じようにOBC用のStorageClassを作ります。

[utubo@tutsunom ceph]$ cat my-sc-bkt-delete.yaml 
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
   name: ceph-bkt-delete
provisioner: ceph.rook.io/bucket
# set the reclaim policy to delete the bucket and all objects
# when its OBC is deleted.
reclaimPolicy: Delete
parameters:
  objectStoreName: objstore
  objectStoreNamespace: rook-ceph
[utubo@tutsunom ceph]$ 
[utubo@tutsunom ceph]$ kubectl create -f my-sc-bkt-delete.yaml 
storageclass.storage.k8s.io/ceph-bkt-delete created
[utubo@tutsunom ceph]$ kubectl -n default get sc
NAME                      PROVISIONER                  AGE
ceph-bkt-delete           ceph.rook.io/bucket          5s
default                   kubernetes.io/aws-ebs        30h
gp2 (default)             kubernetes.io/aws-ebs        30h

できました。objectStoreNameには先に作ったオブジェクトストア(CephObjectStoreリソース)の名前を入れます。

次はOBCです。PVCと同じように、こんな感じです。

[utubo@tutsunom ceph]$ cat my-obc-delete.yaml
apiVersion: objectbucket.io/v1alpha1
kind: ObjectBucketClaim
metadata:
  name: objstore-obc-delete
  namespace: default
spec:
  #bucketName:
  generateBucketName: hogehoge
  storageClassName: ceph-bkt-delete
[utubo@tutsunom ceph]$ kubectl create -f my-obc-delete.yaml 
objectbucketclaim.objectbucket.io/my-store-obc-delete created
[utubo@tutsunom ceph]$ kubectl -n default get obc,ob
NAME                                                    AGE
objectbucketclaim.objectbucket.io/objstore-obc-delete   54s

NAME                                                            AGE
objectbucket.objectbucket.io/obc-default-objstore-obc-delete    54s
[utubo@tutsunom ceph]$ kubectl -n default describe obc/objstore-obc-delete
Name:         objstore-obc-delete
Namespace:    default
Labels:       <none>
Annotations:  <none>
API Version:  objectbucket.io/v1alpha1
Kind:         ObjectBucketClaim
Metadata:
  Creation Timestamp:  2019-12-03T08:05:02Z
  Generation:          2
  Resource Version:    446423
  Self Link:           /apis/objectbucket.io/v1alpha1/namespaces/default/objectbucketclaims/objstore-obc-delete
  UID:                 25f8a119-5358-41c4-81c5-13ad833f11d9
Spec:
  Object Bucket Name:    obc-default-objstore-obc-delete
  Additional Config:     <nil>
  Bucket Name:           hogehoge-11231ae6-cca4-4995-ab4c-41390b8b69f1
  Canned Bucket Acl:     
  Generate Bucket Name:  hogehoge
  Ssl:                   false
  Storage Class Name:    ceph-bkt-delete
  Versioned:             false
Status:
  Phase:  bound
Events:   <none>
[utubo@tutsunom ceph]$ kubectl -n default describe ob/obc-default-objstore-obc-delete
Name:         obc-default-objstore-obc-delete
Namespace:    
Labels:       <none>
Annotations:  <none>
API Version:  objectbucket.io/v1alpha1
Kind:         ObjectBucket
Metadata:
  Creation Timestamp:  2019-12-03T08:05:03Z
  Finalizers:
    objectbucket.io/finalizer
  Generation:        1
  Resource Version:  446421
  Self Link:         /apis/objectbucket.io/v1alpha1/objectbuckets/obc-default-objstore-obc-delete
  UID:               6bfaf276-596a-43f1-b89f-72f4e3f064d5
Spec:
  Additional State:
    Ceph User:  ceph-user-1hIDnfeK
  Claim Ref:
    API Version:  objectbucket.io/v1alpha1
    Kind:         ObjectBucketClaim
    Name:         objstore-obc-delete
    Namespace:    default
    UID:          25f8a119-5358-41c4-81c5-13ad833f11d9
  Endpoint:
    Additional Config:  <nil>
    Bucket Host:        rook-ceph-rgw-objstore.rook-ceph
    Bucket Name:        hogehoge-11231ae6-cca4-4995-ab4c-41390b8b69f1
    Bucket Port:        80
    Region:             
    Ssl:                false
    Sub Region:         
  Reclaim Policy:       Delete
  Storage Class Name:   ceph-bkt-delete
Status:
  Conditions:  
  Phase:       bound
Events:        <none>

ほいできた。 Bucketが作られたのですが、ここでSecretを見てみます。

[utubo@tutsunom ceph]$ kubectl -n default get secret
NAME                  TYPE                                  DATA   AGE
default-token-zd6jt   kubernetes.io/service-account-token   3      34h
objstore-obc-delete   Opaque                                2      5m

なんかありますね。そうです、OBと共にS3のAccessKeyとSecretKeyができているのです。これがappの方で出てきます。

[utubo@tutsunom ceph]$ kubectl -n default get secret objstore-obc-delete -o jsonpath="{['data']['AWS_ACCESS_KEY_ID']}" | base64 --decode && echo
NKI5QC8N7FDC93PKU1KP
[utubo@tutsunom ceph]$ kubectl -n default get secret objstore-obc-delete -o jsonpath="{['data']['AWS_SECRET_ACCESS_KEY']}" | base64 --decode && echo
67pnuGUJSMTTtMnF5BSbP9xXmQ8A8nfgaG5FHvOB

OBCのyamlにあるgenerateBucketNameは、作られるBucketに振られる名前のプリフィクスとなります。kubectl describeの出力にあるBucket Nameから確認できます。
Bucket Nameはyamlの中でbucketNameとして固定の名前を振ることができます。が、Bucket名がカブる可能性があるので固定の名前を振るのはあんまりお勧めじゃないっぽい。generateBucketNameを使うとプリフィクス以降はほぼカブらないように自動で振られるのでGood。
「そんな長ったらしいBucket Name覚えるのめんどくせーーーーーーーーーーよ」ですが、Bucket Nameなんて覚えなくていいです。実際にappにOBをattachしてみましょう。

なんか画像をアップロードしたり表示したりするようないい感じのappを見つけたので、そのままパクらせてもらいます。

[utubo@tutsunom ceph]$ cat my-obc-app.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: photo1
  labels:
    name: photo1
spec:
  containers:
  - name: photo1
    image: docker.io/screeley44/photo-gallery:latest
    imagePullPolicy: Always
    envFrom:
    - configMapRef:
        name: objstore-obc-delete
    - secretRef:
        name: objstore-obc-delete
    ports:
    - containerPort: 3000
      protocol: TCP
[utubo@tutsunom ceph]$ kubectl -n default create -f my-obc-app.yaml 
pod/photo1 created

うむ。これで podに入ってenvを見てみましょう。

[utubo@tutsunom ceph]$ kubectl -n default exec -it photo1 bash
root@photo1:/usr/src/app# env | grep -e AWS -e BUCKET
BUCKET_SUBREGION=
BUCKET_HOST=rook-ceph-rgw-objstore.rook-ceph
BUCKET_NAME=hogehoge-11231ae6-cca4-4995-ab4c-41390b8b69f1
BUCKET_PORT=80
AWS_SECRET_ACCESS_KEY=67pnuGUJSMTTtMnF5BSbP9xXmQ8A8nfgaG5FHvOB
BUCKET_REGION=
BUCKET_SSL=false
AWS_ACCESS_KEY_ID=NKI5QC8N7FDC93PKU1KP

ほら、OBCのBucketとSecretがenvに書かれてるでしょう。envFromconfigMapRefsecretRefで指定したOBCからそれぞれの情報が環境変数で取り入れられています。
だからappの中では環境変数で指定すりゃいいので、覚える必要はないっす。

まとめ

今回はRook 1.1の新機能Object Bucket ClaimでBucket作るところまで紹介しました。次回はこれをappから使った時の振る舞いを見てみましょう。 というわけで今日はここまで。

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