Kubernetesクラスターにおけるボリューム：アーキテクチャ、原則、および機能

シニアテクニカルエキスパートであるKan Junbaoが、**Kubernetesクラスター**におけるボリュームの**アーキテクチャ、原理、機能**を大きく3つのパートに分けて紹介します。

著者：アリババのシニアテクニカルエキスパート、Kan Junbao（Junbao）氏

コンテナストレージは、データの永続性を提供する[Kubernetes](https://www.alibabacloud.com/product/kubernetes?spm=a2c65.11461447.0.0.313bbd79F0oh9I)の基本コンポーネントであり、ステートフルなサービスにとって重要な保証となります。デフォルトでは、Kubernetesは主流のボリュームアクセスソリューションであるIn-Treeと、他のストレージサービスがKubernetesサービスにアクセスできるようにするプラグインメカニズムであるOut-Of-Treeを提供します。この記事では、Kubernetesのストレージアーキテクチャと、ボリュームプラグインの原理と実装について説明します。

#1、 Kubernetesのストレージシステムのアーキテクチャ
###例：Kubernetesでボリュームをマウント
次の例は、ボリュームをマウントする方法を示しています。

下図のように、左のYAMLテンプレートではStatefulSetアプリケーションを定義しています。このテンプレートでは、disk-pvcという名前のボリュームが定義され、Pod内の/dataディレクトリにマウントされています。disk-pvcは、storageClassNameが定義されたPVC（persistent volume claim）ボリュームです。

このテンプレートは典型的なダイナミックストレージテンプレートです。前述の図の右側の部分は、ボリュームをマウントするプロセスを6つのステップで示しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/a5c5cde1-885f-26c8-fbcb-083ddfced225.png)


- ステップ1：ユーザーは、PVCを含むPodを作成します。
- ステップ2： PVコントローラは、APIサーバを常時監視します。PVCが作成されているがバインドされていないことを発見すると、そのPVCにパーシステントボリューム（PV）をバインドしようとします。
PVコントローラは、クラスタ内で適切なPVを見つけようとします。適切なPVが見つからない場合は、プロビジョニングのためにボリュームプラグインが呼び出されます。プロビジョニングでは、特定のリモートストレージ媒体からボリュームを作成し、クラスタ内にPVを作成し、PVをPVCにバインドします。

- ステップ3： スケジューラがスケジューリングを行います。
Podが実行されている場合、スケジューラはノードを選択する必要があります。スケジューラは、Podに定義されたnodeSelectorとnodeAffinity、およびボリュームに定義されたいくつかのタグなど、複数の参照に基づいてスケジューリングを実行します。

ボリュームにいくつかのタグを追加することができます。このようにして、このPVを使用しているPodは、スケジューラによってタグに基づいて予想されるノードにスケジューリングされます。



- ステップ4：Podがノードにスケジュールされた後に、Podで定義されたPVがアタッチされていない場合、ADコントローラはボリュームプラグインを呼び出して、リモートボリュームをターゲットノードのデバイス（たとえば、/dev/vdb）にマウントします。
- ステップ5：ボリュームマネージャは、Podがそのノードにスケジュールされ、ボリュームがマウントされたことを見つけると、ローカルデバイス（/dev/vdb）をノード上のPodのサブディレクトリにマウントします。また、フォーマットやGlobalPathへのマウントなど、いくつかの追加操作を行います。
- ステップ6：ローカルにマウントされたボリュームをPod内のコンテナにマッピングします。

#Kubernetesのストレージアーキテクチャ
ここでは、Kubernetesのストレージアーキテクチャについて説明します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/182c5703-8afa-51dd-eea5-edde8ac87921.png)


- PVコントローラ：PVとPVCの結合、ライフサイクルの管理、必要に応じてボリュームの作成・削除を行います。
- ADコントローラー：ターゲットノードへのストレージデバイスの取り付け、取り外しを行います。
- ボリュームマネージャー：ボリュームのマウント／アンマウント操作の管理、ボリュームデバイスのフォーマット、共通ディレクトリへのボリュームのマウントなどを行います。
- ボリュームプラグイン：すべてのマウント機能を実装します。
PVコントローラー、ADコントローラー、ボリュームマネージャーが操作を呼び出し、ボリュームプラグインが操作を実装します。

- スケジューラ：特定のストレージ関連の定義に基づいて、Podのスケジューリングとストレージ関連のスケジューリングを行います。
次に、各コンポーネントの機能について説明します。

###PVコントローラー
いくつかの基本的な概念について説明します。

- **PV：**事前にアタッチされたストレージスペースのパラメータを詳細に定義します。

例えば、リモートのNAS（Network Attached Storage）ファイルシステムをアタッチする際には、NASのパラメータをPVで定義する必要があります。PVはネームスペースによる制限を受けず、一般的には管理者が作成・管理します。

- **PVC：**ネームスペース内のユーザーが呼び出すストレージインターフェースです。ストレージの詳細は感知せず、一部の基本的なストレージのSizeおよびAccessModeパラメータを定義します。

