ข้ามไปยังเนื้อหา

PersistentVolume, Claim และ StorageClass

PersistentVolumeClaim คือ request ขอ storage ที่ Pod เอาไป mount ใช้ และจะ bind กับ PersistentVolume ที่เป็น storage ระดับ cluster ที่อยู่รอดข้าม Pod และตาม Pod ไปได้ข้าม node

emptyDir ตายไปพร้อม Pod ส่วน hostPath ผูกข้อมูลไว้กับ node เฉพาะตัว ทั้งสองแบบอยู่ไม่รอดถ้า Pod ถูก scheduler ย้ายไปรันบน hardware คนละตัว ซึ่ง scheduler มีสิทธิ์ทำแบบนั้นได้ตลอดเวลา workload แบบ stateful จริง ๆ (database, message queue หรืออะไรก็ตามที่ข้อมูลสำคัญ) ต้องการ storage ที่อยู่แยกอิสระจาก Pod หรือ node ตัวใดตัวหนึ่ง และให้ Pod ที่ถูกย้ายไปที่ใหม่ต่อกลับเข้าไปใช้ได้อีก

PersistentVolume (PV) คือ storage resource ระดับ cluster แนวคิดคล้าย Node ที่แทน compute capacity แต่ PV แทน storage capacity แทน admin ของ cluster provision PV แบบ static ล่วงหน้าได้ หรือจะให้ถูกสร้างแบบ dynamic ก็ได้ (อธิบายด้านล่าง)

PersistentVolumeClaim (PVC) คือ request ที่ user หรือ application ยื่นขอ “ขอ storage ขนาดเท่านี้ ด้วย access mode แบบนี้” Kubernetes จะ bind PVC เข้ากับ PV ที่ตรงกับ request นั้น ที่สำคัญคือ Pod reference แค่ PVC ไม่เคย reference PV ตรง ๆ Pod ไม่ต้องรู้หรือสนใจว่าไป bind กับ PV ตัวไหนจริง ๆ

apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-manual-10gi
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath:
path: /mnt/data
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: manual
apiVersion: v1
kind: Pod
metadata:
name: db
spec:
containers:
- name: postgres
image: postgres:18
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumes:
- name: data
persistentVolumeClaim:
claimName: data-claim

PV ประกาศว่ารองรับ access mode ไหนบ้าง ส่วน PVC ก็ขอ mode ที่ต้องการ

  • ReadWriteOnce (RWO) — mount แบบ read-write ได้ แต่แค่ Pod บน node เดียวในเวลาเดียวกันเท่านั้น
  • ReadOnlyMany (ROX) — mount แบบ read-only ได้จากหลาย node พร้อมกัน
  • ReadWriteMany (RWX) — mount แบบ read-write ได้จากหลาย node พร้อมกัน (มีแค่บาง storage backend ที่รองรับ)
  • ReadWriteOncePod (RWOP) — mode ใหม่กว่าและเข้มกว่า volume ถูกจำกัดให้ใช้ได้แค่ Pod เดียว ทั้ง cluster ไม่ใช่แค่จำกัดที่ node เดียว ส่วน ReadWriteOnce ธรรมดายังเปิดให้หลาย Pod share volume กันได้ถ้าอยู่ node เดียวกัน ReadWriteOncePod มาปิดช่องว่างนี้เวลาต้องการ single-writer guarantee จริง ๆ

การ provision PV มือทีละตัวไม่ scale StorageClass อธิบาย storage class หนึ่งประเภท พร้อม provisioner ที่สร้าง storage นั้นได้ตามต้องการ พอ PVC reference StorageClass แทนที่จะ reference PV ที่มีอยู่แล้ว driver แบบ CSI (Container Storage Interface) จะ provision PV ที่ตรงกันให้อัตโนมัติ admin ไม่ต้องมานั่งสร้างล่วงหน้าเลย

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: ebs.csi.aws.com
parameters:
type: gp3
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dynamic-claim
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast-ssd
resources:
requests:
storage: 20Gi
Terminal window
# Watch a PVC bind to a dynamically provisioned PV
kubectl get pvc dynamic-claim -w

