この記事は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
なんかありますね。そうです、OBCと共に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から使った時の振る舞いを見てみましょう。 というわけで今日はここまで。