Certified Kubernetes Administrator (CKA) #17 Storage 2: StorageClass、動的プロビジョニング、reclaim policy、expansion

#16 Storage 1 では PV と PVC、そして管理者が PV をあらかじめ作っておく 静的プロビジョニング を扱いました。静的方式は動作は確実ですが、ユーザーがストレージを要求するたびに管理者が手で PV を作る必要があります。ノードが数十台あり PVC 要求が頻繁に入るクラスターでは、この方式はすぐにボトルネックになります。

今回の記事はそのボトルネックをなくす StorageClass と動的プロビジョニング です。PVC を 1 つ作るだけで PV が自動的に生まれるようにし、その PV を消すときデータを残すか消すかを決める reclaim policy、そしてすでに使っているボリュームを無停止で大きくする volume expansion まで運用の観点で整理します。

静的プロビジョニングの限界 #

#16 で見た静的方式の流れを思い出してみます。管理者が先に PV を作っておくと、ユーザーが PVC で容量とアクセスモードを要求し、コントローラーが条件に合う PV を探して PVC に結びつけます (binding)。問題は PV をあらかじめ作っておかなければならない という点です。

  • ユーザーが要求する容量と数を管理者が前もって知ることはできません。
  • 要求が来るたびに管理者が手で PV を作ると運用の負担が大きくなります。
  • あらかじめ作っておいた PV の容量が要求と正確に合わないと資源が無駄になります。

動的プロビジョニングはこの流れを逆転させます。ユーザーが PVC を作る瞬間、その PVC が指す StorageClass が PV をその場で自動的に作ってくれます。 管理者は PV を 1 つずつ作る必要がなく、どんな種類のストレージをどう作るかを定義する StorageClass を 1 つ用意するだけで済みます。

StorageClass とは #

StorageClass は ストレージの種類 (class) を定義するクラスター単位のオブジェクト です。「この等級のストレージを要求したら、こういう方式でボリュームを作れ」というテンプレートです。ネームスペースには属しません。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
  type: gp3
  fsType: ext4
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true

各フィールドの意味は次のとおりです。

  • provisioner: 実際にボリュームを作る主体です。どの CSI ドライバーまたは in-tree プラグインがプロビジョニングを担当するかを指定します。クラウドなら EBS・PD・Disk などの CSI ドライバー、オンプレミスなら NFS・Ceph・ローカルプロビジョナーが入ります。
  • parameters: provisioner に渡すオプションです。ディスクタイプ (gp3・premium)、ファイルシステム、レプリカ数のような値が provisioner ごとに異なって定義されます。
  • reclaimPolicy: この StorageClass が作った PV を、PVC が消えた後どう処理するかです。後で詳しく扱います。
  • volumeBindingMode: PV をいつバインドしてプロビジョニングするかを決めます。この値が動作に大きく影響します。
  • allowVolumeExpansion: この値が true でないと、後で PVC の容量を大きくできません。

動的プロビジョニングの流れ #

動的プロビジョニングでユーザーが作るのは PVC 1 つだけです。PVC が storageClassName で StorageClass を指定すると、その StorageClass の provisioner が条件に合う PV を自動的に作って PVC に結びつけます。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: data-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 20Gi

この PVC を適用すると次の順序で処理が進みます。

  1. ユーザーが data-pvc を作ります。storageClassName: fast-ssd で等級を指定します。
  2. fast-ssd StorageClass の provisioner が 20Gi のボリュームを実際のストレージ (例: EBS) に作ります。
  3. そのボリュームを表す PV が自動的に生成され、data-pvc にバインドされます。
  4. Pod がこの PVC をマウントすると、ボリュームを使い始めます。

管理者が PV を手で作ったことが一度もないという点が核心です。PVC と StorageClass だけで PV が自動的に登場しました。

# StorageClass 一覧 (default 表示の確認)
k get storageclass

# PVC が PV にバインドされたか確認
k get pvc data-pvc
k get pv

default StorageClass #