reclaim policy กำหนดว่า storage เบื้องหลังจะเป็นยังไงหลัง PVC ของตัวเองถูกลบ

  • Delete — PV และ storage เบื้องหลัง (disk จริง) ถูกลบไปพร้อม PVC เป็นค่า default ของ StorageClass แบบ dynamic provisioning ส่วนใหญ่
  • Retain — PV และข้อมูลยังอยู่ต่อหลัง PVC ถูกลบ admin เลยกู้คืนหรือเคลียร์ข้อมูลเองได้ก่อนที่ storage เบื้องหลังจะถูกปล่อยจริง ๆ

volumeBindingMode ของ StorageClass คุมว่า PVC จะ bind เข้ากับ PV ตอนไหน Immediate (ค่า default แบบเดิม) จะ bind — และสำหรับ StorageClass แบบ dynamic ก็ provision ด้วย — ทันทีที่ PVC ถูกสร้าง ก่อนที่ Kubernetes จะรู้ด้วยซ้ำว่า Pod ไหนจะใช้หรือจะไปลงที่ node ไหน ส่วน WaitForFirstConsumer จะรอจนกว่า Pod ที่ reference PVC นี้มีอยู่จริงและกำลังถูก schedule ก่อน provisioner ถึงจะสร้าง volume ที่ตรงกับ node ที่ scheduler เลือกจริง ๆ ได้ (storage ที่ผูกกับ topology เช่น local disk หรือ zone เฉพาะ ต้องพึ่งพฤติกรรมนี้ ไม่งั้น PVC อาจ bind กับ storage ใน zone ที่ Pod ไปไม่ถึงเลย)

StorageClass standard ของ kind เองก็มากับ WaitForFirstConsumer อยู่แล้ว — พิสูจน์จริงบน cluster จริง (kind create cluster, Kubernetes 1.37.0, kind 0.33):

$ kubectl get storageclass -o wide
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
standard (default) rancher.io/local-path Delete WaitForFirstConsumer false 22m

สร้าง PVC เข้ากับ class นี้โดยยังไม่มี Pod ปล่อยให้ PVC ค้าง Pending จริง ๆ และ event จริงก็บอกเหตุผลตรง ๆ:

k8s/persistent-volumes-claims-and-storage-classes/wffc-demo.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: wffc-demo
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wffc-pvc
spec:
accessModes: ['ReadWriteOnce']
storageClassName: wffc-demo
resources:
requests:
storage: 64Mi
$ kubectl apply -f wffc-demo.yaml
storageclass.storage.k8s.io/wffc-demo created
persistentvolumeclaim/wffc-pvc created
$ kubectl get pvc wffc-pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
wffc-pvc Pending wffc-demo 0s
$ kubectl describe pvc wffc-pvc | tail -4
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal WaitForFirstConsumer 8s (x2 over 8s) persistentvolume-controller waiting for first consumer to be created before binding

พอมี Pod ที่ mount wffc-pvc ถูกสร้างขึ้น การ bind (และสำหรับ dynamic class ก็ provision ด้วย) จะเกิดขึ้นทันที:

$ kubectl apply -f wffc-pod.yaml
pod/wffc-consumer created
$ kubectl get pvc wffc-pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
wffc-pvc Bound pvc-1cd1cd2c-02c9-464b-a42a-33e01ef819c9 64Mi RWO wffc-demo 17s

PVC ที่ Pending อยู่บน StorageClass แบบ WaitForFirstConsumer ไม่ได้แปลว่าค้าง — มันทำงานตามที่ออกแบบไว้เป๊ะ ๆ และจะ Pending ต่อไปเรื่อย ๆ จนกว่าจะมี Pod มา reference จริง

allowVolumeExpansion: true บน StorageClass ทำให้ PVC ที่ bind แล้ว resize เพิ่มขนาดได้ทีหลังโดยแก้ spec.resources.requests.storage — โดยไม่ต้องลบสร้าง PVC ใหม่ (ลดขนาดไม่รองรับ ทำได้แค่ขยายขึ้นอย่างเดียว) ค่า default คือ false ตามที่ output ของ standard ด้านบนแสดงให้เห็นแล้วบน kind cluster นี้

