KubernetesにおけるContainer Object Storage Interface (COSI)の概要

# はじめに

本ドキュメントでは、Kubernetes v1.21 にAlphaとして登場予定のContainer Object Storage Interface(COSI)について紹介します。
なお、本内容は2020/10/20にマージされたKEPの内容をベースにしており、Kubernetes v1.21 or later向けには大きく変更される可能性があります。

:memo: KubernetesコミュニティのSIG-StorageのCo-Chairの発表ではCOSIを「コーズィー」と発音していました。

# COSIの概要

これまでKubernetesではブロックストレージとファイルストレージをターゲットに[CSI](https://qiita.com/ysakashita/items/4b56c2577f67f1b141e5)が提供されてきました。
これに加え Kubernetes v1.21 からはAlphaとしてオブジェクトストレージをターゲットにしたCOSIが登場する予定です。
ブロックストレージ・ファイルストレージ・オブジェクトストレージの違いは下図をご参照ください。

![objectstorage.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/229880/e4080ec9-ab00-e7d7-0578-beac1284fb65.png)


SIG-Storageでは、COSIをサポートすることで、あるKubernetesクラスタ上で動作しているPod, DeploymentやConfigMapなどのリソースを別のKubernetesクラスタへオブジェクトストレージ経由で移動(マイグレーション)するようなユースケースも視野にいれ、COSIの策定を進めています。(※)
2020/12時点でのステータスとしては、2020/10/20にCOSIのKEPがマージされ、KubeCon+CloudNativeCon NA 2020 Virtual でもCOSIについて発表されました。仕様は叩き台ができあがり、サンプル実装も並行して開発中([cosi-controller-manager](https://github.com/container-object-storage-interface/cosi-controller-manager), [consi-provisioner-sidecar](https://github.com/container-object-storage-interface/cosi-provisioner-sidecar), [api](https://github.com/container-object-storage-interface/api))の状況です。

※ Kubernetes クラスタ間のマイグレーションなどはKubernetesコミュニティのData Protection WGでも議論されています。

# APIs

COSIではAPIとして以下の6つのAPIが提供される予定です。
なお、オブジェクトストレージでは、データの格納先をBucketという単位で管理します。ブロックストレージやファイルストレージのVolume相当だと思ってもらっても良いかと思います。

- Storage APIs
  - BucketRequest (Bucketの要求仕様、CSIのPVCに相当)
  - Bucket   (Bucket、CSIのPVに相当)
  - BucketClass (Bucketの生成元、CSIのStorageClassに相当)
- Access APIs
  - BucketAccessRequest (Bucketへのアクセス権(クレデンシャル)の要求仕様）
  - BucketAccess   (Bucketへのアクセス権(クレデンシャル))
  - BucketAccessClass  (Bucketへのアクセス権(クレデンシャル)の生成元)

COSIのアーキテクチャについては下図をご参照ください。

![Arch](https://raw.githubusercontent.com/yard-turkey/enhancements/e2c34d419f4eef2003ec7a30af744a082d5423ac/keps/sig-storage/1979-object-storage-support/images/arch.png)

各ストレージベンダは、COSI-Sidecar-ControllerからGRPC経由で呼びされるVendor-Provisionerを独自に開発することになります。

以下に、それぞれのAPIについて詳細に紹介します。


## BucketRequest  (BR)
BucketRequestは、Bucketの生成やBucketへのアクセス時に必要となるクレデンシャルの生成する際の要求をまとめたリソースです。
Namespaceごとのリソースになります。
BucketRequestとBucketは1:1でマッピングされます。

以下に、リソースの定義を示します。

```YAML
apiVersion: cosi.io/v1alpha1
kind: BucketRequest
metadata:
  name:
  namespace:
  labels:
      cosi.io/provisioner: [1] 
  finalizers:
    - cosi.io/finalizer [2]
spec:
  protocol:
    name: [3]
    version: [4]
  bucketPrefix: [5]
  bucketClassName: [6]
  bucketInstanceName: [7]
status:
  bucketAvailable: [8]
```

1. Provisionerの名前
2. 削除時に呼び出されるFinalizer
3. プロトコル名 (例: `S3`, `gs`, `azureBlob`など)
4. プロトコルバージョン　(例: `S3`の場合、`v2` や`v4`など) 
5. Buketにつける接頭辞 
6. Bucketの生成元となるBucketClassの名前
7. Buketの名前 
8. `True`の場合、Bucketが生成済み

## Bucket
データの格納先となるBuketを示すリソースです。
Kubernetesクラスタでユニークなリソースになります(cluster-scoped)。
また、COSIでもCSIと同じくDynamic Volume Provisioning相当の機能があります。
COSIではDynamic Volume Provisioningに相当するものを`greenfiled`, Manually Volume Provisioningに相当するものを `brownfield` と呼んでいます。
つまり、`greenfiled`の場合、Bucketのリソースは、BucketRequestとBuketClassにより自動生成されます。

以下に、リソースの定義を示します。

```YAML
apiVersion: cosi.io/v1alpha1
kind: Bucket
metadata:
  name: [1]
  labels: [2]
    cosi.io/provisioner:
    cosi.io/bucket-prefix:
  finalizers:
    - cosi.io/finalizer [3]
spec:
  provisioner: [4]
  retentionPolicy: [5]
  anonymousAccessMode: [6]
  bucketClassName: [7]
  bucketRequest: [8]
  allowedNamespaces: [9]
    - name:
  protocol: [10]
    name:
    version:
    azureBlob:
      containerName:
      storageAccount:
    s3:
      endpoint:
      bucketName:
      region:
      signatureVersion:
    gs:
      bucketName:
      privateKeyName:
      projectId:
      serviceAccount:
  parameters: [11]
status:
  bucketAvailable: [12]
```

1. Bucket名
2. Provisionerの名前とBucketの接頭辞。
3. 削除時に呼び出されるFinalizer
4. Provisionerの名前(BucketClassから伝搬)
5. BucketRequestの削除時のポリシー
   - `Retain`(default): Bucketとデータは保存されたまま(削除されない)
   - `Delete`: BucketRequestが削除されるとBucketも削除
6. Bucketへアクセス時にクレデンシャルが指定されていない時の(uncredentialed access)のアクセス権限
   - `private` (default): Bucketへのアクセス禁止
   - `publicReadOnly`: Readのみ可能(Listも可)
   - `publicWriteOnly`: Writeのみ可能
   - `publicReadWrite`: Read/Write
7. 生成元のBuketClass名
8. マッピングされたBucketRequest
9. アクセスが許可されているNamespace
10. プロトコルの情報(プロトコル名やバージョンなど)
11. パラメータ。BucketClassから伝搬
12. `True`の場合、Bucketが生成済み

## BucketClass

Bucketの生成元となるリソースです。
Kubernetesクラスタでユニークなリソースになります(cluster-scoped)。

以下にリソースの定義を示します。

```YAML
apiVersion: cosi.io/v1alpha1
kind: BucketClass
metadata:
  name: 
provisioner: [1]
isDefaultBucketClass: [2]
protocol:
  name: [3]
  version: [4]
anonymousAccessMode: [5]
retentionPolicy: {"Delete", "Retain"} [6]
allowedNamespaces: [7]
  - name:
parameters: [8]
```

1. Provisioner名
2. defaultのBucketClassか否かを示すフラグ。`true`の場合、BucketRequestにて`bucketClassName`が省略された場合、自動で選択される(デフォルト値は`false`)
3. プロトコル名 (例: `S3`, `gs`, `azureBlob`など)
4. プロトコルバージョン (例: `S3`の場合、`v2` や`v4`など)
5. Bucketへアクセス時にクレデンシャルが指定されていない時の(uncredentialed access)のアクセス権限
   - `private` (default): Bucketへのアクセス禁止
   - `publicReadOnly`: Readのみ可能(Listも可)
   - `publicWriteOnly`: Writeのみ可能
   - `publicReadWrite`: Read/Write

6. BucketRequestの削除時のポリシー
   - `Retain`(default): Bucketとデータは保存されたまま(削除されない)
   - `Delete`: BucketRequestが削除されるとBucketも削除 
7. アクセスが許可されているNamespace
8. パラメータ。Provisionerへ渡す設定値

## BucketAccessRequest (BAR)
Buketへのアクセスの要求をまとめたリソースです。
BucketRequestと同じNamespaceに作成します。

以下に、リソースの定義を示します。

```YAML
apiVersion: cosi.io/v1alpha1
kind: BucketAccessRequest
metadata:
  name:
  namespace:
  labels:
    cosi.io/provisioner: [1]
  finalizers:
  - cosi.io/finalizer [2]
spec:
  serviceAccountName: [3]
  bucketRequestName: [4]
  bucketAccessClassName: [5]
  bucketAccessName: [6]
status:
  accessGranted: [7]
```

1. Provisionerの名前
2. 削除時に呼び出されるFinalizer
3. アクセス権を与えるKubernetesのService Account名
4. 関連付けるBucketRequest名
5. BuketAccessの生成元となるBucketAccessClass名　
6. BucketAccess名 (COSIにより自動で設定)
7. `true`の場合、bucketにアクセス権が付与されています

## BucketAccess (BA)
Buketへのアクセス権限を示すリソースです。
Kubernetesクラスタでユニークなリソースになります(cluster-scoped)。
BucketAccessRequestとBucketAccessは1:1でマッピングされます。

以下にリソースの定義を示します。

```YAML
apiVersion: cosi.io/v1alpha1
kind: BucketAccess
metadata: 
  name: [1]
  labels:
    cosi.io/provisioner: [2]
  finalizers:
  - cosi.io/finalizer [3]
 spec:
  provisioner: [4]
  bucketInstanceName: [5]
  bucketAccessRequest: [6]
  serviceAccount: [7]
  mintedSecretName: [8]
  policyActionsConfigMapData: [9]
  principal: [10]
  parameters: [11]
 status:
   accessGranted: [12]
```

1. BucketAccess名
2. Provisionerの名前 
3. 削除時に呼び出されるFinalizer
4. Provisionerの名前(BucketAccessClassから伝搬)
5. BucketAccessに関連付けられたBuketリソースの名前
6. マッピングされたBucketAccessRequestへの参照
7. BucketAccessRequestで指定されたKubernetesのService Accountへの参照
8. アクセス時のクレデンシャル情報を含むSecretの名前　(provisionerがSecretを生成)
9. 特定のユーザーIDに対するポリシーアクション。
例

```
   {“Effect”:“Allow”,“Action”:“s3:PutObject”,“Resource”:“arn:aws:s3:::profilepics/*“},
   {“Effect”:“Allow”,“Action”:“s3:GetObject”,“Resource”:“arn:aws:s3:::profilepics/*“},
   {“Effect”:“Deny”,“Action”:“s3:*“,”NotResource”:“arn:aws:s3:::profilepics/*“}
```

10. Buketにアクセスできるユーザを一意に識別するためのオブジェクトストレージプロバイダーのユーザー名/アクセスキー
11. パラメータ。BuketAccessClassから伝搬
12. `true`の場合、Bucketへのアクセスが可能

## BucketAccessClass
BucketAccessの生成元となるリソースです。
Kubernetesクラスタでユニークなリソースになります(cluster-scoped)。

以下にリソースの定義を示します。

```YAML
apiVersion: cosi.io/v1alpha1
kind: BucketAccessClass
metadata: 
  name:
provisioner: [1]
policyActionsConfigMap: [2]
  name:
  namespace:
parameters: [3]
```

1. Provisioner名
2. BuketAccessの`policyActionsConfigMapData`に設定されるポリシーアクションを設定したConfigMapの名前とNamespace名
3. パラメータ。Provisionerへ渡す設定値

以上、COSIのAPIリソースを紹介しました。

# Podからのマウント方法

次に、PodからオブジェクトストレージのBuketをマウントする場合のManifestについて紹介します。
まだまだ仕様が柔らかいCOSIの中でも、さらに柔らかい状態のものになります。
以下に、PodのサンプルのManifestを示します。

```YAML
kind: Pod
metadata:
  name: my-app-pod
  namespace: dev-ns
spec:
  serviceAccountName: hoge
  containers:
    - name: my-app
      image: ...
      volumeMounts:
        - mountPath: /cosi
          name: cosi-vol
  volumes:
    - name: cosi-vol
      csi: [3]
        driver: cosi.sigs.k8s.io
        volumeAttributes:
          bucketAccessRequestName: <my-bar-name>
```

マウントの指定(`volumeMounts`)は、CSIのPVCと同様の指定になります。
`volumes`に関しては、CSIのinline ephemeral volumeを利用し指定しています。

# 感想

遂にオブジェクトストレージのインターフェースであるCOSIのKEPがまとまり、Kubernetes v1.21でAlphaとして登場してくるようです。
オブジェクトストレージは、ストレージ業界からみても業界標準化が進んでいない領域のため、各クラウドベンダやストレージベンダで似ているけど若干違うといった所があります。
そのためか、COSIでもBucketAccess周りで何とかしようと詰め込んでいる感じが、ひしひしと伝わってきます。
BucketAccess周り以外は、従来のCSIのPVC/PV/StorageClassと似た設計となっており、PVC/PV/StorageClassを使っているKubernetesのユーザにとっては分かりやすいのではないかと思います。
また、特にPodへマウントさせる時の仕様は暫定的な印象のため、Kubernetes v1.21までには変更が入るだろうと予測しています。
まだまだ、COSIは大幅に仕様が変更するかもしれませんが、スタートは切られましたので、これからどのように仕様が成長していくのか楽しみです。

# 参考情報

- https://github.com/container-object-storage-interface
- https://github.com/kubernetes/enhancements/pull/1383
  - https://github.com/yard-turkey/enhancements/blob/e2c34d419f4eef2003ec7a30af744a082d5423ac/keps/sig-storage/1979-object-storage-support/README.md