Certified Kubernetes Administrator (CKA) #16 Storage 1: Volume の種類、PV、PVC の静的プロビジョニング

#15 リソース管理 で Pod が CPU とメモリをどう要求し制限されるかを扱ったなら、この記事からは データ です。コンテナの中のファイルシステムはコンテナが死ねば一緒に消える一時的な空間なので、DB データやユーザーのアップロードファイルのように Pod のライフサイクルを越えて生き残らなければならないデータには別の保存モデルが必要です。Kubernetes はこれを Volume、PersistentVolume、PersistentVolumeClaim という 3 つの抽象で表現します。

この記事は Storage ドメインの最初の編として、Volume の種類と PV・PVC の静的プロビジョニングを整理します。動的プロビジョニングと StorageClass は #17 に続きます。Storage は試験比重が 10% で大きくはありませんが、PV と PVC のバインディング規則は 1 文字でもずれると Pending に引っかかるので、手に覚えておく方が安全です。

Volume が解く 2 つの問題 #

Pod の中のコンテナが使うファイルシステムは 2 つの限界を持ちます。1 つ目は、コンテナが再起動するとその間に書いたファイルが消えることです。2 つ目は、1 つの Pod の中の複数のコンテナが同じファイルを共有する手段がないことです。Volume はこの 2 つをまとめて解決する抽象です。Volume は Pod の spec.volumes に宣言され、各コンテナの volumeMounts で特定のパスにマウントされます。

ただし Volume の寿命は種類によって異なります。ある Volume は Pod が消えると一緒に消え、ある Volume は Pod と無関係に生き残ります。この違いがそのまま Volume の種類を分けます。

Volume の種類 #

CKA で知っておくべき Volume は大きく 4 つに分かれます。

種類寿命用途
emptyDirPod と同じPod 内のコンテナ同士の一時共有、キャッシュ・scratch
hostPathノードのディスクに依存ノードの特定パスをマウント (単一ノード・テスト)
configMap / secretPod と同じ (ソースは別オブジェクト)設定・秘密情報をファイルとして注入
persistentVolumeClaimPVC・PV の寿命本物の永続ストレージ

emptyDir: Pod の寿命の一時空間 #

emptyDir は Pod がノードにスケジュールされるときに空のディレクトリとして作られ、Pod がノードから削除されると一緒に消えます。同じ Pod の中の 2 つのコンテナがファイルをやり取りするときに最もよく使います。

apiVersion: v1
kind: Pod
metadata:
  name: emptydir-demo
spec:
  containers:
    - name: writer
      image: busybox
      command: ["sh", "-c", "echo hello > /cache/data; sleep 3600"]
      volumeMounts:
        - name: scratch
          mountPath: /cache
    - name: reader
      image: busybox
      command: ["sh", "-c", "sleep 3600"]
      volumeMounts:
        - name: scratch
          mountPath: /shared
  volumes:
    - name: scratch
      emptyDir: {}

scratch という Volume を 2 つのコンテナがそれぞれ別のパスにマウントします。writer が /cache に書いたファイルを reader は /shared で読みます。Pod が死ぬとこのデータは消えます。

hostPath: ノードのディスクを直接マウント #

hostPath はノードの特定のパスを Pod の中にマウントします。データがノードのディスクに残るので Pod の再起動には耐えますが、Pod が別のノードへ移るとそのデータにアクセスできません。そのため運用クラスターの一般的なワークロードにはほとんど使わず、単一ノード環境や、ノード自体のログ・ソケットを読むシステムコンポーネントに限られます。

apiVersion: v1
kind: Pod
metadata:
  name: hostpath-demo
spec:
  containers:
    - name: app
      image: busybox
      command: ["sh", "-c", "sleep 3600"]
      volumeMounts:
        - name: node-data
          mountPath: /data
  volumes:
    - name: node-data
      hostPath:
        path: /var/lib/node-data
        type: DirectoryOrCreate

configMap と secret: 設定をファイルとして #

configMap と secret は #12 で扱ったオブジェクトを Volume としてマウントする形です。環境変数の代わりにファイルとして注入したいときに使います。このとき Volume のデータ自体は ConfigMap・Secret オブジェクトに入っており、Volume はそれをコンテナのパスへ展開するだけです。

PersistentVolume: クラスターが持つストレージの断片 #