k8s/persistent-volumes-claims-and-storage-classes/expand-demo.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: expand-demo
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
reclaimPolicy: Delete
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: expand-pvc
spec:
accessModes: ['ReadWriteOnce']
storageClassName: expand-demo
resources:
requests:
storage: 64Mi
$ kubectl apply -f expand-demo.yaml
storageclass.storage.k8s.io/expand-demo created
persistentvolumeclaim/expand-pvc created
$ kubectl patch pvc expand-pvc -p '{"spec":{"resources":{"requests":{"storage":"128Mi"}}}}'
persistentvolumeclaim/expand-pvc patched
$ kubectl get pvc expand-pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
expand-pvc Bound pvc-1f3fa7ee-25c9-4262-9091-ab17913f3f83 64Mi RWO expand-demo 39m
$ kubectl describe pvc expand-pvc | tail -3
Events:
...
Normal ExternalExpanding 5s volume_expand waiting for an external controller to expand this PVC

spec.requests.storage ยอมรับค่า 128Mi (API server ยอม edit เพราะ allowVolumeExpansion: true) แต่ status.capacity ยังคงอยู่ที่ 64Mi และไม่เคยมี condition FileSystemResizePending โผล่มาเลย — เหตุผลจริงที่พิสูจน์แล้วอยู่ใน event ตรงนั้นเลย: rancher.io/local-path (provisioner default ของ kind เอง) ไม่มี external-resizer controller ลงทะเบียนไว้ คำขอ resize เลยรอ controller ภายนอกที่ไม่มีอยู่จริงบน cluster นี้ นี่คือ finding จริงที่มีประโยชน์ ไม่ใช่สมมติฐาน: allowVolumeExpansion: true จำเป็นแต่ไม่พอ — provisioner ต้องมี resize sidecar/controller ที่ตรงกันติดตั้งอยู่จริงด้วย ตรงตาม เอกสาร Kubernetes เรื่อง CSI volume expansion และ CSI driver ของ cloud (ที่มี resizer controller ของตัวเองมาด้วย) จะพฤติกรรมต่างจากนี้

k8s/persistent-volumes-claims-and-storage-classes/reclaim-demo.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: retain-demo
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: retain-pvc
spec:
accessModes: ['ReadWriteOnce']
storageClassName: retain-demo
resources:
requests:
storage: 32Mi
$ kubectl apply -f reclaim-demo.yaml
storageclass.storage.k8s.io/retain-demo created
persistentvolumeclaim/retain-pvc created
pod/retain-consumer created
$ kubectl get pvc retain-pvc -o wide
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
retain-pvc Bound pvc-1eea386b-988f-4a79-a99f-d07812b445c1 32Mi RWO retain-demo 8s
$ kubectl delete pod retain-consumer --wait=true
pod "retain-consumer" deleted
$ kubectl delete pvc retain-pvc
persistentvolumeclaim "retain-pvc" deleted
$ kubectl get pv pvc-1eea386b-988f-4a79-a99f-d07812b445c1
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE
pvc-1eea386b-988f-4a79-a99f-d07812b445c1 32Mi RWO Retain Released default/retain-pvc retain-demo 41s

หลัง Pod กับ PVC หายไปทั้งคู่ PV ยังอยู่ — STATUS: Released ไม่ถูกลบ — ตรงตามพฤติกรรม reclaim policy “Retain” เป๊ะ: disk อยู่รอด แต่ก็ใช้ต่อทันทีไม่ได้อัตโนมัติเหมือนกัน (PVC ใหม่จะไม่ bind กับ PV ที่ Released จนกว่า admin จะเคลียร์ claimRef หรือลบสร้างใหม่) sequence เดียวกันกับ reclaimPolicy: Delete (default ของ class standard เองตามที่แสดงก่อนหน้า) จะลบทั้ง PV และ directory เบื้องหลังให้อัตโนมัติทันทีที่ PVC หายไป — ไม่มีสถานะ Released เลย object หายไปตรง ๆ