PVC で storageClassName を省略すると、クラスターの default StorageClass が使われます。default は StorageClass に付けた annotation で指定します。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: ebs.csi.aws.com
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer

annotation の値が "true" の StorageClass が default になります。既存の default を別の StorageClass に切り替えるには、現在の default の annotation を "false" に下げ、新しい StorageClass の annotation を "true" に上げます。default は 1 つだけにしておくのが安全です。2 つ以上あるとどれが選ばれるか予測しにくくなります。

# コマンドで default を切り替え
k patch storageclass standard \
  -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

storageClassName: "" (空文字列) を明示すると、default を使わず動的プロビジョニングをオフにするという意味です。この場合、静的に作っておいた PV にのみバインドされます。この区別が試験でよく混乱する箇所です。

volumeBindingMode #

volumeBindingModePV をいつ作って結びつけるか を決めます。2 つの値の違いがスケジューリングに直結するので、正確に理解する必要があります。

動作用途
ImmediatePVC を作った直後に PV をプロビジョニングしてバインドどこからでもアクセス可能なネットワークストレージ
WaitForFirstConsumerこの PVC を使う Pod がスケジュールされるまでバインドを遅らせるトポロジー制約のあるストレージ (特定の zone・ノード)

Immediate は PVC ができるとすぐにボリュームを作ります。ところがクラウドディスクやローカルボリュームのように 特定の zone やノードに紐づくストレージ だと問題が起きます。Pod がまだどのノードに行くか決まっていないのにボリュームが先に 1 つの zone に作られると、スケジューラーが別の zone のノードを選んだときボリュームをマウントできません。

WaitForFirstConsumer はこの問題を避けます。Pod が実際にスケジュールされるまでバインドとプロビジョニングを遅らせ、スケジューラーがノードを決めた後にそのノードのトポロジーに合うボリュームを作ります。トポロジー制約のあるストレージでは、この値が事実上のデフォルト選択です。

reclaim policy: データを守るか #

reclaimPolicyPVC が削除されて PV が解放された後、その PV と実際のデータをどう処理するか を決めます。StorageClass に指定すると、そのクラスが作る PV がその方針を引き継ぎます。

PVC 削除後の動作データ
DeletePV と実際のボリューム (EBS ディスクなど) を一緒に削除消える
RetainPV を Released 状態で残し、ボリュームも保存保存される

Delete は動的プロビジョニングの既定値です。PVC を消すと PV も、その背後の実際のディスクも一緒に削除されます。一時データや再生成可能なデータに適しており、資源の無駄がありません。ただし 誤って PVC を消すとデータがすぐに消える というリスクがあります。

Retain は PVC を消しても PV と実際のデータを残します。PVC が消えると PV は Released 状態になりますが、この状態の PV は自動的に別の PVC にバインドされません。データを回収したり再利用したりするには、管理者が手で介入する必要があります。

# Released 状態の PV の claimRef を空にして再び Available にする
k patch pv <pv-name> -p '{"spec":{"claimRef": null}}'

データベースのように 失ってはいけないデータRetain を使い、PVC の削除がそのままデータの削除につながらないようにするのが安全です。すでに作られた PV の方針は次のように事後でも変えられます。

# 特定の PV だけ Retain に変更 (誤削除の防止)
k patch pv <pv-name> \
  -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}'

StorageClass の reclaimPolicy は、そのクラスが これから作る PV に適用されます。すでに存在する PV には影響しないので、既存の PV を守るには上のように PV を直接 patch する必要があります。

volume expansion: ボリュームを大きくする #

運用中にディスクが埋まってきたらボリュームを大きくする必要があります。Kubernetes は PVC の要求容量を大きくする方式で 無停止の拡張 をサポートします。条件は 2 つです。

  • 該当する StorageClass に allowVolumeExpansion: true が設定されている必要があります。
  • provisioner (CSI ドライバー) が拡張をサポートする必要があります。

拡張は PVC の spec.resources.requests.storage の値をより大きな値に直せば済みます。

# 20Gi の PVC を 50Gi に拡張
k patch pvc data-pvc \
  -p '{"spec":{"resources":{"requests":{"storage":"50Gi"}}}}'