emptyDir と hostPath はどちらもノードに縛られていて、本物の永続性を与えられません。Pod がどのノードへ行っても同じデータを保証するにはノードの外のストレージが必要で、Kubernetes はそのストレージ 1 つを PersistentVolume (PV) というクラスターレベルのオブジェクトで表現します。PV は namespace に属さず、管理者があらかじめ作っておくか、StorageClass が動的に作り出します。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nfs-5g
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  nfs:
    server: 10.0.0.10
    path: /exports/data

capacity と accessModes #

capacity.storage はこの PV が提供する容量です。accessModes はこの PV をどうマウントできるかを宣言するもので、4 種類あります。

accessMode略称意味
ReadWriteOnceRWO1 つのノードから読み書き
ReadOnlyManyROX複数のノードから読み取り専用
ReadWriteManyRWX複数のノードから読み書き
ReadWriteOncePodRWOPたった 1 つの Pod のみ読み書き

ここで RWO の「Once」は Pod 1 つ ではなく ノード 1 つ を意味するという点が試験によく出ます。1 つのノードに乗っている複数の Pod は RWO PV を同時にマウントできます。本当に Pod 1 つだけを許可するには RWOP を使わなければなりません。そして、どの accessMode を実際に使えるかはストレージの種類が決めます。NFS は RWX をサポートしますが、単純なブロックデバイスベースは RWO のみサポートする、といった具合です。

persistentVolumeReclaimPolicy #

persistentVolumeReclaimPolicy は、PVC が削除されて PV が解放されたときに、その PV をどう処理するかを定めます。

ポリシー動作
RetainPV を保存して Released 状態に置く。データは保存し、再利用は管理者が手動で処理
DeletePV と backing ストレージを一緒に削除
Recycle(廃止) データを消して再利用。現在は使わない

静的プロビジョニングで作った PV は、データを守るために通常 Retain を使います。Retain で解放された PV は Released 状態になり、この状態では新しい PVC に自動でバインディングされません。再び使うには、管理者が PV の claimRef を空にしてデータを整理し、Available に戻す必要があります。

PersistentVolumeClaim: ユーザーの要求 #

PV が「クラスターにこれだけのストレージがある」という供給なら、PersistentVolumeClaim (PVC) は「これだけのストレージをこういう条件でくれ」という需要です。PVC は namespace に属し、Pod は PV を直接参照せず、常に PVC を経由します。この分離のおかげで、アプリ開発者はストレージの実際の種類を知らなくても、容量とアクセスモードだけを書いて要求できます。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-data
  namespace: default
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi
  storageClassName: manual

PVC が PV にバインディングされる規則 #

PVC を作ると、コントローラーが条件に合う Available 状態の PV を探して 1 対 1 でバインディングします。バインディングが成立するには、次の 3 つがすべて合致する必要があります。

  1. 容量。PV の capacity.storage が PVC の requests.storage 以上でなければなりません。PVC が 5Gi を要求したのに PV が 3Gi ならバインディングされません。逆に PV がより大きければバインディングはされますが、余る容量は捨てられます (静的 PV は分割されません)。
  2. accessModes。PVC が要求したすべての accessMode を PV がサポートしなければなりません。
  3. storageClassName。PVC と PV の storageClassName が同じでなければなりません。静的プロビジョニングでは両方とも manual のような同じ名前を書くか、両方とも空にします。

このうち 1 つでもずれると PVC は Pending にとどまります。試験で PVC が Bound に移らないときは、上の 3 項目を PV と並べて比較するのが最も速い診断です。selector (spec.selector) で特定のラベルの PV だけを受けるよう絞ることもできます。

# PVC と PV の状態を並べて確認
k get pvc,pv

# バインディングできないときに理由を確認
k describe pvc pvc-data

Pod で PVC をマウント #

PVC が PV に Bound されると、Pod はその PVC を Volume として参照してマウントします。Pod の立場では、裏に NFS があってもクラウドディスクがあっても気にする必要はなく、PVC 名だけ書けば済みます。

apiVersion: v1
kind: Pod
metadata:
  name: app-with-pvc
spec:
  containers:
    - name: app
      image: nginx
      volumeMounts:
        - name: data
          mountPath: /usr/share/nginx/html
  volumes:
    - name: data
      persistentVolumeClaim:
        claimName: pvc-data

volumespersistentVolumeClaim.claimName が先ほど作った PVC を指します。この PVC がまだ Pending なら Pod もスケジュールされず、ContainerCreating か Pending に引っかかります。つまり Pod の起動は PVC のバインディングに、PVC のバインディングは条件に合う PV の存在に、順に依存します。

静的プロビジョニング: 管理者が PV をあらかじめ作る #