- **ストレージクラス：**ダイナミックボリューム用のPVを作成するためのテンプレートを定義します。テンプレートには、テンプレートの作成に必要ないくつかのパラメータと、PVを作成するプロビジョナーが含まれます。

PVコントローラは、PVの作成や削除、PVやPVCのステータスの変更など、PVやPVCのライフサイクル管理を行います。また、PVコントローラーは、PVCとPVをバインドします。PVCはPVにバインドされて初めて利用可能になります。また、1つのPVCは1つのPVにしか結合できず、1つのPVは1つのPVCにしか結合できません。

次の図は、PV の状態変化を示しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/d7384b19-c676-b52a-f386-43a38085dc97.png)


PV が作成されると、PV は Available 状態になります。PVC が PV にバインドされると、PV はバインド状態になります。PVCが削除されると、PVは解放状態になります。

解放された状態の PV は、ReclaimPolicy フィールドに従って、Available 状態になるか Deleted 状態になるかを決定します。ReclaimPolicy フィールドが Recycle に設定されている場合、PV は Available 状態になります。状態変化に失敗した場合は、Failed 状態になります。

次の図は、PVC のステータス変更をよりシンプルにしたものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/0d57ba82-2fc4-214a-0ff4-8518ea16f9d5.png)


作成されたPVCはPending状態にあります。PV にバインドされた後、PVC は Bound 状態になります。PVC にバインドされていた PV が削除されると、PVC は Lost 状態になります。Lost 状態の PVC に PV を作成してバインドすると、PVC は Bound 状態になります。

次の図は、PVC にバインドされる PV を選択する方法を示しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/a10dad02-960a-6f46-8828-9a76dc59e31c.png)


- まず、PVのVolumeModeタグがPVCのものと一致していることを確認します。VolumeMode は、ボリュームがファイルシステムであるかブロックであるかを定義します。
- 第2に、PVCでLabelSelectorが定義された後、ラベルを持ち、PVCのLabelSelectorと一致するPVを選択します。
- 第3に、StorageClassNameの確認です。PVC に StorageClassName が定義されている場合は、同じクラス名を持つ PV をフィルタリングします。
StorageClassName タグで指定されたストレージクラスは、その PVC に PV がない場合に PV を作成します。条件を満たすPVがあれば、そのPVをPVCに直接バインドします。

- 第4に、AccessMode の確認です。
AccessMode は通常、PVC の ReadWriteOnce や RearWriteMany などのタグに定義されています。バインドされるPVCとPVは同じAccessModeである必要があります。

- 最後に「Size」を確認します。

PVCは宣言されたボリュームであり、実際のボリュームが少なくとも宣言されたボリュームと等しい場合にのみ結合することができるため、PVCのサイズはPVのサイズを超えることはできません。

次に、PVコントローラの実装を見てみましょう。

PVコントローラは、ClaimWorkerとVolumeWorkerのロジックで実装することができます。

ClaimWorkerはPVCのステータス変更を実装します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/fbea27ad-0160-b11c-6265-ad35c8622f94.png)

システムタグ`pv.kubernetes.io/bind-completed`は、PVCのステータスを識別するために使用されます。

- このタグがTrueであれば、PVCはバインドされたことになります。この場合、いくつかの内部ステータスを同期させるだけで済みます。
- タグが False の場合、PVC はバインドされていません。
また、クラスタ内のすべての PV をフィルタリングする必要があります。findBestMatch を使用して、前の 5 つのバインド条件に基づいてすべての PV をフィルタリングできます。PVが見つかった場合は、それをPVCにバインドします。それ以外の場合は、PV を作成します。

VolumeWorkerはPVのステータス変更を実装します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/07216fcf-ecb7-0dd2-4cc5-d2608e0c5f45.png)


PV の ClaimRef タグは、PV の状態を判断するために使用されます。タグが空であれば、PVはAvailable状態であり、ステータスの同期を取るだけでよいことになります。タグが空でない場合は、タグに基づいてクラスタ内のPVCを見つけることができます。PVCが存在する場合、PVはBound状態であり、ステータスを同期させることができます。PVC が存在しない場合、PV はバインドされていた PVC が削除されたため Released 状態になっています。その後、ReclaimPolicy に基づいて、ボリュームを削除するか、ステータスを同期させるかを判断します。

以上が、PVコントローラの簡単な実装ロジックです。

###ADコントローラ
ADコントローラは、アタッチとデタッチのコントローラを略した名前です。

このコントローラは、destiredStateOfWorldとactualStateOfWorldという2つのコアオブジェクトを持っています。

- DesiredStateofWorldは、クラスタ内で達成すべきボリュームマウントの状態を示します。
- ActualStateOfWorldは、クラスターにおける実際のボリュームマウントの状態を示します。

ADコントローラには、2つのコア・ロジック・パラメータ、wantedStateOfWorldPopulatorとreconcilerがあります。

- desiredStateOfWorldPopulatorは、クラスタ内の一部のデータとDSWおよびASWのデータ更新を同期させます。たとえば、クラスター内にPVCやPodを作成すると、PVCやPodのステータスをDSWに同期させます。
- reconcilerはDSWとASWのステータスを同期させます。ASW のステータスを DSW のステータスに変更します。このステータス変更中に、アタッチやデタッチなどの操作を行います。
desiredStateOfWorld と actualStateOfWorld オブジェクトの例を以下に示します。