注意すべき点は 縮小は不可能 だということです。容量は大きくすることだけができ、縮めることはできません。また拡張は実際のストレージを大きくする作業とファイルシステムを大きくする作業に分かれますが、ファイルシステムの拡張が Pod が立ち上がった状態で終わる場合もあれば、Pod の再起動を要求する場合もあります。どちらかは CSI ドライバーに依存するので、拡張後に PVC と Pod の状態を一緒に確認する習慣が必要です。

# 拡張の進行状況を確認
k get pvc data-pvc
k describe pvc data-pvc   # Conditions で FileSystemResizePending などを確認

CSI 一行まとめ #

provisioner に入る ebs.csi.aws.com のような値が、まさに CSI (Container Storage Interface) ドライバー です。CSI は Kubernetes がさまざまなストレージベンダーのボリュームを標準的な方式で扱えるよう定義したインターフェースであり、動的プロビジョニング・スナップショット・拡張のような機能は、すべて CSI ドライバーがそれをサポートするときに動作します。

試験ポイント #

CKA の Storage ドメインは比重が 10% ですが、動的プロビジョニングと reclaim policy は出題頻度が高いです。次を手に覚えさせておきます。

  • default StorageClass の切り替え: あるクラスの default annotation を "false" に下げ、別のクラスを "true" に上げる作業がよく出ます。default が 2 つにならないようにします。
  • storageClassName の省略と空文字列の違い: 省略すると default を使い、"" だと動的プロビジョニングをオフにして静的 PV にのみバインドされます。
  • reclaimPolicy の変更によるデータ保護: 特定の PV を Retain に変えて PVC 削除時にデータが消えないようにする作業が典型的です。k patch pv を覚えておきます。
  • Released PV の再利用: Retain の PV は claimRef を空にしてはじめて再び Available になります。
  • volume expansion: allowVolumeExpansion: true を確認した後、PVC の requests.storage を大きくします。縮小は不可能です。
  • WaitForFirstConsumer: PVC が Pending なのに原因が「Pod がまだスケジュールされていないため」というケースを、このモードで説明できる必要があります。

特に PVC が Pending で止まる状況はトラブルシューティングに直結します。WaitForFirstConsumer なら Pod を付けるまでは意図された Pending であり、default StorageClass がなかったり storageClassName が間違っていたりする場合は本物のエラーです。この 2 つを k describe pvc のイベントで区別する感覚が点数を分けます。

まとめ #

この記事で押さえたこと:

  • StorageClass はストレージ等級を定義するクラスター単位のテンプレートです。provisioner・parameters・reclaimPolicy・volumeBindingMode・allowVolumeExpansion で構成されます。
  • 動的プロビジョニング は PVC が StorageClass を指定すると PV が自動的に作られる方式です。管理者が PV を手で作る必要がありません。
  • default StorageClass は annotation で指定し、storageClassName の省略時に適用されます。空文字列は動的プロビジョニングをオフにする意味です。
  • volumeBindingModeImmediate (即時) と WaitForFirstConsumer (Pod のスケジュールまで待機) に分かれ、トポロジー制約のあるストレージでは後者が安全です。
  • reclaimPolicyDelete (PV・データを一緒に削除) と Retain (保存) に分かれます。重要なデータは Retain で守ります。
  • volume expansionallowVolumeExpansion: true のとき PVC の容量を大きくして無停止で拡張します。縮小は不可能です。

次へ: Networking 1 #

ストレージを終えたので、ここからネットワーキングに移ります。Pod はいつでも死んで再び立ち上がり IP が変わるので、安定したアクセスポイントが必要です。そのアクセスポイントが Service です。

#18 Networking 1: Service では、ClusterIP (クラスター内部アクセス)、NodePort (ノードポート公開)、LoadBalancer (外部負荷分散)、ExternalName (DNS エイリアス) がそれぞれどう動作するか、selector と endpoint がどう結びつくか、そして kube-proxy がトラフィックをどう流すかまで YAML で整理します。

X