VolumeSnapshot คือการขอให้ CSI driver capture ข้อมูลของ PVC ณ ช่วงเวลาหนึ่ง ส่วน VolumeSnapshotClass ก็เหมือน StorageClass เวอร์ชัน snapshot — ระบุ CSI driver ที่รองรับ snapshot (driver:) กับ deletion policy การ restore คือสร้าง PVC ใหม่ ที่ spec.dataSource ชี้ไปที่ VolumeSnapshot แทนที่จะขอ storage เปล่า ๆ:

# k8s/persistent-volumes-claims-and-storage-classes/snapshot-demo.yaml — @expect-error no snapshot-capable CSI driver was installed on this cluster before the host became unresponsive; verified with --dry-run=server against verify-k8s instead
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: csi-hostpath-snapclass
driver: hostpath.csi.k8s.io
deletionPolicy: Delete
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: data-snapshot
spec:
volumeSnapshotClassName: csi-hostpath-snapclass
source:
persistentVolumeClaimName: expand-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: restored-pvc
spec:
accessModes: ['ReadWriteOnce']
storageClassName: expand-demo
dataSource:
name: data-snapshot
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
resources:
requests:
storage: 64Mi

สิ่งที่รันจริง: CRD client ของ external-snapshotter (VolumeSnapshotClass/VolumeSnapshotContent/VolumeSnapshot, v8.6.0 — เวอร์ชันเดียวกับที่ harness คอร์สติดตั้งไว้บน verify-k8s) กับ Deployment snapshot-controller ติดตั้งสำเร็จบน cluster storage-net ของโมดูลนี้เอง (kubectl apply -k external-snapshotter/client/config/crd กับ .../deploy/kubernetes/snapshot-controller ทั้งคู่จาก release ต้นทาง kubernetes-csi/external-snapshotter v8.6.0) ส่วนการติดตั้ง CSI hostpath driver เอง (kubernetes-csi/csi-driver-host-path v1.18.0) — ตัวที่ implement snapshot จริง ๆ — คือจุดที่มีปัญหา: เครื่องที่ใช้ร่วมกันซึ่งรัน kind cluster ของหลาย agent พร้อมกันในคอร์สนี้ ตกอยู่ในสภาพ CPU ขาดแคลนอย่างหนักนานพอสมควรในเซสชันนี้ (uptime แสดง load average ค้างที่ 20–30, docker stats แสดง CPU 50–95% ต่อ container ของแต่ละ node ยืนยันแล้วว่าเป็นปัญหาระดับเครื่องทั้งเครื่องเพราะ cluster verify-k8s ที่ใช้ร่วมกันแยกต่างหาก ก็ fail กับ kubectl get ns แบบพื้นฐานเดียวกันในช่วงเวลาเดียวกัน) driver เลยไม่เคยขึ้นมา running ก่อนที่หน้าต่างเวลาจะปิดลง จนพิสูจน์ snapshot-and-restore จริงแบบ end to end ไม่ได้ รูปแบบ object ด้านบนตรวจแล้วว่าถูกต้องด้วย kubectl apply --dry-run=server (ตรวจ OpenAPI + admission เต็มรูปแบบ) กับ verify-k8s ส่วนพฤติกรรม end-to-end เรื่องการรักษาข้อมูลอ้างอิงตาม เอกสาร Kubernetes CSI volume snapshots ไม่ได้พิสูจน์ซ้ำเองในบทนี้