- desiredStateOfWorldは、ワーカーに含まれるボリュームやマウント情報など、各ワーカーを定義します。
- actualStateOfWorlは、各ボリュームのターゲットノードやマウント状態など、すべてのボリュームを定義します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/af78f852-a7b5-2f07-dafe-55a065c9f0a1.png)


次の図は、ADコントローラの実装の論理図です。

ADコントローラには多くのインフォーマーが含まれており、クラスター内のPodステータス、PVステータス、ノードステータス、PVCステータスをローカルマシンに同期させています。

初期化時には、populateDesireStateOfWorldとpopulateActualStateOfWorldが呼び出され、d desiredStateOfWorldとactualStateOfWorldが初期化されます。

実行中、destateStateOfWorldPopulatorは、クラスタ内のデータの状態をdestateStateofWorldに同期させます。reconcilerは、ポーリング・モードでactualStateOfWorldとdestateStateOfWorldのデータを同期させます。同期中、ボリューム・プラグインを呼び出してアタッチとデタッチの操作を行い、さらにnodeStatusUpdaterを呼び出してノードの状態を更新します。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/3b7fc51a-f81c-14e3-3d27-4aa01f4dc1e4.png)


これがADコントローラーの簡単な実装ロジックです。

###Volume Manager
Volume Managerは、kubeletのマネージャーの1つです。ローカルノードのボリュームのアタッチ、デタッチ、マウント、アンマウントに使用されます。

ADコントローラーと同様に、d desiredStateOfWorldとactualStateOfWorldを含みます。また、ノード上のプラグインを管理する volumePluginManager も含まれています。そのコアロジックは、ADコントローラと同様です。desiredStateOfWorldPopulatorを介してデータを同期し、reconcilerを介してAPIを呼び出します。

ここでは、アタッチおよびデタッチの操作について説明します。

前述の通り、ADコントローラーもアタッチとデタッチの操作を行います。`--enable-controller-attach-detach` タグを使用して、AD コントローラとボリュームマネージャのどちらがこの操作を実行するかを指定できます。値が True の場合は、AD コントローラが操作を実行します。値が False の場合は、ボリュームマネージャが操作を実行します。

これはキューブレットのタグであり、ノードの動作を定義することしかできません。したがって、クラスタに10のノードがあり、5つのノードでタグがFalseと定義されている場合、5つのノードのキューブレットがアタッチとデタッチの操作を実行し、他の5つのノードのADコントローラがこれらの操作を実行します。

次の図は、ボリュームマネージャの実装ロジックを示しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/b98c036f-09a5-c2ee-6475-91687f8882da.png)


一番外側の層はループで、内側の層は、destateStateOfWorldやactualStateOfWorldオブジェクトなどの異なるオブジェクトに基づいたポーリングです。

例えば、actualStateOfWorld.MountedVolumesオブジェクトがポーリングされます。そのオブジェクトのボリュームがdounsedStateOfWorldにも存在する場合、実際のボリュームとdounsedのボリュームがマウントされます。したがって、何もする必要はありません。desiredStateOfWorldにボリュームが存在しない場合は、ボリュームがUmounted状態であることが予想されます。この場合、UnmountVolumeを実行して、その状態をd desiredStateOfWorldの状態に変更します。

このプロセスでは、基礎となるAPIが呼び出され、destatedStateOfWorldとactualStateOfWorldの比較に基づいて、対応する操作が実行されます。desiredStateOfWorld.UnmountVolumesとactualStateOfWorld.AttachedVolumesの操作は同様です。

###ボリュームプラグイン
前述のPVコントローラー、ADコントローラー、ボリュームマネージャーは、Provision、Delete、Attach、DetachなどのボリュームプラグインAPIを呼び出して、PVやPVCを管理します。これらのAPIの実装ロジックはボリュームプラグインにあります。

ボリュームプラグインは、ソースコードの位置によって、In-Tree型プラグインとOut-of-Tree型プラグインに分けられます。

- In-Treeとは、ソースコードがKubernetes内に置かれ、Kubernetesとともにリリース、管理、イテレーションを行うことを示します。しかし、イテレーションのスピードが遅く、柔軟性に欠けます。
- Out-of-Treeとは、ソースコードがKubernetesから独立しており、ストレージプロバイダーが提供・実装することを示します。現在、主な実装機構はFlexVolumeとContainer Storage Interface (CSI)で、ストレージタイプに応じて異なるストレージプラグインを実装することができます。そのため、当社ではOut-of-Treeを採用しています。
ボリュームプラグインは、PVコントローラ、ADコントローラ、ボリュームマネージャから呼び出されるライブラリです。これらはIn-TreeプラグインとOut-of-Treeプラグインに分けられます。ボリューム・プラグインは、これらの実装に基づいてリモート・ストレージを呼び出し、リモート・ストレージをローカル・デバイスにマウントします。例えば、NASファイルシステムをマウントする`mount -t nfs ***`コマンドは、ボリューム・プラグインに実装されています。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/0072c347-cbe9-2d43-4796-c7be4f725ba7.png)