ここまでの流れがそのまま 静的プロビジョニング です。管理者がストレージをあらかじめ準備して PV オブジェクトとして登録しておき、ユーザーは PVC でそのうちの 1 つを要求して使います。PV が先に存在し、PVC がそれを選び取る順序です。

管理者: PV 作成   →  PV(Available)
                       ↑ バインディング (容量・accessModes・SC 一致)
ユーザー: PVC 作成 →  PVC  →  Pod が PVC をマウント

静的プロビジョニングは、NFS サーバーのようにあらかじめ決まったストレージがある環境、またはどのストレージを誰が使うかを管理者が統制したい環境に合います。限界もはっきりしています。新しい要求が来るたびに管理者が PV を手で作らなければならず、容量が正確に合わないと空間が無駄になります。この手作業をなくすのが #17 で扱う動的プロビジョニングです。

全体の流れを一度に #

PV、PVC、Pod をまとめて静的プロビジョニングを 1 サイクル回してみます。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-manual-1g
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: manual
  hostPath:
    path: /mnt/data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-manual
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 500Mi
  storageClassName: manual
---
apiVersion: v1
kind: Pod
metadata:
  name: pv-consumer
spec:
  containers:
    - name: app
      image: nginx
      volumeMounts:
        - name: store
          mountPath: /data
  volumes:
    - name: store
      persistentVolumeClaim:
        claimName: pvc-manual

PVC は 500Mi を要求しましたが 1Gi の PV にバインディングされます (容量は要求以上なら成立)。accessModes と storageClass (manual) が両方とも一致するのでバインディングが成立します。適用後に状態を確認します。

k apply -f static.yaml
k get pv pv-manual-1g       # STATUS が Bound、CLAIM に default/pvc-manual
k get pvc pvc-manual        # STATUS が Bound、VOLUME に pv-manual-1g
k get pod pv-consumer       # STATUS が Running

試験ポイント #

CKA の Storage 作業で点数を分ける箇所を整理します。

  • RWO はノード単位。「Once」が Pod ではなくノードを意味する点を取り違えないようにします。Pod 1 つだけを許可するなら RWOP です。
  • バインディングの 3 条件。PVC が Pending なら、容量 (PV ≥ PVC)、accessModes、storageClassName の 3 つを PV と並べて照合します。3 つのうち 1 つでもずれるとバインディングされません。
  • storageClassName の一致。静的プロビジョニングでは PV と PVC の storageClassName を同じに書くか、両方とも空にします。片方だけ空にすると期待と違う動作をします。
  • reclaimPolicy Retain。Retain で解放された PV は Released 状態のまま残り、自動で再バインディングされません。データ保存が目的なら Retain です。
  • Pod は PVC のみ参照。Pod は PV を直接マウントしません。常に PVC を経由します。
  • 速い診断コマンドk get pvc,pv で状態を並べて見て、k describe pvc で Pending の理由を確認する流れを手に覚えておきます。

同じテーマをアプリ開発者の視点でより噛み砕いた記事として K8s 中級 #2 PV / PVC / StorageClass があります。モデルがうまくつかめないなら一緒に読むと役立ちます。

まとめ #

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

  • Volume の種類。emptyDir (Pod の寿命の一時空間)、hostPath (ノードのディスク)、configMap・secret (設定の注入)、persistentVolumeClaim (永続ストレージ)
  • PV。capacity、accessModes (RWO/ROX/RWX/RWOP)、persistentVolumeReclaimPolicy (Retain/Delete)。クラスターレベルのオブジェクトで namespace に属さない
  • PVC。容量とアクセスモードを要求する namespace オブジェクト。Pod は PVC のみ参照
  • バインディング規則。容量 (PV ≥ PVC)、accessModes、storageClassName の 3 条件がすべて合致して初めて Bound
  • 静的プロビジョニング。管理者が PV をあらかじめ作り、ユーザーが PVC で選んで使う。手作業と容量の無駄が限界

次へ: Storage 2 #

静的プロビジョニングには、管理者が PV を 1 つずつ作らなければならない負担がありました。#17 Storage 2 では、この手作業をなくす StorageClass と動的プロビジョニングを扱います。PVC を作ると PV が自動で生まれる流れ、reclaim policy が動的環境でどう動くか、allowVolumeExpansion で PVC の容量を増やす expansion、そして volumeBindingMode の WaitForFirstConsumer がスケジューリングとストレージの位置をどう合わせるかまで続けます。

X