ボリュームは三層に分かれている
一言でいうと
Kubernetesのストレージは、StorageClass(設計図) → PersistentVolume(実物) → PersistentVolumeClaim(要求書)の3層です。この3つは、それぞれ別の人の関心事を代弁しているので分かれており、ずれると、PVCが静かにPendingのままになります。
なぜ必要なのか
開発者は、「1Giで、複数のノードから同時に使えるディスク」を望みます。管理者は、「それはNASの/volume1/k8sの下に、NFS v4.1で、hardマウントでnconnect 4」という決定をします。この2つの関心事を1つのオブジェクトに混ぜると、開発者がストレージのベンダーを知らなければならなくなります。
そのため、PVCは要求だけを書き、StorageClassが、その要求を実物に変える方法を定義し、PVが実物を代表します。バインドの条件は単純です。storageClassNameが同じで、PVのaccessModesがPVCの要求を含み、PVの容量がPVCの要求以上なら、結び付きます。1つでもずれると、何のエラーもなくPendingになります。
CSIが登場した理由も、同じ分離の欲求です。以前は、ストレージプラグインがKubernetesのコアコードの中にあったため(in-tree)、新しいストレージを付けるには、Kubernetes自体を直さなければなりませんでした。CSIはその結合を断ち切り、ドライバーは、コントローラー(Deployment、ボリュームの作成/削除/拡張/スナップショット)とノードプラグイン(DaemonSet、マウント/アンマウント)に分かれます。ノードプラグインがDaemonSetである理由は、Podがスケジュールされうるすべてのノードに、存在する必要があるからです。
どう動くのか
StorageClassで間違えると、元に戻しにくいフィールドが3つあります。
| フィールド | 意味 | 間違えると |
|---|---|---|
reclaimPolicy |
PVC削除後のPVの運命 | Deleteにするとデータまで消える |
volumeBindingMode |
いつボリュームを作るか | ImmediateだとPodとは別のゾーンにボリュームができることがある |
allowVolumeExpansion |
あとで拡張できるか | falseで作ったクラスのPVCは、永久に拡張不可 |
WaitForFirstConsumerは、Podがスケジュールされるノードがわかってから、ボリュームを作ります。マルチゾーン環境では必須です。
アクセスモードは4つです。RWO(1つのノードで読み取り/書き込み)、ROX(複数のノードで読み取り専用)、RWX(複数のノードで読み取り/書き込み)、RWOP(単一のPod専用)です。ブロックストレージは、通常RWOまでで、RWXは、NFSのようなファイルストレージの領域です。
デフォルトのStorageClassが指定されたクラスターで、そのクラスを使わないようにするには、storageClassNameを省略するのではなく、空文字列で明示する必要があります。省略すると、デフォルト値が注入されます。この区別は、試験にも出ます。
現場での姿
事例1: マウントの確認が先です。ホームラボのクラスターでkubectl get scを実行すると、No resources foundでした。StorageClassがなければ、PVCは永遠にPendingで、状態を持つワークロードを1つも載せられません。自宅にあったSynologyのNASを接続することにしたのですが、ドライバーを導入する前に、Podから、NFSマウントができるかどうかを先に確認しました。最初の試みは失敗しました。
mount.nfs: Operation not permitted for 10.0.0.109:/volume1/k8s on /mnt/t
privileged: trueを指定したのに、拒否されました。足りなかったのは、hostNetwork: trueでした。NFSマウントはrpcbindと通信し、1024未満の予約ポートを送信元ポートとして使いますが、Podのネットワークネームスペースの中では、この動作が制約を受けます。privilegedはcapabilityを与えますが、ネットワークネームスペースの問題は解決しません。順序を守ったおかげで、原因がドライバーではないとすぐにわかりました。
事例2: クラスを用途別に分けます。csi-driver-nfs 4.13.4を導入したあと、StorageClassを2セット作成しました。
nfs-synology(デフォルト):reclaimPolicy: Delete、onDelete: archive、allowVolumeExpansion: true、マウントオプションnfsvers=4.1, hard, nconnect=4, noatimenfs-synology-retain:reclaimPolicy: Retain、onDelete: retain
hardを選んだ理由が、特に重要です。softは、タイムアウト時にI/Oエラーをアプリケーションに返しますが、データベースが書き込みの途中でエラーを受け取ると、静かなデータ破損につながる可能性があります。hardは、サーバーが戻ってくるまで無限にリトライするので、データは安全な代わりに、NASの障害時にPodがエラーなしに止まったように見え、原因の把握が難しくなります。すべてのストレージ設定は、このようなトレードオフです。
subDirのパターンも、네임스페이스-PVC이름-PV이름としました(プレースホルダーは、ネームスペース、PVC名、PV名です)。PV名だけを使うと、NASのファイルエクスプローラーには、pvc-da6bb53d-...のようなUUIDだけが並び、あとで何を削除してよいのかわかりません。
事例3: 容量は強制されません。PVCに2Giを要求しましたが、NFSにはそれを強制する手段がありません。Podが100GBを使っても、止められません。PVCの容量は、スケジューリングと会計目的のメタデータにすぎず、実際の制限は、バックエンドが行う必要があります。ブロックストレージは、ボリュームのサイズがそのまま物理的な限界なので、自然に強制されますが、NFSのサブディレクトリはそうではありません。
そして、archiveポリシーの代償も、実物で確認しました。NFS exportのルートを開いてみると、以前のクラスターが残したディレクトリが32個あり、PrometheusとOpenSearchのデータがそのまま残っていて、19.9Tのうち9.4Tを使っていました。安全な代わりに、誰も削除しなければ、ずっと積み上がります。
次のラボですること
StorageClassを2セット作成して3つのフィールドの違いを確認し、PVとPVCを静的にバインドし、RWXボリュームを作成し、StatefulSetのvolumeClaimTemplatesがPVCを自動作成するのを確認します。最後のステップは、デフォルトのStorageClassを明示的に拒否する方法です。