ボリュームプラグインは多くの種類に分けられます。In-Treeのプラグインには、数十種類の一般的なストレージの実装が含まれています。しかし、企業によっては独自のタイプを定義し、独自のAPIやパラメータを持っており、一般的なストレージプラグインではサポートされていません。このような場合は、CSIやFlexVolumeなどのOut-of-Tree型のストレージ実装を使用する必要があります。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/0e984ef2-912a-4fd0-1c4a-33f3e21bf479.png)


ボリュームプラグインの具体的な実装方法については後述します。ここでは、ボリュームプラグインの管理について説明します。

KubernetesはPVコントローラ、ADコントローラ、ボリュームマネージャのプラグインをVolumePlguinMgを用いて、主にプラグインとプローバーのデータ構造で管理します。

pluginsはプラグインリストを保存するためのオブジェクトで、proberは新しいプラグインを発見するためのプローブです。例えば、FlexVolumeとCSIは拡張プラグインであり、動的に作成・生成されるため、プローブによってのみ発見することができます。

次の図は、プラグイン管理のプロセス全体を示しています。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/1c818360-09e8-e197-8368-c03d30b1dac0.png)


PVコントローラ、ADコントローラ、またはボリュームマネージャの起動時に、InitPluginsメソッドが実行され、VolumePluginMgrが初期化されます。

すべてのIn-Treeプラグインがプラグインリストに追加され、プローバのinitメソッドが呼び出されます。このメソッドはまずinitWatcherを呼び出し、ディレクトリ（図では/usr/libexec/kubernetes/kubelet-plugins/volume/exec/など）を常時チェックします。そのディレクトリに新しいプラグインが生成されると、新たにFsNotify.Createイベントが生成され、EventsMapに追加されます。同様に、ディレクトリからプラグインが削除されると、FsNotify.Remove イベントが生成され、EventsMap に追加されます。

上位層がrefreshProbedPluginsを呼び出すと、プローバはプラグインを更新します。FsNotify.Createが呼ばれた場合、新しいプラグインがプラグインリストに追加されます。FsNotify.Removeが呼ばれた場合は、プラグインがプラグインリストから削除されます。

以上で、ボリュームプラグインのプラグイン管理の仕組みが理解できました。



###Kubernetes ボリュームのスケジューリング
Podは実行する前にワーカーにスケジューリングする必要があります。Podをスケジューリングする際には、VolumeZonePredicate、VolumeBindingPredicate、CSIMaxVolumLimitPredicateなどのボリューム関連のスケジューラーを含む、フィルタリングのための異なるスケジューラーを使用します。

VolumeZonePredicateは、PV内のタグ（failure-domain.beta.kubernetes.io/zoneタグなど）をチェックします。そのタグがゾーン情報を定義している場合、VolumeZonePredicateは、対応するゾーンのノードのみがスケジュール可能であると判断します。

次の図の左側では、タグがゾーン cn-senzhen-a を定義しています。次の図の右側では、PV に対して nodeAffinity が定義されており、PV に想定されるノードのタグが含まれています。このタグは VolumeBindingPredicate によってフィルタリングされます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/8df6cda6-de7e-3f7d-d7f8-0f3643c8832d.png)


ボリュームのスケジューリング方法の詳細については、「[Getting Started with Kubernetes - App Storage and Persistent Volumes：Snapshot Storage and Topology Scheduling](http://mp.weixin.qq.com/s?spm=a2c65.11461447.0.0.313bbd79F0oh9I&__biz=MzUzNzYxNjAzMg==&mid=2247487353&idx=1&sn=80263666e7e2e17da37374990262d283&chksm=fae504b6cd928da0b44045ed3d1596597a84128cd6a7d6cfbcfac33382a252f295fce2e39d78&scene=21#wechat_redirect)」を参照してください。

#2. FlexVolumeの紹介
FlexVolumeは、ボリュームプラグインの拡張機能です。アタッチ、デタッチ、マウント、アンマウントの各操作を実装しています。これらの操作は、もともとボリュームプラグインで実装されていました。しかし、一部のストレージタイプでは、ボリュームプラグインを拡張し、ボリュームプラグイン以外の操作を実装する必要があります。

次の図に示すように、ボリューム・プラグインにはFlexVolumeの実装コードが含まれています。しかし、そのコードにはプロキシ機能しかありません。

たとえば、プラグインのアタッチ操作を呼び出すとき、ADコントローラはまずボリューム・プラグイン内のFlexVolumeのアタッチ操作を呼び出します。しかし、この操作は、FlexVolume の Out-Of-Tree 実装に呼び出しを転送するだけです。

FlexVolume は、kubelet 駆動の実行ファイルです。各呼び出しは、シェルのIsスクリプトを実行することと同じです。メモリ常駐型のデーモンではありません。

FlexVolumeの標準出力には、JSON形式のkubeletの呼び出し結果が出力されます。


FlexVolumeのデフォルトのストレージアドレスは、/usr/libexec/kubernetes/kubelet-plugins/volume/exec/alicloud~disk/diskです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/07933362-b212-60cd-0dd2-dd2622c0c31a.png)