ตอนรีวิว manifest ของ PV/PVC/StorageClass ที่ AI สร้าง ให้เช็ค

  • PVC ที่ไม่มี storageClassName บน cluster ที่ไม่มี default StorageClass — จะค้าง Pending ตลอดไป มี event จริงที่พลาดดูง่ายมาก (no persistent volumes available for this claim and no storage class is set) ไม่มี error โผล่ใน kubectl get เลย
  • PVC ที่ Pending แล้วเข้าใจว่าพัง ทั้งที่ StorageClass เป็น WaitForFirstConsumer — พิสูจน์ด้านบนแล้วว่านี่คือพฤติกรรมที่คาดไว้จนกว่าจะมี Pod
  • allowVolumeExpansion ไม่ได้ตั้ง (default เป็น false) บน StorageClass ที่ผู้เขียนคาดว่าจะ resize ได้ทีหลัง
  • comment ที่บอกว่า reclaimPolicy: Delete “ปลอดภัย” เพราะ “Kubernetes จะเคลียร์ให้เอง” — พิสูจน์ด้านบนด้วยการเทียบกับ Retain แล้วว่า Delete แปลว่า disk เบื้องหลังหายไปทันทีที่ PVC หายไป ซึ่งตรงข้ามกับ “ปลอดภัย” เลยสำหรับอะไรก็ตามที่ผู้เขียนตั้งใจจะเก็บไว้

🐛 หา bug ในโค้ดนี้

k8s/persistent-volumes-claims-and-storage-classes/db-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: db-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
---
apiVersion: v1
kind: Pod
metadata:
name: db
spec:
containers:
- name: postgres
image: postgres:18
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumes:
- name: data
persistentVolumeClaim:
claimName: db-data
เฉลย

PVC นี้ไม่ได้ตั้ง storageClassName เลย บน cluster ที่มี default StorageClass ก็เป็นทางลัดที่ใช้ได้ปกติ — แต่บน cluster ที่ไม่มี default (พิสูจน์ด้านบนแล้วว่า class standard ของ kind มีเพราะ kind ติดตั้งให้เอง cluster ที่สร้างเองหรือตัดทอนมาอาจไม่มีเลย) PVC นี้ไม่มีทาง provision แบบ dynamic ได้ และไม่มี PV แบบ static ตัวไหนตรงกันพอดี เลยค้าง Pending ตลอดไป Pod ที่ reference มันก็ Pending ตามไปด้วย (FailedScheduling, unbound PVC) โดยไม่มีอะไรใน status ของสองตัวนี้ชี้ตรง ๆ ว่า “ไม่มี default StorageClass” คือ root cause — ต้องรู้เองว่าต้องไปรัน kubectl get storageclass แล้วเช็คหา (default) ต่อท้ายชื่อ class ทางแก้คือ ตั้ง storageClassName เองตรง ๆ หรือไม่ก็ยืนยันก่อนว่า cluster ปลายทางมี default StorageClass จริงก่อนจะปล่อย manifest ที่ไม่ได้ตั้งค่านี้ออกไป

PV/PVC/StorageClass คือสัญญาที่แยกจากกันเป็นชิ้น ๆ ไม่ใช่ object เดียวที่มีสามชื่อ StorageClass สัญญาว่าจะให้ storage หนึ่งประเภทพร้อมวิธี provision PVC สัญญาว่า “ขอ storage เท่านี้ access mode แบบนี้ bind ในจังหวะที่ถูกต้อง” และ PV คือผลลัพธ์รูปธรรมที่ได้ volumeBindingMode, allowVolumeExpansion, reclaimPolicy ล้วนเป็นปุ่มปรับว่า เมื่อไหร่ และ เข้มงวดแค่ไหน สัญญานั้นถูกทำตาม — ไม่ได้เปลี่ยนว่า PV หรือ PVC คืออะไรโดยพื้นฐาน

