###### tags: `k8s` `csi`
# CSIプラグイン開発
## CSIとは
> Container Storage Interface (CSI) is a standardized mechanism for Container
Orchestration Systems (COs), including Kubernetes, to expose arbitrary storage systems
to containerized workloads. Storage Provider (SP) develops once and this works across
a number of COs.
> 引用: https://events19.linuxfoundation.org/wp-content/uploads/2017/12/Internals-of-Docking-Storage-with-Kubernetes-Workloads-Dennis-Chen-Arm.pdf
CSIはkubernetesを含むコンテナオーケストレーションシステム(COs)のために標準化されたメカニズムで、コンテナ化されたワークロードに対して任意のストレージシステムをエクスポーズするためのものである。ストレージプロバイダ(SP)は一度開発を行えば様々なCOs上で動作する。
### ゴール
- サードパーティCSIボリュームドライバーと対話するためのKubernetes APIの定義
- マスター及びノードコンポーネントが任意のサードパーティCSIボリュームドライバーと安全に通信するメカニズムの定義
- マスター及びノードコンポーネントがKubernetesにデプロイされた任意のサードパーティCSIボリュームドライバーを検出および登録するメカニズムの定義
- Kubernetes互換のサードパーティCSIボリュームドライバーのパッケージ要件の定義
- 推奨されたKubernetes互換のサードパーティCSIボリュームドライバーのパッケージ要件
- 推奨されたKubernetesクラスター上のKubernetes互換のサードパーティCSIボリュームドライバーの展開プロセス
### デザイン
Kubernetes APIを監視する外部コンポーネントによってprovisionやdelete, attach, detachなどはハンドルされ、CSIドライバの対応するRPCを呼び出す。
Kubernetes固有のロジックをキャプチャし、サードパーティのコンテナー化されたCSIドライバとKubernetes間のアダプターとして機能するコンテナーを提供する
## コンポーネント間の通信
### Kubelet <-> CSIドライバ
Kubelet(マウントおよびアンマウントを担当)は、Unixドメインソケットを介して、同じホストマシンで実行されている外部のCSIドライバと通信する。CSIドライバはノードの次のパスにソケットを作成する必要がある`/var/lib/kubelet/plugins/[CSIDriverName]/csi.sock`。
初期化時にKubeletはCSIメソッドである`NodeGetInfo`を発行しノード情報及びCSIノードドライバ情報をバインドする。
### マスタ <-> CSIドライバ
Kubeコントローラーマネージャー(作成、削除、アタッチ、およびデタッチを担当)は、Unixドメインソケットを介してCSIドライバコンテナと通信せず、代わりにコントローラーマネージャはKubernetes APIを介して外部のCSIドライバと通信する。
外部コンポーネントはCSIドライバの代わりにKubernetes APIを監視し、それに対して適切なRPCをトリガーする必要する。外部コンポーネントはプロキシコンテナとして提供される。
### プロビジョニング
`StorageClass`でprovisionerを登録し、そのprovisionerを指定しPersistent Volume Claim(PVC)を作成する。provisionerサイドカーコンテナはKubernetes APIを監視することでPVCの作成に反応しCSIドライバに対して`CreateVolume`を発行する。操作が正常に完了するとprovisionerは`PersistentVolume`オブジェクトを作成する。
```go
// PersistentVolumeClaim is a user's request for and claim to a persistent volume
type PersistentVolumeClaim struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
Spec PersistentVolumeClaimSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
Status PersistentVolumeClaimStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}
// PersistentVolumeClaimSpec describes the common attributes of storage devices
// and allows a Source for provider-specific attributes
type PersistentVolumeClaimSpec struct {
AccessModes []PersistentVolumeAccessMode `json:"accessModes,omitempty" protobuf:"bytes,1,rep,name=accessModes,casttype=PersistentVolumeAccessMode"`
Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,4,opt,name=selector"`
Resources ResourceRequirements `json:"resources,omitempty" protobuf:"bytes,2,opt,name=resources"`
VolumeName string `json:"volumeName,omitempty" protobuf:"bytes,3,opt,name=volumeName"`
StorageClassName *string `json:"storageClassName,omitempty" protobuf:"bytes,5,opt,name=storageClassName"`
VolumeMode *PersistentVolumeMode `json:"volumeMode,omitempty" protobuf:"bytes,6,opt,name=volumeMode,casttype=PersistentVolumeMode"`
DataSource *TypedLocalObjectReference `json:"dataSource,omitempty" protobuf:"bytes,7,opt,name=dataSource"`
}
// PersistentVolume (PV) is a storage resource provisioned by an administrator.
type PersistentVolume struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
Spec PersistentVolumeSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
Status PersistentVolumeStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}
// PersistentVolumeSpec is the specification of a persistent volume.
type PersistentVolumeSpec struct {
Capacity ResourceList `json:"capacity,omitempty" protobuf:"bytes,1,rep,name=capacity,casttype=ResourceList,castkey=ResourceName"`
PersistentVolumeSource `json:",inline" protobuf:"bytes,2,opt,name=persistentVolumeSource"`
AccessModes []PersistentVolumeAccessMode `json:"accessModes,omitempty" protobuf:"bytes,3,rep,name=accessModes,casttype=PersistentVolumeAccessMode"`
ClaimRef *ObjectReference `json:"claimRef,omitempty" protobuf:"bytes,4,opt,name=claimRef"`
PersistentVolumeReclaimPolicy PersistentVolumeReclaimPolicy `json:"persistentVolumeReclaimPolicy,omitempty" protobuf:"bytes,5,opt,name=persistentVolumeReclaimPolicy,casttype=PersistentVolumeReclaimPolicy"`
StorageClassName string `json:"storageClassName,omitempty" protobuf:"bytes,6,opt,name=storageClassName"`
MountOptions []string `json:"mountOptions,omitempty" protobuf:"bytes,7,opt,name=mountOptions"`
VolumeMode *PersistentVolumeMode `json:"volumeMode,omitempty" protobuf:"bytes,8,opt,name=volumeMode,casttype=PersistentVolumeMode"`
NodeAffinity *VolumeNodeAffinity `json:"nodeAffinity,omitempty" protobuf:"bytes,9,opt,name=nodeAffinity"`
}
```
### アタッチ
attacherサイドカーコンテナはKubernetes APIを監視し、`VolumeAttachment`オブジェクトに対応した`ControllerPublishVolume`をCSIドライバに対して呼び出し、ノードに対し当該ボリュームをアタッチする。
```go
// VolumeAttachment captures the intent to attach or detach the specified volume
type VolumeAttachment struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
Spec VolumeAttachmentSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"`
Status VolumeAttachmentStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}
// VolumeAttachmentSpec is the specification of a VolumeAttachment request.
type VolumeAttachmentSpec struct {
Attacher string `json:"attacher" protobuf:"bytes,1,opt,name=attacher"`
Source VolumeAttachmentSource `json:"source" protobuf:"bytes,2,opt,name=source"`
NodeName string `json:"nodeName" protobuf:"bytes,3,opt,name=nodeName"`
}
// VolumeAttachmentSource represents a volume that should be attached.
type VolumeAttachmentSource struct {
PersistentVolumeName *string `json:"persistentVolumeName,omitempty" protobuf:"bytes,1,opt,name=persistentVolumeName"`
InlineVolumeSpec *v1.PersistentVolumeSpec `json:"inlineVolumeSpec,omitempty" protobuf:"bytes,2,opt,name=inlineVolumeSpec"`
}
```
### マウント
KubeletがUnixドメインソケット経由でCSIドライバに対しユニークに生成したターゲットパスをパラメータに`NodePublishVolume`を発行する。
## 開発
CSIプラグインには以下の2つの種類がある。
- Controller Plugin
- Node Plugin
### Controller Plugin
Controllerコンポーネントは`StatefulSet`もしくは`Deployment`としてクラスタにデプロイすることができる(よってどのノードに配置されてもよい)。
ControllerコンポーネントはCSI Controller Serviceを実装したCSIドライバと1つ以上のサイドカーコンテナで構成される。サイドカーコンテナはKubernetesオブジェクトとインタラクティブにやり取りしドライバのCSI Controller ServiceのRPCを呼び出す
ホストと直接的なやりとりは必要とせず全ての操作はKubernetes API及び外部のコントロールプレーンを介して行われる(複数レプリカを作成することはできるがリーダー選出機構の実装が推奨される)
コントローラサイドカーには`external-provisioner`、`external-attacher`、`external-snapshotter`及び`external-resizer`が含まれる。サイドカーをデプロイメントに含むかどうかはオブショナル。
![](https://i.imgur.com/K99laHV.png)
> 引用: https://kubernetes-csi.github.io/docs/deploying.html
Unixドメインソケットを用いた通信は同じPod内の`emptyDir`または`hostPath`を介して行われる。
サイドカーコンテははKubernetesのイベントを管理しCSIドライバに対して適切な処理を呼び出す。これはサイドカーとCSIドライバの共有しているUnixドメインソケットを介して行われる
### Node Plugin
Nodeコンポーネントはクラスタの全てのノードに配置される必要があるので`DaemonSet`としてデプロイする。
ControllerコンポーネントはCSI Node Serviceを実装したCSIドライバと` node-driver-registrar`サイドカーコンテナで構成される。
![](https://i.imgur.com/B5Py3a5.png)
UnixドメインソケットはhostPathに配置する必要がある(`/var/lib/kubelet/<plugin name>/csi.sock`)
Kubeletは全てのノードで動作しCSI Node Serviceを呼び出す責任があり、これはストレージシステムからのマウントやアンマウント、ステージングなどの処理を呼び出す。KubeletはhostPathで共有したUnixドメインソケットを通じて処理を呼び出す。2番目のUnixドメインソケットは`node-driver-registrar`がCSIドライバをKubeletに登録するために使用する。
## Service
gRPCのインターフェースを定義したものでサイドカーコンテナやKubeletから呼び出される。サービスには以下の3種類がある。
- Identity Service
- Node Plugin及びController Pluginが当該サービスのRPCを実装する必要がある
- Controller Service
- Controller Pluginが当該サービスのRPCを実装する必要がある
- Node Service
- Node Pluginが当該サービスのRPCを実装する必要がある
```proto
service Identity {
rpc GetPluginInfo(GetPluginInfoRequest)
returns (GetPluginInfoResponse) {}
rpc GetPluginCapabilities(GetPluginCapabilitiesRequest)
returns (GetPluginCapabilitiesResponse) {}
rpc Probe (ProbeRequest)
returns (ProbeResponse) {}
}
service Controller {
rpc CreateVolume (CreateVolumeRequest)
returns (CreateVolumeResponse) {}
rpc DeleteVolume (DeleteVolumeRequest)
returns (DeleteVolumeResponse) {}
rpc ControllerPublishVolume (ControllerPublishVolumeRequest)
returns (ControllerPublishVolumeResponse) {}
rpc ControllerUnpublishVolume (ControllerUnpublishVolumeRequest)
returns (ControllerUnpublishVolumeResponse) {}
rpc ValidateVolumeCapabilities (ValidateVolumeCapabilitiesRequest)
returns (ValidateVolumeCapabilitiesResponse) {}
rpc ListVolumes (ListVolumesRequest)
returns (ListVolumesResponse) {}
rpc GetCapacity (GetCapacityRequest)
returns (GetCapacityResponse) {}
rpc ControllerGetCapabilities (ControllerGetCapabilitiesRequest)
returns (ControllerGetCapabilitiesResponse) {}
rpc CreateSnapshot (CreateSnapshotRequest)
returns (CreateSnapshotResponse) {}
rpc DeleteSnapshot (DeleteSnapshotRequest)
returns (DeleteSnapshotResponse) {}
rpc ListSnapshots (ListSnapshotsRequest)
returns (ListSnapshotsResponse) {}
rpc ControllerExpandVolume (ControllerExpandVolumeRequest)
returns (ControllerExpandVolumeResponse) {}
}
service Node {
rpc NodeStageVolume (NodeStageVolumeRequest)
returns (NodeStageVolumeResponse) {}
rpc NodeUnstageVolume (NodeUnstageVolumeRequest)
returns (NodeUnstageVolumeResponse) {}
rpc NodePublishVolume (NodePublishVolumeRequest)
returns (NodePublishVolumeResponse) {}
rpc NodeUnpublishVolume (NodeUnpublishVolumeRequest)
returns (NodeUnpublishVolumeResponse) {}
rpc NodeGetVolumeStats (NodeGetVolumeStatsRequest)
returns (NodeGetVolumeStatsResponse) {}
rpc NodeExpandVolume(NodeExpandVolumeRequest)
returns (NodeExpandVolumeResponse) {}
rpc NodeGetCapabilities (NodeGetCapabilitiesRequest)
returns (NodeGetCapabilitiesResponse) {}
rpc NodeGetInfo (NodeGetInfoRequest)
returns (NodeGetInfoResponse) {}
}
```
### Identity Service RPC
プラグインの権限や動作状態、その他メタデータなどを提供する。
以下のメソッドが用意されている。
- GetPluginInfo
- ドライバ名やバージョンを取得する
- GetPluginCapabilities
- サポートされた権限一覧を取得する
- Probe
- 動作状態を取得する
### Controller Service RPC
以下のメソッドが用意されている。
- CreateVolume
- ボリュームをプロビジョニングする
- DeleteVolume
- ボリュームを削除する
- ControllerPublishVolume
- 指定されたノード上でボリュームを有効化する
- ControllerUnpublishVolume
- 他のノードでボリューム使用を可能にする
- ValidateVolumeCapabilities
- リクエストした権限を保持しているか確認する
- ListVolumes
- 全てのボリュームの情報を返す
- GetCapacity
- ストレージプールのキャパシティを返す
- ControllerGetCapabilities
- プラグインが権限をサポートしているかどうか確認
- CreateSnapshot
- スナップショットを作成する
- DeleteSnapshot
- スナップショットを削除する
- ListSnapshots
- スナップショット一覧を取得する
- ControllerExpandVolume
- ボリュームサイズの変更
### Node Service RPC
- NodeStageVolume
- ボリュームの使用開始前に呼び出す
- NodeUnstageVolume
- ステージされたボリュームに対して呼び出す
- NodePublishVolume
- ボリュームが使用されるノード上で呼び出す
- NodeUnpublishVolume
- パブリッシュされたパスに対して呼び出す
- NodeGetCapabilities
- プラグインが権限をサポートしているかどうか確認
- NodeGetInfo
- ノードの情報を取得する
- NodeExpandVolume
- ボリュームサイズの変更
## サイドカーコンテナ
CSIドライバの開発とデプロイを簡素化する目的で開発された。これらのコンテナにはKubetnetes APIを監視し、CSIドライバに対して適切な操作をトリガーし必要に応じてKubernetes APIの更新を行う。
サイドカーコンテナは必要に応じてCSIドライバコンテナにバンドルされPodとして一緒にデプロイされることを想定している。
### CSI external-attacher
git: https://github.com/kubernetes-csi/external-attacher/
`VolumeAttachment`のためにKubernetes APIサーバを監視し`Controller[Publish|Unpublish]Volume`をCSIエンドポイントに対して発行するサイドコンテナ
#### 使い方
Kubernetesのボリュームアタッチ及びデタッチのフックが必要な場合は当該サイドコンテナを使用し加えて`PUBLISH_UNPUBLISH_VOLUME`のケーパビリティを広告する必要がある。
### CSI external-provisioner
git: https://github.com/kubernetes-csi/external-provisioner/
`PersistentVolumeClaim`にKubernetes APIサーバを監視し`Controller[Publish|Unpublish]Volume`をCSIエンドポイントに対して発行するサイドコンテナ
新たなボリュームのプロビジョニングするため、指定されたCSIエンドポイントに対して`CreateVolume`を呼び出す
PVCがStorage Classを参照し且つ指定されたCSIエンドポイントの`GetPluginInfo`で返却される名前とprovisionerの名前が同一である場合、新たな`PersistentVolumeClaim`の作成によりボリュームプロビジョニングはトリガーされる
新たなボリュームが正常にプロビジョニングされるとサイドカーコンテナは対応する`PersistentVolume`オブジェクトを作成する。
#### Storage Class パラメータ
新たなボリュームをプロビジョニングする際に`external-provisioner`はController Service RPCの`CreateVolumeRequest`内の`map<string, string> parameters`に`StorageClass`で指定されたキーバリューの値をセットする(`csi.storage.k8s.io/`のプレフィクスが付いたキーは`parameter`としてCSIドライバに渡されず、対応するgRPCのリクエストフィールドにセットされる)
https://github.com/container-storage-interface/spec/blob/master/spec.md#createvolume
#### 使い方
Kubernetesの動的ボリュームプロビジョニングをサポートする場合、当該サイドコンテナを使用し加えて`CREATE_DELETE_VOLUME`のケーパビリティを広告する必要がある。
### CSI external-resizer
git: https://github.com/kubernetes-csi/external-resizer
`PersistentVolumeClaim`にKubernetes APIサーバを監視し`ControllerExpandVolume`をCSIエンドポイントに対して発行するサイドコンテナ
### CSI external-snapshotter
// todo
### CSI livenessprobe
git: https://github.com/kubernetes-csi/livenessprobe
CSIドライバが正常に動作しているかを監視し`Liveness Probe mechanism`を用いてKubernetesに報告する。これはKubernetesに対して自動的にCSIドライバの問題を検知しPodを再起動することを可能とする。
全てのCSIドライバは可用性の向上を目的にこれを使用すべきである。
### CSI node-driver-registrar
git: https://github.com/kubernetes-csi/node-driver-registrar
CSIエンドポイントからドライバの情報を取得し、ノード上のKubeletに`kubelet plugin registration mechanism`を用いてそれを登録するサイドカーコンテナ
#### 使い方
Kubelethは直接CSIドライバに対して`NodeGetInfo`、`NodeStageVolume`及び`NodePublishVolume`を呼び出す。それは`kubelet plugin registration mechanism`を用いてUnixドメインソケットを検出しCSIドライバと対話する。CSIドライバをKubeletに登録するため全てのCSIドライバは当該サイドカーコンテナを使用すべきである。
### CSI cluster-driver-registrar
**Deprecated**
自動的にドライバの情報を含んだ`CSIDriver`オブジェクトをKubernetesに登録するために使用されていた。当該サイドカーコンテナを使用しない場合CSIドライバベンダーは`CSIDriver`オブジェクトをインストールマニフェストに含む必要がある。
## デプロイ
![](https://github.com/kubernetes/community/raw/master/contributors/design-proposals/storage/container-storage-interface_diagram1.png?raw=true)
> 引用: https://github.com/kubernetes/community/blob/master/contributors/design-proposals/storage/container-storage-interface.md
>
以下をデプロイする。
- `SCIDriver`オブジェクト
```yaml
apiVersion: storage.k8s.io/v1beta1
kind: CSIDriver
metadata:
name: csi-cubefsplugin
spec:
attachRequired: false
volumeLifecycleModes:
- Persistent
podInfoOnMount: true
```
- RBAC
- Controller Plugin (StatefulSet or Deployment)
- CSIドライバコンテナ
- サイドカーコンテナ
- external-provisioner
- external-attacher
- external-resizer
- external-snapshotter
- livenessprobe
- Node Plugin (DeamonSet)
- CSIドライバコンテナ
- サイドカーコンテナ
- node-driver-registrar
- livenessprobez
- `StorageClass`オブジェクト
```yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: csi-my-sc
provisioner: csi-myplugin
reclaimPolicy: Delete
volumeBindingMode: Immediate
allowVolumeExpansion: true
```
上記をデプロイし後以下のようなマニフェストでPVCの動的プロビジョニングを行う。
```yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: csi-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: csi-my-sc
```
## 参考実装
- [csi-driver-nfs](https://github.com/kubernetes-csi/csi-driver-nfs)
- [csi-driver-host-path](https://github.com/kubernetes-csi/csi-driver-host-path)
## 参考資料
*順に読むと理解がしやすい
1. [KubernetesにおけるContainer Storage Interface (CSI)の概要と検証](https://qiita.com/ysakashita/items/4b56c2577f67f1b141e5)
- SSIA
1. [CSI Volume Plugins in Kubernetes Design Doc](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/storage/container-storage-interface.md)
- 内部の動作
1. [Kubernetes CSI Developer Documentation](https://kubernetes-csi.github.io/docs/#kubernetes-container-storage-interface-csi-documentation)
- サイドカーコンテナ
- 構成要素
1. [Container Storage Interface (CSI) Spec](https://github.com/container-storage-interface/spec/blob/master/spec.md)
- gRPCのインターフェース定義