次の図は、コマンドのフォーマットと呼び出し例です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/ed747d30-713b-6b83-8687-184dc3ec3b67.png)

###FlexVolumeのAPIの紹介
FlexVolumeは以下のAPIを提供しています。

- init：初期化操作を実行します。たとえば、プラグインの展開や更新時に初期化操作を行うと、FlexVolumeプラグインの機能を説明するDriveCapabilities型のデータ構造が返されます。
- GetVolumeName：プラグイン名を返します。
- Attach：アタッチ機能を実装します。--enable-controller-attach-detach タグは、AD コントローラと kubelet のどちらがアタッチ操作を開始するかを決定します。
- WaitforAttach：他の操作を続ける前に、非同期のアタッチ操作の完了を待ちます。
- MountDevice:マウント操作の一部です。この例では、マウント操作は MountDevice と SetUp に分かれています。MountDevice は、デバイスのフォーマットや GlobalMount ディレクトリへのマウントなど、簡単な前処理を行います。
- GetPath：各Podのローカルマウントディレクトリを取得します。
- Setup：GlobalPathのデバイスをPodのローカルディレクトリにバインドします。
- TearDown、UnmountDevice、Detach：先行するいくつかのAPIの逆方向の処理を実装します。
- ExpandVolumeDevice：ボリュームを展開します。expandコントローラから呼び出されます。
- NodeExpand：ファイルシステムを拡張します。kubelet から呼び出されます。

前述の操作のすべてが実装されなければならないわけではありません。操作が実装されていない場合は、以下のように結果を定義し、呼び出し側に通知します。

```
{
    "status": "Not supported",
    "message": "error message"
}
```

FlexVolume がボリュームプラグインで提供する API は、プロキシとしての役割に加えて、マウント操作などのいくつかのデフォルト実装も提供します。したがって、この API が FlexVolume で定義されていない場合は、デフォルトの実装が呼び出されます。

PV を定義する際に、secretRef フィールドを使用して特定のシークレット機能を定義することができます。たとえば、secretRef は、マウントに必要なユーザー名とパスワードを転送することができます。

###FlexVolume マウントの分析
FlexVolumeのマウントとアンマウントのプロセスを見てみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/a6491e07-da13-de89-c8d5-5c6000f1ed00.png)


まず、Attach オペレーションがリモート API を呼び出し、ターゲットノード上のデバイスにストレージをアタッチします。次に、MountDevice オペレーションは、ローカルデバイスを GlobalPath にマウントし、フォーマットなどの特定の操作を行います。Mount オペレーション（SetUp）は、GlobalPath を PodPath にマウントします。PodPath は、Pod起動時にマッピングされるディレクトリです。

例えば、ディスクのボリュームIDがd-8vb4fflsonz21h31cmssだとします。Attach および WaitForAttach オペレーションが完了すると、ターゲットノードの /dec/vdc ディレクトリにマウントされます。MountDevice オペレーションが実行されると、デバイスがフォーマットされ、ローカルの GlobalPath にマウントされます。Mount オペレーションが実行されると、GlobalPath がPod関連のサブディレクトリにマップされます。最後に、Bind オペレーションがローカルディレクトリをPodにマッピングします。これでマウント処理が完了します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/d773053f-3d26-4459-37bd-60da82cd441f.png)

アンマウントの処理は逆の処理になります。前述では、ブロックデバイスをマウントする処理を説明しました。ファイルストレージに必要なのは、マウント操作だけです。したがって、ファイルシステムにFlexVolumeを実装するためには、マウント操作とアンマウント操作のみを実行すればokです。

###FlexVolumeのコード例

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/1c90e7f9-15cb-5b72-10d7-3fca66d5be40.png)


init()、doMount()、doUnmount()メソッドが実装されています。スクリプトを実行する際には、入力パラメータによって実行するコマンドを決定します。

GitHubでは、参考となる多くのFlexVolumeの例を提供しています。Alibaba Cloudでは、参考となる[FlexVolumeの実装](https://github.com/AliyunContainerService/flexvolume?spm=a2c65.11461447.0.0.313bbd79F0oh9I)を提供しています。

###FlexVolumeの使用
次の図は、FlexVolumeタイプのPVテンプレートを示しています。タイプがflexVolumeとして定義されていること以外は、他のテンプレートと同様です。flexVolume には、driver、fsType、オプションが定義されています。

- driver は、図の alicloud/disk ドライバや alicloud/nas ドライバなど、実装されているドライバを定義します。
- fsType は、ext4 などのファイルシステムタイプを定義します。
- options は volumeId などの特定のパラメータを含みます。

他のタイプと同様に，selector で matchLabels を使用してフィルタリング条件を定義することができます。また、ゾーンを cn-shinzhen-a とするなど、スケジューリング情報を定義することもできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/6f9c4105-70f0-3951-0ddb-4724bad52008.png)


