この記事はRookだらけの Advent Calendar 2019 4日目の記事です。
ども。レッドハットでストレージを中心にクラウドインフラを生業にしているウツノミヤです。
さあイッテンヨンまで後1ヶ月になりました。1.5のIWGPヘビーの方が格上なのにどうも1.4のIWGP ICの方が盛り上がってるんですよね…リマッチなのに。このあたりいつも饒舌な内藤選手が何も喋らないことでうまくメディアと世論をコントロールしてる感じがします。あとみんな二冠二冠と言い出したから、IWGPヘビー級のベルトが二冠のための手段に扱われてちょっと相場が下がってる感があるのも否めないかな。まあこれは個人的なアレですけど。
まあプロレスの話はこれくらいにしといて、Rookアドカレ、見ていただいていますか?
今年はじめてアドカレやってますが、もう4日目にしてヒイヒイ言っとるウツノミヤです。全部俺系の人は引き出しがヤバいですね。ほんとに敬服します。
引き続き、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でアクセスするためのインターフェースとなるコンポーネントがあります。
それではこいつを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に書かれてるでしょう。envFrom
のconfigMapRef
とsecretRef
で指定したOBCからそれぞれの情報が環境変数で取り入れられています。
だからappの中では環境変数で指定すりゃいいので、覚える必要はないっす。
まとめ
今回はRook 1.1の新機能Object Bucket ClaimでBucket作るところまで紹介しました。次回はこれをappから使った時の振る舞いを見てみましょう。 というわけで今日はここまで。