Rook/Cephのブロックストレージとオブジェクトストレージを使ってGROWI WikiをKubernetesで動かす

本記事は「[Rookと仲間たち、クラウドネイティブなストレージの Advent Calendar 2020](https://qiita.com/advent-calendar/2020/rook-friends-cns)」の3日目の記事となります。よろしくお願いします。

# 前置き

[~~こばさんにそそのかされて、~~](https://twitter.com/tzkb/status/1323833071070662656)夏ごろに仕事でRook/Cephのオブジェクトストレージを使ったアプリデプロイの機会があったのですが、当時は構築がうまくいかないまま別の作業にさくっと再アサインされて中途半端なままだったので、アドベントカレンダーへのエントリをきっかけに改めてRook/Cephのオブジェクトストレージを試してみました。

[GROWIというWikiツール](https://growi.org/ja/)があるのですがご存じでしょうか。
Markdownで記述でき、画像は(アップロード先S3などの設定を行えば)D&Dやクリップボードから簡単に貼り付けられ、割とサクサク軽快に動作するOSSのWikiツールです。
QiitaやVS CodeのMarkdown Previewの、左側で編集・右側にリアルタイムプレビューの2画面構成に慣れている方は、とくに使いやすいと思います。
[デモサイト](https://demo.growi.org/)もあります。
[GitHubのページ](https://github.com/weseek/growi)では使ってる様子をアニメーションで見れるのでわかりやすいです。

[SaaSとして提供](https://growi.cloud/)されているのですが、実は[Dockerイメージ](https://hub.docker.com/r/weseek/growi)も公開されており、[Docker Compose](https://github.com/weseek/growi-docker-compose)を使用することで、手元の環境で簡単にデプロイすることができます。(https-portalも付属しているので、Let's Encryptを使ったHTTPS公開も簡単にできます)

本記事はこのGROWIを使って

1. Rook/Cephのデプロイ
2. GROWIのDBであるMongoDBでRook/Cephのブロックストレージを使う
3. GROWIで画像アップロードのためのAmazon S3のかわりにRook/Cephのオブジェクトストレージを使う

ということをやって、ストレージ含めてKubernetes上で完結するGROWI Wikiを動かしてみようという内容になります。

# 環境

以下の通りです。

- Kubernetes v1.19.4
    - kubeadmを使って素のCentOS 7.9上に構築
    - いわゆるオンプレKubernetes
    - コントロールプレーン1台・ワーカー2台構成
- MetalLB v0.9.3 (最新はv0.9.5)
- Rook v1.5.1
- GROWI 4.2

MetalLBについては本記事では説明しません。以下参照。
[[Kubernetes] オンプレK8sでもtype:LoadBalancer Serviceが使えるようになるMetalLBを入れてみた - zaki work log](https://zaki-hmkc.hatenablog.com/entry/2020/07/10/235944)

# GROWIで使用するイメージ

Kubernetes用のマニフェスト類は用意されていないので、[docker-compose.yml](https://github.com/weseek/growi-docker-compose/blob/6854f2373eef26e21d3a7b111dc6cfb7b10e13cf/docker-compose.yml)の内容ベースにポーティングしていきます。
現バージョンで使用しているイメージは以下の通り。

| アプリケーション                      | イメージ      |
| ----------------------------- | --------- |
| app (GROWI本体)                 | 自前でビルド    |
| mongo (MongoDB)               | `mongo:4.4` |
| elasticsearch (Elasticsearch) | 自前でビルド    |

自前でビルドしているDockerfileの内容については以下の通り。

| Dockerfile                                                                                                                             | 概要                                                                     |
| -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| [app](https://github.com/weseek/growi-docker-compose/blob/6854f2373eef26e21d3a7b111dc6cfb7b10e13cf/Dockerfile)                         | `weseek/growi:4`をベースに[dockerize](https://github.com/jwilder/dockerize)をインストール                                   |
| [elasticsearch](https://github.com/weseek/growi-docker-compose/blob/3f201d20f3b3057dd6fe8c4b47b6af936adbbe43/elasticsearch/Dockerfile) | `docker.elastic.co/elasticsearch/elasticsearch:6.8.10`をベースに日本語検索系のプラグイン追加 |

`dockerize`のインストールについては、[MongoDBとElasticsearchの起動を待つために使用](https://github.com/weseek/growi-docker-compose/blob/6854f2373eef26e21d3a7b111dc6cfb7b10e13cf/docker-compose.yml#L29-L32)しているので、Kubernetes環境ではlivenessProbeで代用でもよさそうなので不要。
また、[Elasticsearchは実はオプション](https://github.com/weseek/growi#optional-dependencies)なので、今回は省略。

よって、使うイメージは以下の2つ。

- GROWI本体
    - [weseek/growi:4](https://hub.docker.com/r/weseek/growi)
- MongoDB
    - [mongo:4.4](https://hub.docker.com/_/mongo)

Elasticsearch込みの構成については別途やってみる予定。
↓
書きました。
[Skaffoldを使ってGROWI on K8s用のElasticsearchとHackMDをビルド＆デプロイ - Qiita](https://qiita.com/zaki-lknr/items/58cbd858ceff40c42323)

# Rook/Cephのデプロイ

つい先日v1.5がリリースされましたが、記事作成時点の最新バージョンはv1.5.1です。
[Quickstart -> Ceph Storage](https://rook.io/docs/rook/v1.5/ceph-quickstart.html)のTL;DRの通りでよいです。

ポイントは以下の通り。

1. 全ワーカーノードにOSにマウントされていないディスクを接続
2. [`crds.yaml`](https://github.com/rook/rook/blob/v1.5.1/cluster/examples/kubernetes/ceph/crds.yaml)を使ってカスタムリソース作成
3. [`common.yaml`](https://github.com/rook/rook/blob/v1.5.1/cluster/examples/kubernetes/ceph/common.yaml)を使ってロールやアカウント類を作成
4. [`operator.yaml`](https://github.com/rook/rook/blob/v1.5.1/cluster/examples/kubernetes/ceph/operator.yaml)を使ってOperatorを作成
5. [`cluster.yaml`](https://github.com/rook/rook/blob/v1.5.1/cluster/examples/kubernetes/ceph/cluster.yaml)を使ってCephクラスター作成
    - ※ 検証環境など小規模であれば代わりに[`cluster-test.yaml`](https://github.com/rook/rook/blob/v1.5.1/cluster/examples/kubernetes/ceph/cluster-test.yaml)を使用

## Rook Operatorをデプロイする

前述ポイント2~4のマニフェストに対して`kubectl create -f`でリソースを作成します。

```
[zaki@cloud-dev ceph]$ kubectl get pod -n rook-ceph
NAME                                  READY   STATUS    RESTARTS   AGE
rook-ceph-operator-78554769bd-9dw5f   1/1     Running   0          23s
```

ちなみにこのRook Operatorのデプロイは、[Helmを使ってデプロイ](https://rook.io/docs/rook/v1.5/helm-operator.html)もできます。

## Cephクラスタを作成する

前述ポイント5のリソース作成。今回は[`cluster-test.yaml`](https://github.com/rook/rook/blob/v1.5.1/cluster/examples/kubernetes/ceph/cluster-test.yaml)を使用します。

```
[zaki@cloud-dev ceph]$ kc get pod -n rook-ceph
NAME                                                    READY   STATUS              RESTARTS   AGE
csi-cephfsplugin-6msxk                                  0/3     ContainerCreating   0          45s
csi-cephfsplugin-provisioner-7dc78747bf-5plfv           0/6     ContainerCreating   0          45s
csi-cephfsplugin-provisioner-7dc78747bf-w7ml5           6/6     Running             0          45s
csi-cephfsplugin-vdxwx                                  3/3     Running             0          45s
csi-rbdplugin-fffpb                                     3/3     Running             0          46s
csi-rbdplugin-provisioner-54d48757b4-l2pxr              0/6     ContainerCreating   0          46s
csi-rbdplugin-provisioner-54d48757b4-wcx5c              6/6     Running             0          46s
csi-rbdplugin-r7dt7                                     0/3     ContainerCreating   0          46s
rook-ceph-mgr-a-679f49c584-qfnqp                        1/1     Running             0          87s
rook-ceph-mon-a-6776558969-9cxnm                        1/1     Running             0          97s
rook-ceph-operator-78554769bd-9dw5f                     1/1     Running             0          4m44s
rook-ceph-osd-0-675958d76b-bzt6f                        1/1     Running             0          68s
rook-ceph-osd-1-f88668f5d-fb4dm                         1/1     Running             0          57s
rook-ceph-osd-prepare-k8s-worker01.esxi.jp-z.jp-g56gz   0/1     Completed           0          85s
rook-ceph-osd-prepare-k8s-worker02.esxi.jp-z.jp-dffhx   0/1     Completed           0          85s
```

これは別ターミナルで`kubectl get pod -n rook-ceph -w`しておくと楽しい。

Cephクラスタが作成されたらCephClusterリソースを`get`するとヘルス状態を確認できます。`HEALTH_OK`であればOK

```
[zaki@cloud-dev ceph]$ kubectl -n rook-ceph get cephcluster
NAME         DATADIRHOSTPATH   MONCOUNT   AGE     PHASE   MESSAGE                        HEALTH
my-cluster   /var/lib/rook     1          2m57s   Ready   Cluster created successfully   HEALTH_OK
```

Rook OperatorをHelmでインストールした場合でも、Cephクラスタの作成はチャートが用意されてないためマニフェストからリソース生成します。

## pvを使用するためのStorageClassを作成

今回はMongoDBでpvを使用します。
pvをRook/Cephから動的にプロビジョニングするためにStorageClassを作成します。

[Rook Docs / Block Storage](https://rook.io/docs/rook/v1.5/ceph-block.html)

StorageClassのマニフェストは[`csi/rbd/storageclass.yaml`](https://github.com/rook/rook/blob/v1.5.1/cluster/examples/kubernetes/ceph/csi/rbd/storageclass.yaml)にあるのでそれを使います。
が、今回は検証環境なので、[`csi/rbd/storageclass-test.yaml`](https://github.com/rook/rook/blob/v1.5.1/cluster/examples/kubernetes/ceph/csi/rbd/storageclass-test.yaml)を使用。

```
[zaki@cloud-dev ceph]$ kubectl create -f csi/rbd/storageclass-test.yaml
cephblockpool.ceph.rook.io/replicapool created
storageclass.storage.k8s.io/rook-ceph-block created
[zaki@cloud-dev ceph]$ kubectl get sc
NAME              PROVISIONER                  RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
rook-ceph-block   rook-ceph.rbd.csi.ceph.com   Delete          Immediate           true                   5s
```

## オブジェクトストレージ

前置きが長くなりましたが、本記事のメインです。
今回はGROWIの画像アップロード用に使用します。

[Rook Docs / Object Storage](https://rook.io/docs/rook/v1.5/ceph-object.html)

### オブジェクトストアの作成

まずはCephObjectStoreリソースを作成します。
[`object.yaml`](https://github.com/rook/rook/blob/v1.5.1/cluster/examples/kubernetes/ceph/object.yaml)が用意されていますが、こちらも検証用に[`object-test.yaml`](https://github.com/rook/rook/blob/v1.5.1/cluster/examples/kubernetes/ceph/object-test.yaml)をデプロイします。

```
[zaki@cloud-dev ceph]$ kubectl apply -f object-test.yaml
cephobjectstore.ceph.rook.io/my-store created
[zaki@cloud-dev ceph]$ kubectl get cephobjectstore -n rook-ceph
NAME       AGE
my-store   29s
```

少し経つと、CephのRGWサービスのpodとserviceが作成されます。
これがS3 API互換のオブジェクトストレージのデータストアになります。(という表現で合ってると思うけど…たぶん。。)

```
[zaki@cloud-dev ceph]$ kubectl get pod,svc -n rook-ceph -l ceph_daemon_type=rgw
NAME                                           READY   STATUS    RESTARTS   AGE
pod/rook-ceph-rgw-my-store-a-d7857c998-8sp6g   1/1     Running   0          116s

NAME                             TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
service/rook-ceph-rgw-my-store   ClusterIP   10.97.160.63   <none>        80/TCP    2m39s
```

### バケットの作成

Amazon S3でもバケットを作ってオブジェクトストレージを利用しますが、Rook/Cephでも同じようにバケットを作成します。
この「バケットの作成」は、Rook/Ceph環境では「StorageClassの作成」によって行われます。

[`storageclass-bucket-delete.yaml`](https://github.com/rook/rook/blob/v1.5.1/cluster/examples/kubernetes/ceph/storageclass-bucket-delete.yaml)と[`storageclass-bucket-retain.yaml`](https://github.com/rook/rook/blob/v1.5.1/cluster/examples/kubernetes/ceph/storageclass-bucket-retain.yaml)が用意されているので、今回はdeleteの方を使ってみます。

```
[zaki@cloud-dev ceph]$ kubectl apply -f storageclass-bucket-delete.yaml
storageclass.storage.k8s.io/rook-ceph-delete-bucket created
[zaki@cloud-dev ceph]$ kubectl get sc
NAME                      PROVISIONER                     RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
rook-ceph-block           rook-ceph.rbd.csi.ceph.com      Delete          Immediate           true                   28m
rook-ceph-delete-bucket   rook-ceph.ceph.rook.io/bucket   Delete          Immediate           false                  5s
```

これでストレージ側の準備ができました。

…と言いたいとこですが、このバケットに対するClaimも作成します。
PersistentVolumeに対するPersistentVolumeClaimと同じようなものです。

### ObjectBucketClaimの作成

PersistentVolumeに対するPersistentVolumeClaimに相当する、オブジェクトストア用StorageClassに要求を出すためのObjectBucketClaimを作成します。
ObjectBucketClaimを作成することで、AWS S3と同様にRook/Ceph上のバケットにアクセスするためのエンドポイントやアクセスキーが提供されます。

[`object-bucket-claim-delete.yaml`](https://github.com/rook/rook/blob/v1.5.1/cluster/examples/kubernetes/ceph/object-bucket-claim-delete.yaml)と[`object-bucket-claim-retain.yaml`](https://github.com/rook/rook/blob/v1.5.1/cluster/examples/kubernetes/ceph/object-bucket-claim-retain.yaml)が用意されています。今回はバケット作成時にdeleteを使用しているので、ここでもdeleteを使います。

なお、このリソースはPersistentVolumeClaimと同様に、バケットを使用するアプリケーションのネームスペースに作成します。
なので、この後のGROWIをデプロイするためのネームスペースをここで作っておきます。

```
[zaki@cloud-dev ceph]$ kubectl create namespace growi-on-k8s
namespace/growi-on-k8s created
```

ObjectBucketClaimを作成すると、ConfigMapとSecretリソースも作成されます。

```
[zaki@cloud-dev ceph]$ kubectl apply -f object-bucket-claim-delete.yaml -n growi-on-k8s
objectbucketclaim.objectbucket.io/ceph-delete-bucket created
[zaki@cloud-dev ceph]$ kubectl get objectbucket -n growi-on-k8s
NAME                                  AGE
obc-growi-on-k8s-ceph-delete-bucket   3s
[zaki@cloud-dev ceph]$ kubectl get cm,secret -n growi-on-k8s
NAME                           DATA   AGE
configmap/ceph-delete-bucket   5      15s
configmap/istio-ca-root-cert   1      3m28s

NAME                         TYPE                                  DATA   AGE
secret/ceph-delete-bucket    Opaque                                2      15s
secret/default-token-mp6kq   kubernetes.io/service-account-token   3      3m28s
```

```yaml
[zaki@cloud-dev ceph]$ kc get cm -n growi-on-k8s ceph-delete-bucket -o yaml
apiVersion: v1
data:
  BUCKET_HOST: rook-ceph-rgw-my-store.rook-ceph.svc
  BUCKET_NAME: ceph-bkt-4e095ccf-dbb0-41b6-babc-e646d441d548
  BUCKET_PORT: "80"
  BUCKET_REGION: us-east-1
  BUCKET_SUBREGION: ""
kind: ConfigMap
metadata:
```

```yaml
[zaki@cloud-dev ceph]$ kc get secrets -n growi-on-k8s ceph-delete-bucket -o yaml
apiVersion: v1
data:
  AWS_ACCESS_KEY_ID: NThMME1OU1UxNzJUQjdOQjU0N0k=
  AWS_SECRET_ACCESS_KEY: MlFneDdmOHNTNFFqU0E5bmZvdG5EWTVjRklQVmhjNlVaa2VwRHlVSQ==
kind: Secret
metadata:
```

S3を使ったことがある方であれば、このConfigMapとSecretの内容で何をどう使えばよいかわかると思いますが、アプリケーションでS3の設定を行う箇所でこれらの値を設定すればOKです。
__※ Secretの値は、Base64エンコードされているので、`base64 -d`などでデコードして利用すること__

# GROWIのデプロイ

いよいよオブジェクトストレージを使用するアプリケーション部分です。

再掲ですが、使用するイメージは以下の通り。

- GROWI本体
    - [weseek/growi:4](https://hub.docker.com/r/weseek/growi)
- MongoDB
    - [mongo:4.4](https://hub.docker.com/_/mongo)

また、[作成したマニフェストファイルは、GitHubに置いています。](https://github.com/zaki-lknr/k8s-samples/blob/master/growi-on-k8s/manifests/growi-on-k8s.yaml)

## MongoDB

ストレージ使うのでStatefulSetで定義します。
何気に私がStatefulSetをゼロから書くのが今回が初めてだったりします。(余談)

[Docker ComposeではVolumes設定が2か所で行われている](https://github.com/weseek/growi-docker-compose/blob/6854f2373eef26e21d3a7b111dc6cfb7b10e13cf/docker-compose.yml#L41-L43)ので、それぞれのパスに対して`volumeClaimTemplates`の設定を行います。

```yaml:docker-compose.yml
  mongo:
    image: mongo:4.4
    restart: unless-stopped
    volumes:
      - mongo_configdb:/data/configdb
      - mongo_db:/data/db
```

```yaml:growi-on-k8s.yaml
      containers:
      - name: growi-mongo
        image: mongo:4.4
        volumeMounts:
        - name: mongo-config
          mountPath: /data/configdb
        - name: mongo-db
          mountPath: /data/db
  volumeClaimTemplates:
  - metadata:
      name: mongo-config
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: rook-ceph-block
      resources:
        requests:
          storage: 256Mi
  - metadata:
      name: mongo-db
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: rook-ceph-block
      resources:
        requests:
          storage: 4Gi
```

## GROWI本体

Docker Composeの設定をベースに[環境変数をDeploymentに設定](https://github.com/zaki-lknr/k8s-samples/blob/master/growi-on-k8s/manifests/growi-on-k8s.yaml#L21-L25)します。
また、[起動チェックのためにLivenessProbe](https://github.com/zaki-lknr/k8s-samples/blob/master/growi-on-k8s/manifests/growi-on-k8s.yaml#L26-L34)も追加します。

```
[zaki@cloud-dev growi-on-k8s]$ kubectl apply -f manifests/growi-on-k8s.yaml -n growi-on-k8s
deployment.apps/growi-app created
service/growi-app created
statefulset.apps/growi-mongo created
service/growi-mongo created
```

```
[zaki@cloud-dev growi-on-k8s]$ kubectl get pod,svc -n growi-on-k8s
NAME                            READY   STATUS    RESTARTS   AGE
pod/growi-app-fd6fcdd9d-vmtn2   1/1     Running   1          108s
pod/growi-mongo-0               1/1     Running   0          108s

NAME                  TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)          AGE
service/growi-app     LoadBalancer   10.110.19.195    192.168.0.185   3000:32510/TCP   108s
service/growi-mongo   ClusterIP      10.111.134.164   <none>          27017/TCP        108s
```

```
[zaki@cloud-dev growi-on-k8s]$ kubectl get pvc -n growi-on-k8s
NAME                         STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
mongo-config-growi-mongo-0   Bound    pvc-6b29acb4-bad6-4b01-9135-4302bf1f35d3   256Mi      RWO            rook-ceph-block   87s
mongo-db-growi-mongo-0       Bound    pvc-21ea3319-5487-4b10-83d3-a9c076d30b28   4Gi        RWO            rook-ceph-block   87s
[zaki@cloud-dev growi-on-k8s]$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                     STORAGECLASS      REASON   AGE
pvc-21ea3319-5487-4b10-83d3-a9c076d30b28   4Gi        RWO            Delete           Bound    growi-on-k8s/mongo-db-growi-mongo-0       rook-ceph-block            88s
pvc-6b29acb4-bad6-4b01-9135-4302bf1f35d3   256Mi      RWO            Delete           Bound    growi-on-k8s/mongo-config-growi-mongo-0   rook-ceph-block            88s
```

動いてますね。

## webアクセスと初期設定

この構成ではMetalLBを使って`type:LoadBalancer`のServiceをデプロイできるようにしているので、表示されているExternal-IPへブラウザでアクセスします。(ない場合は`port-forward`などを使用してください)

上の例でいえば

```
[zaki@cloud-dev growi-on-k8s]$ kc get svc -n growi-on-k8s
NAME          TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)          AGE
growi-app     LoadBalancer   10.110.19.195    192.168.0.185   3000:32510/TCP   5m13s
growi-mongo   ClusterIP      10.111.134.164   <none>          27017/TCP        5m13s
```

なので、ブラウザで http://192.168.0.185:3000 にアクセスします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/885294cb-a8e9-8e92-0dc9-5941fb3edac1.png)

無事に起動・接続できました。

以下はGROWIの使い方になりますが、初期アクセス時には管理ユーザーを作成するので、好きなUserIDを入力してアカウント作成・ログインします。
ログインすると初期設定があり、Wiki名・ドメイン名(今回はIPアドレスしかないので、IPアドレスを入力)などを入力します。

ちなみに初期状態だと、Wikiの編集画面へ画像ファイルをD&Dしようとしても、「File uploading is disabled」と表示されている通りアップロードはできません。

![2020-12-02_09h41_58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/4c21dd38-c0c6-6d19-9b25-1ea1ec9be86a.png)

## S3バケット設定

S3バケット設定は、Wiki管理メニューの「アプリ設定」の「ファイルアップロード設定」の以下の場所。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/dc517e1f-6cfd-4028-89ff-09e79dc76759.png)

この各設定欄に、ObjectBucketClaimによって作成されたConfigMapとSecretの内容を入力して「更新」ボタン押下します。

```yaml
[zaki@cloud-dev ceph]$ kc get cm -n growi-on-k8s ceph-delete-bucket -o yaml
apiVersion: v1
data:
  BUCKET_HOST: rook-ceph-rgw-my-store.rook-ceph.svc
  BUCKET_NAME: ceph-bkt-4e095ccf-dbb0-41b6-babc-e646d441d548
  BUCKET_PORT: "80"
  BUCKET_REGION: us-east-1
  BUCKET_SUBREGION: ""
kind: ConfigMap
metadata:
```

```yaml
[zaki@cloud-dev ceph]$ kc get secrets -n growi-on-k8s ceph-delete-bucket -o yaml
apiVersion: v1
data:
  AWS_ACCESS_KEY_ID: NThMME1OU1UxNzJUQjdOQjU0N0k=
  AWS_SECRET_ACCESS_KEY: MlFneDdmOHNTNFFqU0E5bmZvdG5EWTVjRklQVmhjNlVaa2VwRHlVSQ==
kind: Secret
metadata:
```

| 設定項目              | 入力値                                             |
| ----------------- | ----------------------------------------------- |
| リージョン             | `us-east-1`                                     |
| カスタムエンドポイント       | `http://rook-ceph-rgw-my-store.rook-ceph.svc`   |
| バケット名             | `ceph-bkt-4e095ccf-dbb0-41b6-babc-e646d441d548` |
| Access Key ID     | `AWS_ACCESS_KEY_ID`の値をBase64デコード                |
| Secret access key | `AWS_SECRET_ACCESS_KEY`の値をBase64デコード            |

アップロード用S3が設定済みであれば、編集画面へ画像ファイルをD&Dすると

![2020-12-02_09h41_05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/ae40d43e-41b6-db7b-5fcb-4e67c7101806.png)

この通り、アップロードできるようになります。

![2020-11-27_22h42_52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/3f4d57ea-6bda-ebf1-413f-8704f8ebd30e.png)

# ceph df

[Rook Docs / Rook Toolbox](https://rook.io/docs/rook/v1.5/ceph-toolbox.html)

`toolbox.yaml`をデプロイすると、`ceph`コマンドが使用できるようになります。
画像を1つアップロードした状態だとこの通り。

```
[zaki@cloud-dev ceph]$ kc exec -it -n rook-ceph $(kc get pod -n rook-ceph -l app=rook-ceph-tools -o 'jsonpath={.items[].metadata.name}') -- ceph df
--- RAW STORAGE ---
CLASS  SIZE    AVAIL   USED     RAW USED  %RAW USED
ssd    40 GiB  38 GiB  103 MiB   2.1 GiB       5.25
TOTAL  40 GiB  38 GiB  103 MiB   2.1 GiB       5.25

--- POOLS ---
POOL                         ID  STORED   OBJECTS  USED     %USED  MAX AVAIL
device_health_metrics         1      0 B        0      0 B      0     36 GiB
replicapool                   3  101 MiB       64  101 MiB   0.27     36 GiB
my-store.rgw.control          4  3.9 KiB        8  3.9 KiB      0     36 GiB
my-store.rgw.meta             5  2.7 KiB       12   40 KiB      0     36 GiB
my-store.rgw.log              6  3.6 KiB      211  136 KiB      0     36 GiB
my-store.rgw.buckets.index    7  190 KiB       22  190 KiB      0     36 GiB
my-store.rgw.buckets.non-ec   8      0 B        0      0 B      0     36 GiB
.rgw.root                     9  3.8 KiB       16   60 KiB      0     36 GiB
my-store.rgw.buckets.data    10   74 KiB        1   76 KiB      0     36 GiB
```

最後の行の`my-store.rgw.buckets.data`に、`OBJECTS: 1`となってます。

画像の更に1ファイル追加でアップロードすると

```
--- RAW STORAGE ---
CLASS  SIZE    AVAIL   USED     RAW USED  %RAW USED
ssd    40 GiB  38 GiB  104 MiB   2.1 GiB       5.25
TOTAL  40 GiB  38 GiB  104 MiB   2.1 GiB       5.25

--- POOLS ---
POOL                         ID  STORED   OBJECTS  USED     %USED  MAX AVAIL
device_health_metrics         1      0 B        0      0 B      0     36 GiB
replicapool                   3  102 MiB       64  102 MiB   0.28     36 GiB
my-store.rgw.control          4  3.9 KiB        8  3.9 KiB      0     36 GiB
my-store.rgw.meta             5  2.7 KiB       12   40 KiB      0     36 GiB
my-store.rgw.log              6  3.6 KiB      211  136 KiB      0     36 GiB
my-store.rgw.buckets.index    7  190 KiB       22  190 KiB      0     36 GiB
my-store.rgw.buckets.non-ec   8      0 B        0      0 B      0     36 GiB
.rgw.root                     9  3.8 KiB       16   60 KiB      0     36 GiB
my-store.rgw.buckets.data    10   87 KiB        2   92 KiB      0     36 GiB
```

`2`に増えました。
ちなみにこの数値はファイル数とイコールでなく、ファイルサイズが大きいと分割されるそうです。

> RGWで置かれるデータはデフォルト4MiBでストライプされるので、14MiBのファイルは4つのオブジェクトにストライプされて置かれてるってことです。
> 
> 「ストレージオーケストレーター Rook : 第8話 宿命のObject Bucket(Bパート)」より
> https://rheb.hatenablog.com/entry/2019/12/05/rook_ep8

# 関連・参考情報

夏ごろはうまくいかなかったけど改めて試すとすんなり動いてやったー！という感じでしたが、去年のアドカレのうつぼさんの記事を見返してみると、やっていることはほぼほぼ同じでした(;'∀')

- [[Kubernetes] オンプレK8sでもRook-Cephを使ってpvのダイナミックプロビジョニングを試してみる - zaki work log](https://zaki-hmkc.hatenablog.com/entry/2020/07/05/170830)
- [ストレージオーケストレーター Rook : 第7話 宿命のObject Bucket(Aパート) - 赤帽エンジニアブログ](https://rheb.hatenablog.com/entry/2019/12/04/rook_ep7)
- [ストレージオーケストレーター Rook : 第8話 宿命のObject Bucket(Bパート) - 赤帽エンジニアブログ](https://rheb.hatenablog.com/entry/2019/12/05/rook_ep8)
- [「Ceph、完全に理解した」って Tweetする為のセッション ｰ Ceph 101 ｰ - Speaker Deck](https://speakerdeck.com/tsukaman/ceph-wan-quan-nili-jie-sita-tute-tweetsuruwei-falsesetusiyon-ceph-101)

それから、Rookのオブジェクトストレージ周りに動きがあったようで、遠くない未来には扱い方が変わるかもしれないようです。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">もともとRookで使っていたPV,PVCのオブジェクトストレージ版ObjectBucket(OB)、ObjectBucketClaim(OBC)を標準化しようとしていろいろあって全然別のインタフェースになった、という経緯があります。つまりRookも数年後にはオブジェクトストレージを扱うインタフェースが変わります</p>&mdash; sat🚪 (@satoru_takeuchi) <a href="https://twitter.com/satoru_takeuchi/status/1329332713279475714?ref_src=twsrc%5Etfw">November 19, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>