次の図は、詳細な実行結果を示しています。Podには、/dev/vdbのディスクが取り付けられています。「mount | grep disk」コマンドを実行すると、マウントディレクトリが表示されます。/dev/vdbをGlobalPathにマウントし、mountコマンドを実行してGlobalPathをPodで定義されたローカルサブディレクトリにマウントし、ローカルサブディレクトリを/dataにマッピングしています。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/dd4e769b-8aa0-5c49-3f46-dbd195db2206.png)


#3、 CSIの紹介

FlexVolumeと同様に、CSIはサードパーティのストレージにボリュームを提供する抽象的なインターフェイスです。

FlexVolumeが使えるのに、なぜCSIが必要なのでしょうか？

FlexVolumeはKubernetesのオーケストレーションシステムでのみ使用されますが、CSIはMesosやSwarmなどの異なるオーケストレーションシステムでも使用できます。

また、CSIはコンテナベースの展開を採用しており、環境への依存度は低いものの安全性が高く、豊富なプラグイン機能を備えています。FlexVolumeは、ホスト空間にあるバイナリファイルです。FlexVolumeを実行することは、ローカルのシェルコマンドを実行することと同じであり、FlexVolumeのインストール時にいくつかの依存関係をインストールすることができます。これらの依存性は、お客様のアプリケーションに影響を与える可能性があります。そのため、セキュリティや環境の依存性を傷つけてしまいます。

Kubernetesのエコシステムにオペレーターを実装する際には、ロールベースのアクセスコントロール（RBAC）を使ってKubernetesのAPIを呼び出し、コンテナに特定の機能を実装することがよくあります。しかし、FlexVolume環境では、ホスト空間のバイナリプログラムであるため、これらの機能を実装することができません。これらの機能は、CSIのRBACによって実装することができます。

CSIには、CSIコントローラー・サーバーとCSIノード・サーバーがあります。

- CSIコントローラー・サーバーは、コントロール側の作成、削除、アタッチ、デタッチの機能を提供します。
- CSIノード・サーバは、ノードに対するマウント、アンマウントの機能を提供します。
次の図は、CSIの通信プロセスを示しています。CSIコントローラー・サーバーと外部CSIサイドカーはUNIXソケットで通信し、CSIノード・サーバーとkubeletはUNIXソケットで通信します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/a7f9d4bd-e07f-c822-7334-35a0b6daa1dd.png)


以下の表は、CSIのAPIの一覧です。APIは一般管理API、ノード管理API、中央管理APIに分かれています。

- 一般管理APIはCSIに関する一般的な情報（プラグイン名、ドライバー・ アイデンティティ、プラグイン機能など）を返します。
- ノード管理 API である NodeStageVolume および NodeUnstageVolume は、FlexVolume における MountDevice および UnmountDevice に相当します。NodePublishVolume および NodeUnpublishVolume の API は、SetUp および TearDown の API と同じです。
- 中央管理APIのCreateVolumeとDeleteVolumeは、ボリュームの作成と削除を行い、ControllerPublishVolumeとControllerUnPublishVolumeは、アタッチとデタッチの操作に使用されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/c2bfba7d-4cad-9506-f8dc-1aefd3625302.png)

###CSIのシステム構成
CSIは、CRDの形式で実装されています。そのため、以下のオブジェクト・タイプを導入します。VolumeAttachment、CSINode、CSIDriver、およびCSIコントローラ・サーバとCSIノード・サーバの実装。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/389e857d-8836-0d5d-4e69-3b18c286af31.png)


CSIコントローラーサーバーのADコントローラーとボリュームプラグインは、Kubernetesのものと同様で、VolumeAttachmentオブジェクトを作成しています。

また、CSIコントローラーサーバーには複数の外部プラグインがあり、それぞれCSIプラグインと組み合わせることで特定の機能を実装します。例えば

- External-provisionerとコントローラーサーバーを組み合わせて、ボリュームの作成と削除を行います。
- 外部アタッカーとコントローラーサーバーを組み合わせて、ボリュームのマウントとアンマウントを行います。
- External-resizerとコントローラサーバを組み合わせてボリュームをスケールアウトします。
- External-snapshotterとコントローラサーバを組み合わせて、スナップショットの作成と削除を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/3c1f682e-6bce-cc8d-7401-aa65a91d752b.png)


CSIノード・サーバーには、ボリューム・マネージャーやボリューム・プラグインなどのkubeletコンポーネントが含まれています。これらのコンポーネントは、マウントとアンマウントの操作のためにCSIプラグインを呼び出します。ドライバーレジストラコンポーネントは、CSIプラグインの登録機能を実装しています。

これがCSIの全体的なトポロジーです。以下のセクションでは、それぞれのオブジェクトとコンポーネントについて説明します。

###CSI オブジェクト
ここでは、VolumeAttachment、CSIDriver、CSINodeの各オブジェクトについてご紹介します。

VolumeAttachmentは、Pod内のボリュームのマウントおよびアンマウントに関する情報を記述します。たとえば、ボリュームがノードにマウントされた場合、VolumeAttachmentを使用して追跡します。ADコントローラはVolumeAttachmentを作成し、external-attacherはVolumeAttachmentのステータスに従ってボリュームをマウントまたはアンマウントします。