ทางเลือกข้อดีต้นทุน
volumeBindingMode: WaitForFirstConsumerprovisioner เลือก storage ที่ตรงกับที่ Pod ไปลงจริงได้ (พิสูจน์ด้านบน)PVC ดูเหมือนค้าง Pending จนกว่าจะมี Pod — คนที่ไม่รู้จะคิดว่าพัง
volumeBindingMode: ImmediatePVC bind ทันที ไม่ต้องรอ Podอาจ bind กับ PV/zone ที่ constraint การ schedule ของ Pod ที่จะมาทีหลังไปไม่ถึง
reclaimPolicy: Deleteไม่มี storage เหลือค้างหลัง cleanupลบ PVC พลาดครั้งเดียว disk เบื้องหลังหายตาม พิสูจน์ด้านบนด้วยการเทียบกับ Retain
reclaimPolicy: Retainกู้ข้อมูลคืนได้หลัง PVC ถูกลบต้อง cleanup มือโดย admin หรือ storage จะค้างสะสมเงียบ ๆ
restore จาก VolumeSnapshotกู้คืนแบบ point-in-time โดยไม่ต้องมี backup tool แยกขึ้นอยู่กับว่า CSI driver รองรับ snapshot จริงหรือเปล่าทั้งหมด ไม่มีอะไรใน API ห้ามไม่ให้ประกาศ snapshot กับ driver ที่ไม่รองรับ
  • “PVC ที่ Pending แปลว่าพังเสมอ” พิสูจน์ด้านบนแล้ว — บน StorageClass แบบ WaitForFirstConsumer การ Pending ตอนยังไม่มี Pod คือสถานะที่ถูกต้องตามที่ออกแบบไว้
  • “allowVolumeExpansion เปิดเป็น default” พิสูจน์ด้านบนแล้วว่า class standard ของ kind เองมากับค่า false ต้องตั้งเปิดเองชัด ๆ
  • “ลบ PVC แล้วข้อมูลเบื้องหลังหายเสมอ” จริงแค่กรณี reclaimPolicy: Delete เท่านั้น — Retain (เทียบด้านบน) เก็บ PV กับ disk ไว้ตั้งใจ
  • “มี object VolumeSnapshot แปลว่า snapshot ถูกสร้างจริงแล้ว” API ยอมรับ VolumeSnapshot ที่ reference StorageClass/driver คู่ไหนก็ได้ ส่วนจะมีอะไรเกิดขึ้นจริงหรือเปล่าขึ้นอยู่กับว่ามี CSI driver ที่รองรับ snapshot ติดตั้งอยู่จริงไหม ทั้งหมด ซึ่ง object เองบอกไม่ได้เลย
flowchart LR
  pod["Pod"] -->|mounts| pvc["PVC: dynamic-claim"]
  pvc -->|references| sc["StorageClass: fast-ssd"]
  sc -->|CSI driver provisions| pv["New PersistentVolume"]
  pv --> disk[("Underlying disk, e.g. an EBS volume")]
Dynamic provisioning: PVC สั่งให้ StorageClass กับ CSI driver สร้าง PV ใหม่
ตอน Pod ต้องการ persistent storage ต้อง reference อะไรใน spec.volumes
StorageClass ช่วยให้คุณไม่ต้องทำอะไร
ReadWriteOncePod จำกัดอะไรที่ ReadWriteOnce ธรรมดาไม่จำกัด
ความต่างเชิงปฏิบัติระหว่าง reclaim policy แบบ Delete กับ Retain คืออะไร
PVC ตัวนี้เพิ่งถูกสร้างเข้ากับ StorageClass ที่มี volumeBindingMode: WaitForFirstConsumer และยังไม่มี Pod reference มันเลย output จริงนี้แปลว่าอะไร
$ kubectl get pvc wffc-pvc
NAME       STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
wffc-pvc   Pending                                      wffc-demo      0s

$ kubectl describe pvc wffc-pvc | tail -3
  Normal  WaitForFirstConsumer  8s   persistentvolume-controller  waiting for first consumer to be created before binding
นี่คือ output จริงของ StorageClass default ของ kind เอง คอลัมน์ ALLOWVOLUMEEXPANSION บอกอะไรเกี่ยวกับ default
$ kubectl get storageclass -o wide
NAME                 PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
standard (default)   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  22m
มีการสร้าง object VolumeSnapshot สำเร็จ (kubectl apply ผ่าน) ที่ reference VolumeSnapshotClass ไว้ แต่ไม่มี CSI driver ที่ implement hostpath.csi.k8s.io รันอยู่ใน cluster จริง ๆ จะเกิดอะไรขึ้น