次の図は、VolumeAttachmentの例を示しています。VolumeAttachmentでは、kindにVolumeAttachmentを設定しています。specには、attacherにはマウントのオペレータを指定する ossplugin.csi.alibabacloud.com、nodeNameにはマウントノードを指定する cn-zhangjiakou.192.168.1.53、persistentVolumeNameにはマウントおよびアンマウントのボリュームを指定する oss-csi-pv for sourceが設定されています。

statusでは、attachedでアタッチメントの状態を指定します。値が false の場合、external-attacher はアタッチ操作を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/ea6a2245-b598-766f-f30f-8cd356e0d781.png)


CSIDriverには、クラスタに配置されたCSIプラグインのリストが記述されています。これらのプラグインは、プラグインの種類に応じて管理者が作成する必要があります。

例えば、次の図のようにいくつかのCSIDriverが作成されます。kuberctl get csidriverコマンドを実行すると、ディスク、Apsara File Storage NAS（NAS）、Object Storage Service（OSS）の3種類のCSIDriverが表示されます。

CSIDriverの名前が定義されており、仕様としてattachRequiredタグとpodInfoOnMountタグが定義されています。

- attachRequired は、プラグインがアタッチ機能をサポートしているかどうかを指定します。これは、ブロックストレージとファイルストレージを区別するために使用されます。例えば、ファイルストレージにAttach操作が必要ない場合は、このタグをfalseに設定します。
- podInfoOnMountは、マウントAPIの呼び出し時にKubernetesがPod情報を運ぶかどうかを定義します。


![スクリーンショット 2021-05-07 10.00.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/70ae30ef-5435-8afe-6607-c2169c1cb771.png)



CSINode はクラスタ内のノード情報で、ノード・ドライバ・レジストラの起動時に作成されます。CSINode の情報は、CSI プラグインが新規に登録されるたびに CSINode リストに追加されます。

下図のように、CSINode リストが定義され、各 CSINode は固有の情報を持っています（左の YAML ファイル）。cn-zhangjiakou.192.168.1.49を例にとります。ここには、ディスクCSIDriverとNAS CSIDriverが含まれています。各CSIDriverには、nodeIDとtopologyKeysがあります。トポロジー情報がない場合は、topologyKeysをnullに設定します。このようにして、クラスタに10のノードがある場合、特定のノードに対してのみCSINodesを定義することができます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/167c3c63-3745-ed8e-e93b-6709a875eb5c.png)


###Node-driver-registrar
Node-driver-registrar は、下図のように、CSI プラグインの登録を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/2e926fde-d704-6454-d289-3203d6950321.png)

- ステップ1：起動時に規約を指定します。例えば、/var/lib/kuberlet/plugins_registry ディレクトリにファイルを追加することは、プラグインを追加することと同じです。
起動後、ノード・ドライバー・レジストラは CSI プラグインの GetPluginInfo API を呼び出します。そして、API は CSI のリスニング・アドレスと CSI プラグインのドライバ名を返します。

- ステップ2： ノード・ドライバー・レジストラは、GetInfo APIとNotifyRegistrationStatus APIをリッスンします。
- ステップ 3: `/var/lib/kuberlet/plugins_registry directory`にあるソケットを起動して、diskplugin.csi.alibabacloud.com-reg.sock などのソケットファイルを生成します。kubelet はウォッチャーを通してこのソケットを発見し、このソケットを通してノードドライバーレジストラの GetInfo API を呼び出します。GetInfo は、取得した CSI プラグインの情報（CSI プラグインのリスニング・アドレスやドライバ名など）を kubelet に返します。
- ステップ 4: キューブレットは、取得したリスニング・アドレスを元に、CSIプラグインのNodeGetInfo APIを呼び出します。
- ステップ 5: APIの呼び出し後、キューブレットはノードのアノテーション、タグ、status.allocatableなどのステータス情報を更新し、CSINodeオブジェクトを作成します。
- ステップ 6: ノード・ドライバー・レジストラの NotifyRegistrationStatus API を呼び出すことで、CSI プラグインが登録されたことを kubelet が通知します。
以上の手順で、CSIプラグインの登録が完了します。

###External-attacher
External-attacher は、CSI プラグインの API を呼び出して、ボリュームをマウントおよびアンマウントします。VolumeAttachment のステータスに応じて、ボリュームをマウントまたはアンマウントします。AD コントローラは、ボリューム・プラグインの CSI アタッチャを呼び出し、 VolumeAttachment を作成します。CSIアタッカーは、Kubernetesが実装するIn-Tree APIです。

VolumeAttachmentのステータスがFalseの場合、external-attacherは下位レイヤーのattach関数を呼び出します。目的の値が False の場合、デタッチ機能は ControllerPublishVolume API を通じて実装されます。また、external-attacherは特定のPV情報を同期します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/0966eeba-1d73-4ef2-0dab-6f9aa5d83a8b.png)


###CSIの展開
ここでは、ブロックストレージの展開について説明します。

前述のとおり、CSI コントローラは、CSI コントローラ・サーバと CSI ノード・サーバに分かれています。

CSIコントローラー・サーバーは1台だけ配置する必要があります。複数のバックアップを行う場合は、2台のCSIコントローラー・サーバーを配置します。CSIコントローラー・サーバーは、複数の外部プラグインによって実装されます。例えば、複数の外部コンテナと、CSI コントローラー・サーバーを含むコンテ ナをPodに定義することができます。この場合、異なる機能を提供するために、異なる外部コンポーネントを CSI コントローラー・サーバーと組み合わせます。

CSIノード・サーバーはDaemonSetであり、各ノードに登録されます。kubeletはCSIノード・サーバとソケットを介して直接通信し、attach、detach、mount、unmountの各メソッドを呼び出します。

ドライバーレジストラは、登録機能のみを提供し、各ノードに配置されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/82cf934f-d405-0a48-2ae0-cf053a80ed94.png)

ファイルストレージの展開は、外部アタッカーやVolumeAttachmentが利用できないことを除けば、ブロックストレージの展開と同様です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/8ba7ac18-e820-46f3-1a54-4818378fb704.png)


###CSIのケース
CSIのテンプレートはFlexVolumeのテンプレートと似ています。

主な違いは、テンプレートのタイプがCSIで、driver、volumeHandle、volumeAttribute、およびnodeAffinityを定義していることです。

- driver はマウントに使用するプラグインを定義します。
- volumeHandleは、PVのユニークなタグを示します。
- volumeAttribute はパラメータの追加に使用します。例えば、PVがOSSとして定義されている場合、volumeAttributeにはバケットやアクセスアドレスなどの情報を定義することができます。
- nodeAffinity は、一部のスケジューリング情報を定義します。FlexVolumeと同様に、セレクタやラベルにはいくつかの結合条件を定義できます。

下図の中段は、ダイナミック・スケジューリングの例で、CSI プロビジョナーが定義されている以外は、他のタイプと同じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/23d34ab5-4984-9fe3-931e-02f224dc8209.png)

マウントの例を示します:

起動したPodは、/dev/vdbを/dataにマウントします。クラスタ内にはGlobalPathとPodPathがあります。GlobalPath には /dev/vdb をマウントしますが、これはノード上の CSI PV の固有のディレクトリです。PodPathはPod上のローカルディレクトリで、Pod上のディレクトリをコンテナにマッピングします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/8331190d-6e7b-0d27-7d83-c61f831142d9.png)

###CSIのその他の機能
CSIにはマウント、アンマウント以外にもいくつかの機能があります。例えば、テンプレートに必要なユーザー名とパスワードの情報をSecretで定義することができます。前述のFlexVolumeもこの機能をサポートしています。ただし、CSIはマウント時のSecretとプロビジョン時のSecretのように、ステージごとに異なるSecretを定義することができます。

Topologyは、トポロジー認識機能です。クラスタ内のすべてのノードが、定義されたボリュームの要件を満たせるとは限りません。例えば、ボリューム内の異なるゾーンをマウントする必要があるかもしれません。そのような場合に、トポロジーを意識した機能を使用することができます。関連記事を参考にしてください。

ブロック・ボリュームはボリューム・モードを定義します。ボリューム・モードにはブロック・タイプとファイル・システム・タイプがあります。CSIはブロック・ボリュームをサポートします。つまり、ボリュームがPodにマウントされている場合、それはディレクトリではなくブロック・デバイスです。

Skip Attach と PodInfo On Mount は CSIDriver の 2 つの機能です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/78481efe-65d6-d1b4-6be8-eaa82035ac4e.png)

CSIの最新機能

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/dd537867-4bb0-5f9c-cbac-a6cacdc53505.png)

CSIは新しい実装方法です。最近、多くのアップデートが発表されています。例えば、ExpandCSIVolumesではファイルシステムのスケールアップ、VolumeSnapshotDataSourceではボリュームのスナップショットの取得、VolumePVCDataSourceではPVCデータソースの定義、CSIInlineVolumeではボリュームにいくつかのCSIDriverを直接定義することができます。

Alibaba Cloudは、[Alibaba Cloud Kubernetes CSI Plugin](https://github.com/kubernetes-sigs/alibaba-cloud-csi-driver?spm=a2c65.11461447.0.0.313bbd79F0oh9I)をGitHubで提供しています。

#4. まとめ
この記事では、[Kubernetes](https://www.alibabacloud.com/product/kubernetes?spm=a2c65.11461447.0.0.313bbd79F0oh9I)クラスターにおけるボリュームの機能を紹介しました。

- 第1部では、ボリュームの概念、マウントプロセス、システムコンポーネントを含むKubernetesストレージアーキテクチャについて説明しました。
- 第2部では、FlexVolumeプラグインの実装原則、導入アーキテクチャ、使用例について説明しました。
- 第3部では、CSIプラグインの実装原則、リソースオブジェクト、コンポーネント、使用例について説明しました。
この記事が、ボリュームの設計、開発、トラブルシューティングに役立つことを願っています。

*本ブログは英語版からの翻訳です。オリジナルは[こちら](https://www.alibabacloud.com/blog/getting-started-with-kubernetes-%7C-storage-architecture-and-plug-ins_596307)からご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。*

*アリババクラウドは日本に２つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
[アリババクラウドジャパン公式ページ](https://www.alibabacloud.com/ja)*