[元件][儲存] PV & PVC === ###### tags: `K8s / 元件` ###### tags: `Kubernetes`, `k8s`, `PersistentVolume`, `pv`, `PersistentVolumeClaim`, `pvc` <br> [TOC] <br> ## 術語 - Claim: 一張請求 - PVC: 是使用者的請求 - PV: 是系統或管理者提供的資源 - 靜態的 StorageClass: 是指 `sc.provisioner: kubernetes.io/no-provisioner` <br> ## 錯誤原因 - 翻譯對照 - no persistent volumes available for this claim and no storage class is set - 找不到可用來滿足此要求的 Persistent Volume,且未設定 StorageClass。 - Error from server (BadRequest): error when creating "xxx.yaml": PersistentVolume in version "v1" cannot be handled as a PersistentVolume: strict decoding error: unknown field "spec.persistantVolumeReclaimPolicy - 從伺服器返回的錯誤(BadRequest): - 在建立 `xxx.yaml` 時發生錯誤: - API 版本為 `"v1"` 的 PersistentVolume 無法被處理: - 嚴格解碼錯誤:未知欄位 `"spec.persistantVolumeReclaimPolicy"` - diff ``` persistantVolumeReclaimPolicy persistentVolumeReclaimPolicy -------^--------------------- ``` <br> ## spec ### 📦 SC - StorageClass.spec 欄位整理 | 欄位 | 必填 | 預設值 | Enum | 常用性 | 說明 | |------|------|--------|------|--------|------| | `provisioner` | ✅ | 無 | 無限制(取決於 CSI 插件) | ⭐ 常用 | 動態卷提供者,如:<br>`kubernetes.io/no-provisioner`, `nfs.csi.k8s.io`, `driver.longhorn.io` | | `parameters` | ❌ | 無 | 無 | ⭐ 常用 | 提供給 provisioner 的設定參數,例如 NFS server、path 等 | | `reclaimPolicy` | ❌ | `Delete` | `Retain`, `Delete`, `Recycle`(Recycle 已淘汰) | ⭐ 常用 | Volume 被 release 後要如何處理資料 | | `volumeBindingMode` | ❌ | `Immediate` | `Immediate`, `WaitForFirstConsumer` | ⭐ 常用 | PVC 什麼時候綁定 PV(建議使用 `WaitForFirstConsumer`) | | `allowVolumeExpansion` | ❌ | `false` | `true`, `false` | 🔸 偶用 | 是否允許 PVC 擴充儲存空間 | | `mountOptions` | ❌ | 無 | 無固定 | 🔸 偶用 | 掛載時的附加參數,如:`["vers=4.1"]` | --- ### 📦 PV - PersistentVolume.spec 欄位整理 | 欄位 | 必填 | 預設值 | Enum | 常用性 | 說明 | |------|------|--------|------|--------|------| | `capacity.storage` | ✅ | 無 | - | ⭐ 常用 | 容量,如 `"5Gi"`、`"500Mi"` | | `accessModes` | ✅ | 無 | `ReadWriteOnce`, `ReadOnlyMany`, `ReadWriteMany`, `ReadWriteOncePod` (v1.22+) | ⭐ 常用 | Volume 支援的存取模式 | | `storageClassName` | ❌ | 無 | 無限制 | ⭐ 常用 | 對應的 StorageClass 名稱,與 PVC 對接 | | `persistentVolumeReclaimPolicy` | ❌ | `Retain` | `Retain`, `Delete`, `Recycle` | ⭐ 常用 | PV 被 release 後要如何處理資料 | | `volumeMode` | ❌ | `Filesystem` | `Filesystem`, `Block` | ⭐ 常用 | 掛載成目錄或裸區塊 | | `hostPath.path` | ✅ (for `hostPath`) | 無 | 無 | ⭐ 常用(開發用) | 指定 host 上的路徑,僅適用於單節點或測試環境 | | `local.path` | ✅ (for `local`) | 無 | 無 | ⭐ 常用 | Local Volume 的實體掛載路徑(需搭配 nodeAffinity) | | `nodeAffinity` | ✅ (for `local`) | 無 | 無 | ⭐ 常用 | 限制 PV 綁定在哪個 node(對應 local.path) | | `csi.driver` | ✅ (for CSI) | 無 | 無 | 🔸 特殊用途 | 指定使用哪個 CSI 插件 | | `nfs.server`, `nfs.path` | ✅ (for NFS) | 無 | 無 | 🔸 特殊用途 | NFS 存取參數,若使用 `nfs` provisioner | --- ### 📦 PVC - PersistentVolumeClaim.spec 欄位整理 | 欄位 | 必填 | 預設值 | Enum | 常用性 | 說明 | |------|------|--------|------|--------|------| | `accessModes` | ✅ | 無 | `ReadWriteOnce`, `ReadOnlyMany`, `ReadWriteMany`, `ReadWriteOncePod` | ⭐ 常用 | 要求使用的存取方式,需與 PV 匹配 | | `resources.requests.storage` | ✅ | 無 | - | ⭐ 常用 | 請求容量,如 `5Gi` | | `storageClassName` | ❌ | default StorageClass | 任意已定義的名稱 | ⭐ 常用 | 若省略則綁定 default StorageClass | | `volumeMode` | ❌ | `Filesystem` | `Filesystem`, `Block` | ⭐ 常用 | 申請哪種掛載方式 | | `volumeName` | ❌ | 無 | 任意已存在的 PV 名稱 | 🔸 偶用 | 明確指定要綁定的 PV | | `selector.matchLabels` | ❌ | 無 | 無 | 🔸 偶用 | 用來篩選對應的 PV | | `dataSource.kind` | ❌ | 無 | `PersistentVolumeClaim`, `VolumeSnapshot`, `Populator` | 🔸 特殊用途 | 從現有 PVC / 快照 clone(需 CSI 支援) | | `dataSource.name` | ✅(若使用 dataSource) | 無 | 無 | 🔸 特殊用途 | 要 clone 的來源名稱 | | `dataSource.apiGroup` | ❌ | 視 driver 而定 | 無 | 🔸 特殊用途 | 例如:`snapshot.storage.k8s.io` | --- ### 🧠 延伸說明:accessModes 各模式適用場景 | 模式 | 說明 | 適用場景 | |------|------|-----------| | `ReadWriteOnce` | 單節點可讀寫 | 常見情境,local hostPath, EBS | | `ReadOnlyMany` | 多節點唯讀 | 少見(如多 Pod 同時讀取) | | `ReadWriteMany` | 多節點可讀寫 | NFS、Ceph、GlusterFS | | `ReadWriteOncePod` | 單 Pod 限定綁定 | v1.22+ 特性,提升安全性 | --- ### (PVC 綁定到 PV) 比對邏輯順序(簡化解釋) Kubernetes 在尋找可以與 PVC 綁定的 PV 時,大致會: 1. 找出 `status.phase == Available` 的 PV 2. 過濾出與 PVC 相同 `storageClassName` 的 PV 3. 過濾掉容量不足的 PV(PV 必須 ≥ PVC 請求) 4. 比對 accessModes,PVC 的模式必須是 PV 的子集 5. 比對 volumeMode(通常都是 `Filesystem`) 6. 若 PVC 有 `volumeName`,**只考慮指定的 PV** 7. 若 PVC 有 `selector`,比對 PV 的 labels 8. 綁定第一個符合條件的 PV(非排序式匹配) <br> <hr> <br> ## 常見最小配置 ### SC (StorageClass) 最小配置 ```yaml= apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: standard provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer ``` ### PV (PersistentVolume) 最小配置 ```yaml= ... spec: capacity: storage: 5Gi accessModes: - ReadWriteOnce local: path: /mnt/data/my_local_storage storageClassName: standard ``` ### PVC (PersistentVolumeClaim) 最小配置 ```yaml= ... spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi storageClassName: standard ``` ### `pv.spec.nodeAffinity` 設定範例 ```yaml= spec: nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - worker-node-1 ``` <br> <hr> <br> ## 範例:將 storage 綁定到特定 node (`kubernetes.io/hostname=esc4000`) > 使用「**local PV + nodeAffinity**」的做法,讓 PVC **只能綁到** `esc4000` 這台 node 上的 **`/models`**。 > by gpt-5.2 (2026/01/15) ### 需求 - **labels** ``` kubernetes.io/hostname=esc4000 nvidia.com/gpu.machine=ESC4000 ``` - **PV 用 `local.path: /models`** - **PV 用 `nodeAffinity` 鎖死 `kubernetes.io/hostname = esc4000`** - **綁定 PV 到單一 node:用 `hostname`(必要)** - GPU machine label 只是「可用條件」,但**不適合拿來鎖單一 node**(可能存在多台同型號)。 - StorageClass 用 `kubernetes.io/no-provisioner`(靜態 local PV) - 建議 `volumeBindingMode: WaitForFirstConsumer`,讓排程更穩 --- ### (1)(選用)確認 hostname label K8s 預設就會有 `kubernetes.io/hostname`: ```bash kubectl get node esc4000 --show-labels | tr ',' '\n' | grep hostname ``` 你會看到類似: `kubernetes.io/hostname=esc4000` --- ### (2)(選用)建 StorageClass(local 靜態 PV) > `WaitForFirstConsumer` 建議保留:Pod 用到 PVC 時才真正做 binding / 排程決策。 ```yaml= # sc-local-models.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-models provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer reclaimPolicy: Retain ``` 套用: ```bash kubectl apply -f sc-local-models.yaml ``` :::info :bulb: **如果不建立 StorageClass 物件,PV & PVC storageClassName 設定如下**: ```yaml # 可以允許 local-models 物件不存在 spec: storageClassName: local-models ``` 或是: ```yaml # - 不使用 StorageClass # - 若 PVC 用 storageClassName: "",那 PV 也必須是 storageClassName: "" 才能配對 spec: storageClassName: "" ``` - **PV/PVC 的 `storageClassName` 必須一致**(字串相同就會配對),而且 **K8s 不會強制那個 StorageClass 一定要存在**。 - 若要用 `WaitForFirstConsumer`,就**應該建立 StorageClass 物件**。 - 若不建立 StorageClass,就要明講:你放棄 `WaitForFirstConsumer` 這個優點,且要理解可能的排程差異。 ::: --- ### (3) 建 PV(綁定到 node + /models) 容量你可以自行調整,這邊範例使用 `4Ti`。 ```yaml= # pv-models-esc4000.yaml apiVersion: v1 kind: PersistentVolume metadata: name: pv-models-esc4000 labels: purpose: models node: esc4000 spec: storageClassName: local-models volumeMode: Filesystem capacity: storage: 4Ti accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain local: path: /models nodeAffinity: required: nodeSelectorTerms: - matchExpressions: # Lock to this exact node - key: kubernetes.io/hostname operator: In values: - esc4000 ``` 套用: ```bash kubectl apply -f pv-models-esc4000.yaml ``` - 為什麼不用 label `nvidia.com/gpu.machine=ESC4000`? - 因為那個 label 可能會同時命中多台相同機型;你要「綁死某一台」,`hostname` 最準。 - `spec.capacity.storage` **不會真的對 /models 做 quota**(local volume 不是 LVM quota),它只是給 scheduler / binder 參考。實際可用空間仍取決於 node 的檔案系統剩餘空間。 --- ### (4) 建 PVC(指定 storageClass + 選到那顆 PV) 用 selector 鎖 PV label,避免誤綁到別的 PV: ```yaml= # pvc-models-esc4000.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-models-esc4000 namespace: default spec: storageClassName: local-models accessModes: - ReadWriteOnce resources: requests: storage: 4Ti selector: matchLabels: purpose: models node: esc4000 ``` 套用: ```bash kubectl apply -f pvc-models-esc4000.yaml ``` --- ### (5) Pod / Deployment 使用範例(掛載到容器內) ```yaml= apiVersion: v1 kind: Pod metadata: name: test-models-mount spec: # Optional: make it explicit nodeSelector: kubernetes.io/hostname: esc4000 containers: - name: app image: ubuntu:24.04 command: ["bash", "-lc", "ls -al /mnt/models && sleep 3600"] volumeMounts: - name: models mountPath: /mnt/models volumes: - name: models persistentVolumeClaim: claimName: pvc-models-esc4000 ``` - **不一定需要 nodeSelector** - 只要 StorageClass 是 `WaitForFirstConsumer`,PVC 會驅動排程把 Pod 放到符合 PV nodeAffinity 的那台 node。 - 若你想更直觀也可以加上 nodeSelector。 - **如果你沒有 WaitForFirstConsumer**(例如 StorageClass 沒建),PVC 可能先被綁定,然後 Pod 排程再遇到限制,會更容易出現 Pending / 綁定不如預期。 --- ### 常見坑提醒(很重要) 1. **`/models` 目錄必須在該 node 真實存在** 2. **權限**:容器內 user 可能沒權限讀寫 `/models` 的檔案。必要時: * 讓容器用 root 跑(快速驗證) * 或用 `securityContext.fsGroup` / 調整 host 端檔案權限 3. **ReadWriteOnce**:多個 Pod 只要都在同一台 node,通常仍可共用(但要注意你的應用是否同時寫入造成競態)。 <br> <hr> <br> ## 討論 - StorageClass ### 純 K8s 是否可以建立 storageClassName: standard? - ### gpt-4o (2025/05/31) 是的,**在純 Kubernetes 環境中**,您**可以自行建立名為 `standard` 的 `StorageClass`**,只要您提供一個 **對應的 Volume Provisioner**。 但要注意的是: > 🚫 Kubernetes 不會自動幫你建立 `standard` 或任何 `StorageClass` > ✅ 您必須**自己建立**這個 `StorageClass`,並且確保有一個 **Provisioner** 來處理 PVC 配置 --- ### ✅ 方法一:建立一個簡易的 hostPath StorageClass(單節點開發用) #### 適合情境: - 開發或測試用 - 節點固定,不考慮動態多節點共享 #### 建立 StorageClass: ```yaml= # storageclass-hostpath.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: standard provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer ``` > 這個 `standard` 不會自動產生 PV,而是用於搭配 **手動靜態建立的 PV** 套用: ``` kubectl apply -f storageclass-hostpath.yaml ``` 然後您需要建立對應的 `PersistentVolume`(hostPath)來手動對應。 --- ### ✅ 方法二:安裝動態 StorageClass(推薦:NFS provisioner) 若您希望 PVC 能自動綁定 Volume(像 Minikube 那樣),則需安裝動態 provisioner,例如: - [nfs-subdir-external-provisioner](https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner) - [openebs-hostpath](https://openebs.io/) - [longhorn](https://longhorn.io/)(支援高可用) 這類 Provisioner 安裝後會幫您建立類似: ```yaml provisioner: nfs.csi.k8s.io ``` --- ### 🧩 實務上常見做法 | 環境 | 建議 StorageClass 名稱 | Provisioner 說明 | |--------------|------------------------|---------------------------------------| | Minikube | `standard` | `k8s.io/minikube-hostpath`(預設) | | Dev/單節點 | `manual` / `standard` | `kubernetes.io/no-provisioner` + PV | | NFS 共用環境 | `nfs-client` | `nfs-subdir-external-provisioner` | | 高可用需求 | `longhorn` | `driver.longhorn.io` | --- ### ✅ 總結 | 問題 | 是否可以在純 K8s 建立 `standard` StorageClass? | |------|------------------------------------------------| | 答案 | ✅ 可以,只要您定義它,並指定一個合法 provisioner | <br> <hr> <br> ### 如果不建立 StorageClass,會有問題嗎?PV/PVC 兩邊用相同字串就好? - ### gpt-5.2 (2026/01/15) 可以**不建立 StorageClass**,但要注意兩個點: 1. **PV/PVC 的 `storageClassName` 必須一致**(字串相同就會配對),而且**K8s 不會強制那個 StorageClass 一定要存在**; 2. **PVC 如果不寫 `storageClassName`**,在大多數叢集(有 default StorageClass 時)會被**自動補成 default SC**,結果就會去走動態供應/或配不到你這顆 local PV。 --- ### 你問的「PV/PVC 兩邊用相同字串就好?」— 可以 **可以**,即使你沒有建立那個 StorageClass 物件,只要: * PV:`storageClassName: local-models` * PVC:`storageClassName: local-models` 兩邊一致,PVC 就能跟該 PV 綁起來(如果還有 `selector` + `nodeAffinity` 更穩)。 --- ### 但我更常用的「不建 StorageClass」寫法:兩邊都用空字串 這個做法的目標是:**完全避免 default StorageClass 介入**。 #### PV(不填或填空都行) ```yaml spec: storageClassName: "" ``` #### PVC(一定要明確填空字串,避免被補 default) ```yaml spec: storageClassName: "" ``` > 如果 PVC 省略 `storageClassName`(不寫),很容易被套上 default SC,然後就不會去綁你的 local PV 了。 --- ### 不建 StorageClass 的代價是什麼? **你就用不到 `volumeBindingMode: WaitForFirstConsumer`**(那是 StorageClass 的屬性)。 * 有 SC + WaitForFirstConsumer:Pod 要用 PVC 時才做最後綁定/排程,對「有多顆 local PV、或 pod 有更多限制」的情境更安全。 * 沒 SC:PVC 可能會「先綁死」某顆 PV,再發現 Pod 因其他限制排不上那台 node(若 PV 已經用 hostname 鎖死單一 node,通常問題不大)。 --- ### 結論(照你的需求最簡單) * **可以不建 StorageClass。** * 要嘛 **PV/PVC 都寫同一個字串**(例如 `local-models`),要嘛 **兩邊都寫 `storageClassName: ""`**。 * 我會建議你用 **`storageClassName: ""`** 這套,最不怕 default StorageClass 搞事。 如果你貼一下 `kubectl get storageclass` 的輸出,我也可以直接告訴你:你目前叢集是不是有 default SC、以及你現在這個 PVC 省略 `storageClassName` 會不會被自動套上 default。 <br> <hr> <br> ## 討論 ### 單節點 Kubernetes 環境(control-plane & worker 共用)是否需要設定 nodeAffinity? - ### gpt-4o (2025/05/31) ### 🎯 結論先講: > ✅ **如果您是單節點 K8s 環境(control-plane 與 worker 同一台)**,那麼: > > 🔹 **`nodeAffinity` 是「非必要」的,可以省略。** > 🔹 但如果您 **指定了 `local.path`(hostPath)**,就必須確保該目錄存在於這唯一的節點上。 --- ### 📌 為什麼可以省略 `nodeAffinity`? - 當 Kubernetes 找到 PVC → PV 綁定時, - 若該 PV 是 `local.path` 類型(即 `hostPath`),K8s 只要找得到可掛載的 PV 即會使用; - 若整個 cluster 只有一個 node,則根本無選擇問題,不需 affinity 篩選。 ### 🧩 延伸補充:什麼時候建議寫 `nodeAffinity`? | 使用情境 | 是否建議寫 nodeAffinity? | |----------|-----------------------------| | 多節點環境(worker-1 / worker-2) | ✅ 建議(避免 PVC 被排到不對的 node) | | local volume 特定硬碟 mount point | ✅ 建議(確保 PV 是在正確 node) | | 單節點開發 / 測試 | ❌ 可省略 | <br> <hr> <br> ### 單節點:使用 local volume 建立 Hello World 資料卷,不含 nodeAffinity > 無動態 StorageClass 環境的配置 - ### gpt-4o (2025/05/31) 在單節點 Kubernetes 環境下(control-plane 與 worker 同台),我們可以建立一個不含 nodeAffinity 的 `PersistentVolume`(PV),並配合 `PersistentVolumeClaim`(PVC)來掛載 `hostPath` 本地資料夾。本範例以 `hello-pv` / `hello-pvc` 為例: --- #### 步驟 1:建立對應目錄於主機上 請先登入該單一節點,並建立實體資料夾: ```bash ssh <your-node-name> sudo mkdir -p /mnt/data/hello sudo chmod 777 /mnt/data/hello ``` --- #### 步驟 2:建立 StorageClass(若尚未存在) ```yaml= apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: standard provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer ``` 套用: ```bash kubectl apply -f storageclass.yaml ``` --- #### 步驟 3:建立 PersistentVolume(PV) ```yaml= apiVersion: v1 kind: PersistentVolume metadata: name: hello-pv spec: capacity: storage: 2Gi volumeMode: Filesystem accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain storageClassName: standard local: path: /mnt/data/hello ``` --- #### 步驟 4:建立 PersistentVolumeClaim(PVC) ```yaml= apiVersion: v1 kind: PersistentVolumeClaim metadata: name: hello-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 2Gi storageClassName: standard ``` --- #### 步驟 5:建立測試 Pod 並掛載 PVC ```yaml= apiVersion: v1 kind: Pod metadata: name: hello-pod spec: containers: - name: hello image: busybox command: [ "sleep", "3600" ] volumeMounts: - mountPath: /mnt/hello name: hello-volume volumes: - name: hello-volume persistentVolumeClaim: claimName: hello-pvc ``` --- #### 檢查結果 ```bash kubectl get pvc kubectl get pv kubectl get pod hello-pod ``` 確認 PVC 狀態為 `Bound`,Pod `Running` 即成功掛載。 --- #### 適用情境與注意事項 | 情境 | 是否需要 nodeAffinity | |--------------|------------------------| | 單節點開發環境 | ❌ 可省略 | | 多節點部署 | ✅ 建議設定 | | 需要特定節點存取本地磁碟 | ✅ 建議設定 | 此範例適用於無動態 StorageClass 環境,可協助您使用本地磁碟進行簡單測試與開發。 <br> <hr> <br> ### 為什麼 PersistentVolume (PV) 和 PersistentVolumeClaim (PVC) 都要指定類似的屬性? - ### gpt-4o (2025/05/31) ### ❓是否只寫 PVC 就能用? > ❌ 不行,只寫 PVC 是不夠的,因為: - Kubernetes 要將 PVC 綁定到 PV,需要一個「匹配邏輯」來確認該 Volume 能滿足需求 - 所以: - ✏️ PVC 是使用者的**請求** - 📦 PV 是系統或管理者提供的**資源** --- ### ✅ 結論先講: | 問題 | 答案 | |------|------| | PVC 和 PV 是否要同時存在? | ✅ 是的,**兩個都要有**(除非使用動態 provisioner) | | 兩者 `accessModes`、`storageClassName` 是否都要寫? | ✅ 是的,系統透過這些欄位**匹配** PVC ↔ PV | | 屬性重複合理嗎? | ✅ 合理,因為 PVC 和 PV 是兩方提出要求 & 資源,要能彼此符合 | --- ### 📦 具體範例說明 #### 🧾 1. PersistentVolume(靜態提供的資源) ```yaml= apiVersion: v1 kind: PersistentVolume metadata: name: example-pv spec: capacity: storage: 5Gi accessModes: - ReadWriteOnce # ❗供應者說:我只支援 RWO 模式 storageClassName: standard local: path: /mnt/data/example ``` 📌 這個 PV 的意思是: >「我有 5Gi 空間,在某節點的 `/mnt/data/example`,支援 `ReadWriteOnce`,屬於 `standard` 類別。」 --- #### 🧾 2. PersistentVolumeClaim(使用者的需求) ```yaml= apiVersion: v1 kind: PersistentVolumeClaim metadata: name: example-pvc namespace: demo spec: accessModes: - ReadWriteOnce # ❗使用者說:我只需要能單點寫入 resources: requests: storage: 3Gi # ❗我需要至少 3Gi 空間 storageClassName: standard ``` 📌 這個 PVC 的意思是: >「我需要一個 3Gi 空間的 Volume,要能 `ReadWriteOnce`,使用 `standard` 類別。」 --- ### ✅ 綁定邏輯(系統會怎麼比對) Kubernetes 在 PVC 建立後,會找出「能符合這些條件的 PV」: - `accessModes` 必須包含所需的模式(PV: `RWO`,PVC: `RWO` ✅) - `storageClassName` 要一致(都是 `standard` ✅) - PV 的容量要 >= PVC 的請求(5Gi >= 3Gi ✅) → 成功綁定!`example-pvc` 會被綁定到 `example-pv`。 --- ### 🧠 類比:飯店訂房系統比喻 | 對象 | 類比說明 | |------|---------| | PersistentVolume (PV) | 飯店提供的房型,例如「雙人房 / 10坪 / 有窗」 | | PersistentVolumeClaim (PVC) | 客人的需求:「我要雙人房、至少 8 坪、有窗」 | | 比對邏輯 | 只有飯店房型(PV)滿足條件,訂單(PVC)才會成功成立 | --- ### ✅ 補充:什麼情況只寫 PVC 就夠? 當您使用的是**支援動態配置的 StorageClass(有 provisioner)**,例如: ```yaml storageClassName: nfs-client ``` 這時 PVC 建立後,Kubernetes 會觸發 provisioner 自動建立一個 PV 並綁定回來。所以您只需寫 PVC 即可。 --- ### 🔚 小結 | 問題 | 答案 | |------|------| | 為什麼屬性會重複? | 因為 Kubernetes 要做比對,需求和供應雙方都必須列出 | | 如果沒有動態 provisioner,是否一定要手動寫 PV? | ✅ 是的 | | `accessModes`、`storageClassName` 是否一定要兩邊都寫? | ✅ 是,否則會匹配失敗或綁定不了 | <br> <hr> <br> ### PV 與 PVC 的 `accessModes` 不一致時,是否可綁定? - ### gpt-4o (2025/05/31) 這是一個非常細緻、但**實務上重要**的問題。 ### 🔍 問題重述: 若: ``` PV.spec.accessModes: [ReadWriteMany] PVC.spec.accessModes: [ReadWriteOnce] ``` 是否能配對? --- ### ✅ 正確答案: > **可以配對,綁定會成功。** --- ### 🎯 原因說明: Kubernetes 在進行 PVC ↔ PV 配對時,檢查的條件是: > ✅ **PV 的 `accessModes` 必須包含 PVC 所要求的模式。** #### ✔️ 換句話說: | PV 支援的模式 | PVC 要求的模式 | 是否可以配對? | 原因 | |---------------|----------------|----------------|------| | RWO, ROX, RWX | RWO | ✅ 可以 | PV 有支援 RWO | | RWX | RWO | ✅ 可以 | RWX 含蓋 RWO | | ROX | RWO | ❌ 不行 | 沒有 RWO | | RWO | RWX | ❌ 不行 | PV 不支援 RWX | - X: Many - O: Once --- ### 📌 所以您的例子: ``` PV.accessModes: [ReadWriteMany] PVC.accessModes: [ReadWriteOnce] ``` - ✅ **成功**(因為 RWX 能涵蓋 RWO) - Pod 會以 **單寫入** 的方式掛載該 PV,即使它本身支援多寫入。 --- ### 🧠 類比: 這就像: - 飯店提供一個「可容納 5 人」的大房間(RWX) - 客人只要求「一個單人房」(RWO) - 雖然空間太大,但依然可以入住 ✅ --- ### ❗ 注意事項: 雖然可以綁定成功,但請留意: - 多個 PVC 都要求 RWO,仍然不能**同時掛上同一個 RWX PV** - 除非 `accessModes` 是 `ReadOnlyMany` 或 Pod 限制了 Volume 的掛載行為 --- ### ✅ 範例驗證 YAML #### PV.yaml ```yaml= apiVersion: v1 kind: PersistentVolume metadata: name: test-pv spec: capacity: storage: 1Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain storageClassName: test hostPath: path: "/mnt/data/test" ``` #### PVC.yaml ```yaml= apiVersion: v1 kind: PersistentVolumeClaim metadata: name: test-pvc spec: accessModes: - ReadWriteOnce resources: requests: storage: 500Mi storageClassName: test ``` 結果: ``` $ kubectl get pvc test-pvc NAME STATUS VOLUME ... test-pvc Bound test-pv ``` ✅ 綁定成功! <br> <hr> <br> ### PV 與 PVC 的 `storageClassName` 不一致時,是否可綁定? - ### gpt-4o (2025/05/31) ### ❓ 如果 `PV.spec.storageClassName` 和 `PVC.spec.storageClassName` 不一樣,是否仍可綁定? > ❌ **不能綁定,一定會失敗。** --- ### ✅ 結論: > **Kubernetes 在綁定 PVC → PV 時,`storageClassName` 必須完全相符**(字串比對,大小寫敏感)。 --- ### 📌 對照表:是否能綁定 | PV.storageClassName | PVC.storageClassName | 可綁定? | 原因說明 | |---------------------|----------------------|----------|-----------| | `"fast"` | `"fast"` | ✅ 可以 | 相同字串 | | `"fast"` | `"standard"` | ❌ 不行 | 字串不同 | | `""`(空字串) | 未設定(null) | ✅ 可以 | 兩者皆為無設定,代表預設值 | | `"manual"` | 未設定(null) | ❌ 不行 | 一邊設定,一邊沒設定,視為不匹配 | --- ### 📎 額外補充:什麼情況可以不設 `storageClassName`? #### ✅ 若 PVC 沒有 `storageClassName`,系統會嘗試: 1. 使用**預設 StorageClass**(即 `kubectl get storageclass` 中帶 `(default)` 的那個) 2. 或綁定一個 **沒有設定 `storageClassName`** 的 `PersistentVolume`(即 legacy 手動 PV) 這是舊版 Kubernetes 的行為保留。 --- ### 🧪 測試案例(會失敗) #### PV: ``` spec: storageClassName: manual ``` #### PVC: ``` spec: storageClassName: standard ``` 結果: ``` PVC → Pending ``` 原因: ``` no persistent volumes available for this claim and no storage class is set (找不到可用來滿足此要求的 Persistent Volume,且未設定 StorageClass。) ``` --- ### ✅ 正確做法 #### 要嘛兩者一致: ``` PV: storageClassName: standard PVC: storageClassName: standard ``` #### 要嘛兩者都不設(legacy 模式,K8s 會嘗試綁定) --- ### 🔍 實務建議 | 情境 | 建議做法 | |------|----------| | 靜態配置 PV + PVC | PV 與 PVC 的 `storageClassName` 要一致,或都不寫 | | 動態配置 PV | 讓 PVC 指定 StorageClass 即可,系統會幫你建立 PV | | 多種 StorageClass 共存 | 每個 PVC 要明確指定對應的 StorageClass 名稱 | <br> <hr> <br> ### PV & PVC 比對邏輯 - ### gpt-4o (2025/05/31) ### PV 與 PVC 的匹配(Binding)邏輯一覽表 | PV 欄位 | PVC 欄位 | 是否比對 | 說明 | |----------------------|------------------------|----------|------| | `storageClassName` | `storageClassName` | ✅ 必要 | 必須完全相符(空白也要一致) | | `capacity.storage` | `resources.requests.storage` | ✅ 必要 | PV 必須大於等於 PVC 請求 | | `accessModes` | `accessModes` | ✅ 必要 | PVC 所要求的模式必須是 PV 的子集(例如:PVC 要求 RWO,PV 提供 RWO+ROX 可以匹配) | | `volumeMode` | `volumeMode` | ✅ 必要(預設為 `Filesystem`) | Filesystem 或 Block,兩者需相符 | | `nodeAffinity` | 無(由調度器處理) | ⚠️ 條件式 | 僅在使用 `local` 類型 PV 時會強制要求,需與 Pod 安排在相同節點 | | `persistentVolumeReclaimPolicy` | 無 | ❌ 不影響匹配 | 僅影響 PV 被釋放後的行為 | | `volumeName` | PVC.spec.volumeName | ✅ 條件式 | 如果 PVC 指定了 `volumeName`,**只會綁定該 PV** | | PV.labels | PVC.spec.selector.matchLabels | ✅ 條件式 | 若 PVC 有指定 selector,PV 必須匹配對應 label | | PV.annotations | 無 | ❌ 不參與匹配 | 只供人類與外部工具讀取 | | PV.status.phase | 無 | ✅ 隱性 | 只有 `Available` 的 PV 才會被考慮匹配 | --- ### 比對邏輯順序(簡化解釋) Kubernetes 在尋找可以與 PVC 綁定的 PV 時,大致會: 1. 找出 `status.phase == Available` 的 PV 2. 過濾出與 PVC 相同 `storageClassName` 的 PV 3. 過濾掉容量不足的 PV(PV 必須 ≥ PVC 請求) 4. 比對 accessModes,PVC 的模式必須是 PV 的子集 5. 比對 volumeMode(通常都是 `Filesystem`) 6. 若 PVC 有 `volumeName`,**只考慮指定的 PV** 7. 若 PVC 有 `selector`,比對 PV 的 labels 8. 綁定第一個符合條件的 PV(非排序式匹配) --- ### volumeBindingMode 差異也影響時機 | volumeBindingMode | 行為說明 | |-------------------|----------| | `Immediate` | 立即綁定,只要 PVC 發出就會立即比對與綁定 PV(**無論 Pod 是否已被排程**) | | `WaitForFirstConsumer` | 等到 Pod 真的要使用時才會綁定,並考慮 Pod 調度需求,例如 `nodeAffinity` | --- ### 常見匹配失敗情況 | 原因 | 解釋 | |------|------| | `storageClassName` 不符 | 空字串 ≠ 未設定,請保持一致或顯式設定 | | `volumeMode` 不一致 | 例如 PV 是 `Block`,PVC 是 `Filesystem` | | `accessModes` 不包含 | PVC 想要 RWX,但 PV 僅有 RWO | | 容量不足 | PVC 要求 `10Gi`,但 PV 只有 `5Gi` | | PV 被其他 PVC 綁定了 | PV 的 `status.phase` 是 `Bound`,不能再綁 | <br> <hr> <br> ### SC/PV/PVC 概念 - ### Question SC -> HD / SSD / NFS / ... ,指定 handler (provisioner) 來幫你對應 PV -> 管理者:我有 2TB ,看 user要如何使用 PVC -> 使用者:我想要 500G ,速度要快(SSD) 這樣的說法對嗎? - ### gpt-o4-mini (2025/06/12) 你的說法在概念上是正確的,下面用 Kubernetes 的術語再稍微釐清一下三者的角色與關係: 1. **StorageClass (SC)** * 定義「儲存類型」與「參數模板」,例如 `provisioner`(如 `kubernetes.io/no-provisioner`、`csi-nfs`、`csi-ssd`)以及各類參數(IOPS、快取模式、路徑、NFS server 等)。 * 就像你列的 HD/SSD/NFS,不同 SC 就對應不同底層儲存技術與特性。 2. **PersistentVolume (PV)** * 由集群管理者(或動態供應機制)實際建立好的一塊儲存資源,具有固定容量(如 2 TiB)、速度(IOPS)和透過哪個 SC 來 provision。 * PV 的 `storageClassName` 要對應一個已存在的 SC。 3. **PersistentVolumeClaim (PVC)** * 由使用者提出的「儲存需求」,指定容量(如 500 Gi)與希望的 SC(例如 `fast-ssd`),也可附加額外參數(如讀寫次數、存取模式 `ReadWriteOnce`)。 * K8s 會自動尋找或動態建立(如果 SC 支援)一個符合條件的 PV,然後把它綁定(Bind)到這個 PVC。 --- ## 示例 ```yaml= # StorageClass:定義 SSD 類型 kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: fast-ssd provisioner: csi-ssd.example.com parameters: type: io1 iopsPerGB: "10" --- # 管理者預先建立的 PV(2 TiB) kind: PersistentVolume apiVersion: v1 metadata: name: pv-data-2tb spec: storageClassName: fast-ssd capacity: storage: 2Ti accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Retain csi: driver: csi-ssd.example.com volumeHandle: vol-12345 --- # 使用者提出的 PVC(500 Gi,需要 fast-ssd) kind: PersistentVolumeClaim apiVersion: v1 metadata: name: pvc-myapp spec: storageClassName: fast-ssd accessModes: - ReadWriteOnce resources: requests: storage: 500Gi ``` * **SC**:`fast-ssd`(對應 SSD) * **PV**:`pv-data-2tb`(管理者已準備好 2 TiB SSD) * **PVC**:`pvc-myapp`(使用者要 500 Gi 且要走 `fast-ssd`) Kubernetes 會把這三者對應起來,確保使用者拿到符合預期容量與性能的儲存。 <br> <hr> <br> ### 實際上 PVC 怎麼接到對應的 SSD ? HD ? - ### gpt-o4-mini (2025/06/12) 在 Kubernetes 裡,PVC 要「對接」到底層的 SSD、HD,其實是由 StorageClass 和對應的 CSI(Container Storage Interface)或 in-tree provisioner 來完成的一整套流程,概念上可以拆成三步: 1. **StorageClass → CSI 驅動程式下指令** * 當你建立一張需要 SSD 的 PVC 時(`storageClassName: fast-ssd`),Kubernetes 的控制平面會把這個請求交給對應的 CSI driver(或舊版的 in-tree provisioner),像是 `csi-ssd.example.com`。 * 這個 CSI driver 其實是一個在集群中以 Pod 或 DaemonSet 形態運行的元件,它跟真實的儲存後端通訊(可能是雲端 API、SAN 陣列、或本地 LVM),下指令「幫我建立一個 500 Gi 的 SSD 卷」。 2. **後端儲存系統建立並回報 VolumeHandle** * 假設你是在雲端(AWS/GCP/Azure),CSI driver 會呼叫雲商 API 建立一片 EBS/PD 磁碟;若是在 on-prem,可能是跟 iSCSI SAN、Ceph RBD、或 NetApp/SolidFire 等通訊;若是本地 SSD,可能是先用 LVM 做 PV pool,再開 LV。 * 建好之後,後端會回傳一個唯一的 VolumeHandle(例如 AWS 的 `vol-0abcdef123456`,或 LVM 的 `/dev/vg0/lv500`),這個 ID 會記錄在對應的 PV 裡。 3. **Kubelet 在 Node 上掛載並暴露給 Pod** * 當 PVC 與 PV 綁定完成後,調度器把 Pod 排到某個 Node。該 Node 上的 kubelet 會呼叫 CSI driver 的 NodeStage/NodePublish 接口,實際在該節點上: 1. 透過 iSCSI、FC、NVMe-oF、或直接本地塊裝置(如 `/dev/vg0/lv500`)把卷 attach 到 Node。 2. 在節點的檔案系統(例如 `/var/lib/kubelet/plugins/.../mounts/...`)做格式化(ext4、xfs 等)與 mount。 3. 最後把這個掛載好的路徑 bind-mount 到 Pod 的容器裡,例如 `/mnt/data`。 --- ### 舉例:AWS EBS 動態建立 SSD 磁碟 1. **定義 StorageClass** ```yaml= apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: gp3-ssd provisioner: ebs.csi.aws.com parameters: type: gp3 fsType: ext4 ``` 2. **使用者提出 PVC** ```yaml= apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-data spec: storageClassName: gp3-ssd accessModes: ["ReadWriteOnce"] resources: requests: storage: 500Gi ``` 3. **背後流程** * CSI driver 呼叫 AWS API:`CreateVolume({VolumeType: "gp3", Size: 500, ...})` → 回傳 `VolumeId: "vol-0123456789abcdef0"` * Kubernetes 自動建立對應的 PV,並綁定到你的 PVC。 * Pod 啟動後,kubelet 透過 CSI 把 `vol-0123456789abcdef0` attach 到該 Node,格式化、mount,最後才注入容器。 --- ### 本地 SSD/HD(Local PV)示例 如果你想直接使用節點本機的 SSD 或 HD,也可以用 Local PersistentVolume: ```yaml= apiVersion: v1 kind: PersistentVolume metadata: name: local-ssd-pv spec: storageClassName: local-ssd capacity: storage: 1Ti accessModes: - ReadWriteOnce local: path: /mnt/local-ssd # 這個路徑必須在 Node 上事先準備好 nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: ["worker-0"] # 確保這個 PV 只綁到特定 Node persistentVolumeReclaimPolicy: Retain ``` * 這種方式不會走 CSI 動態 provision,管理員必須事先在每台 Node 上建立好目錄(或 LVM 卷)並設定好 StorageClass。 * 當 PVC 用到 `storageClassName: local-ssd` 時,Kubernetes 會把 Pod 「釘」到該 Node,直接把 `/mnt/local-ssd` 綁定到容器裡。 --- 透過上述機制,使用者只要關心「我要一個 500 Gi SSD 卷」,而 Kubernetes 與 CSI driver 會自動把請求轉給真正的後端系統,完美將 PVC 與實體或雲端的 SSD/HD 連接起來。 <br> <hr> <br> ### provisioner 能客製化自己處理嗎? - ### gpt-o4-mini (2025/06/12) 可以。Kubernetes 允許你自行開發並部署「自訂 Provisioner」,來接管 PVC 到底層儲存的動態供應邏輯。一般做法有兩種: --- ### 一、實作 CSI Driver 1. **撰寫 CSI Controller + Node 外掛程式(Plugin)** * Controller 端(通常包在一個 Deployment)實作 CreateVolume/DeleteVolume 等 RPC,跟你的儲存後端(SAN、Ceph、LVM、雲端 API⋯⋯)溝通。 * Node 端(DaemonSet)實作 NodeStage/NodePublish 等 RPC,負責把實際的塊裝置 attach、format、mount 給 kubelet。 2. **打包成容器.並在叢集裡部署** * 你可以用 [CSI Spec](https://kubernetes-csi.github.io/docs/spec.html) 提供的 Go 模板來快速起步。 * 部署後,它會在叢集中註冊自己能處理的 `provisioner` 名稱(通常像 `csi.mycompany.com`)。 3. **定義 StorageClass** ```yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: my-custom-ssd provisioner: csi.mycompany.com # 就是你 CSI driver 報給 K8s 的這個名字 parameters: foo: bar # 可以自由定義傳給 Controller 的參數 reclaimPolicy: Delete volumeBindingMode: WaitForFirstConsumer ``` --- ### 二、用外部 Provisioner Sidecar(External Provisioner) 如果後端的供應邏輯不需要 CSI 的複雜度,也可以用 Kubernetes 社群提供的 [external-provisioner](https://github.com/kubernetes-csi/external-provisioner) sidecar 架構: 1. **實作一個監聽 PVC 事件的 Controller** * 以 Go/Python/任何語言撰寫一個程式,監聽 API Server 上的 PVC 新增事件(用 client-go Informer 或 Kubernetes API)。 * 收到新 PVC(符合特定 StorageClass)就呼叫後端 API 創建卷,然後回寫一個對應的 PV→PVC Bind。 2. **打包 Sidecar 容器+RBAC** * 給它最高只讀 PVC、新增 PV、更新 PV 狀態的權限。 * 把它部署成 Deployment,並指定它要處理的 `provisioner` 名稱。 3. **StorageClass 範例** ```yaml= apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: external-custom provisioner: external.mycompany.com # 你 Controller 程式自己定 parameters: region: us-west-1 tier: gold ``` --- ### 小結 * **CSI Driver**:業界推薦做法,支援多雲、多種 attach 類型,也能被 Kubernetes 原生管理。適合需要高可靠、跨雲後端、Volume 拖移等複雜場景。 * **External Provisioner**:快速上手,用一般程式呼叫 Kubernetes API+後端 API 即可,適合輕量或內部系統對接。 不論哪種方式,最終你都能在 `StorageClass.provisioner` 填上你自己註冊的名字,Kubernetes 就會把對應的 PVC 交給你開發的程式來「處理動態建立 PV」的流程。 <br> <hr> <br> ### pvc data 會在什麼時候觸發清除? - ### Question ``` $ kubectl get pv -n tj-slurm4 | grep slurm pvc-aec3ace8-8b74-407f-a807-1ed82cc32d39 4Gi RWO Delete Bound tj-slurm/statesave-tj-slurm-controller-0 default-local <unset> 4m44s pvc-c4c3cee6-58f6-47a6-8263-6c553c5bf166 8Gi RWO Delete Bound tj-slurm/data-tj-slurm-mariadb-0 default-local <unset> 4m51s ``` pvc data 會在什麼時候觸發清除? - ### gpt-o4-mini-high (2025/06/19) 在你這兩個 PV(`ReclaimPolicy=Delete`)的設定下,底層儲存(也就是「data」所對應的實體或目錄)只有在對應的 PVC 被刪除時,才會由 Storage Provisioner 自動清除。具體流程是: 1. **刪除 PVC** 當你執行 ```bash kubectl delete pvc -n tj-slurm4 data-tj-slurm-mariadb-0 ``` 這個 PVC 資源就會被刪除。 2. **PV 狀態轉為 Released** Kubernetes 會先將對應的 PV 狀態從 `Bound` 變更為 `Released`(表示它的佔用關係已解除),同時因為 `ReclaimPolicy=Delete`,接著進入回收流程。 3. **Provisioner 刪除底層資源** Dynamic Provisioner(這裡是 `default-local`)偵測到 PVC 被刪,會自動刪除底層儲存卷(對 Local PV 就是刪除當地目錄;對雲端卷就是呼叫雲端 API 刪除磁碟),清空上面的所有資料。 4. **PV 資源也一併移除** 最後,PV 物件本身也會被 Kubernetes 自動刪除。 --- > **重點**: > > * 只刪除 Pod 或 Deployment 並不會清除底層資料,必須刪除 PVC。 > * Namespace 刪除時會一併刪除裡面的 PVC,自動觸發相同流程。 > * 若你想要保留資料,應將 `ReclaimPolicy` 設為 `Retain`,這樣刪除 PVC 後底層資料不會被移除。 <br> <hr> <br> ### RECLAIM POLICY 用途是? - ### Question ``` $ kubetl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE pvc-01a20414-1dc8-4636-9b29-c10f82a9317d 250Gi RWO Delete Bound dev/ollama-pvc default-local <unset> 7d19h pvc-1bfd5aae-15ad-4514-a9b4-4ee3c7e8b59d 8Gi RWO Delete Bound slurm/data-slurm-mariadb-0 default-local <unset> 19h pvc-584979c1-4171-49aa-bb11-6fdeaa156333 4Gi RWO Delete Bound slurm/statesave-slurm-controller-0 default-local <unset> 19h ... ``` RECLAIM POLICY 用途是? - ### gpt-5 (2025/08/09) 簡單說:**RECLAIM POLICY(回收策略)決定當 PVC 被刪除後,這個 PV 與其底層儲存資源要怎麼處理**。常見值與行為: * **Retain(保留)**:PVC 刪掉後,PV 進入 *Released* 狀態但**不會刪資料也不會回到可再利用**。管理員需手動清理資料、移除 `claimRef` 讓 PV 回到 *Available*,或是用同一塊磁碟建立新的 PV。**適合資料庫等重要資料**,避免誤刪。 * **Delete(刪除)**:PVC 刪掉後,**同時刪 PV 物件,並嘗試刪除底層儲存(由外掛/Provisioner 決定實際行為)**。像雲端磁碟(EBS、PD)會真的銷毀;本機/路徑類 Provisioner 可能只是清空目錄。**適合暫存或可重建的資料**。 * **Recycle(回收)**:已廢棄(deprecated),過去是簡單清空後再提供使用,現在不要用。 你目前看到的都是 **Delete**,代表「刪 PVC 通常就連同底層空間一起清掉」。若有 **MariaDB / 狀態資料(你列表裡如 `data-slurm-mariadb-0`、`statesave-*`)**,建議改成 **Retain**,降低風險。 --- ### 常用操作 **1) 僅改單一 PV 的策略** ```bash # 將某個 PV 改為 Retain(之後刪 PVC 就不會把資料一起刪) kubectl patch pv pvc-1bfd5aae-15ad-4514-a9b4-4ee3c7e8b59d \ -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}' ``` **2) 動態建立的 PV:從 StorageClass 根源調整(影響未來新建的 PV)** ```bash # 查看 StorageClass 的回收策略 kubectl get sc default-local -o yaml | yq '.reclaimPolicy' # 將 StorageClass 的回收策略改為 Retain(新建 PV 才會套用) kubectl patch sc default-local -p '{"reclaimPolicy":"Retain"}' ``` > 注意:不同 Provisioner 的 **Delete** 行為不盡相同(雲端磁碟多半會銷毀;本機路徑類型可能只是清資料夾)。調整前先確認你叢集的 Provisioner 實作。 **3) Retain 之後如何讓 PV 再被使用?** ```bash # 刪除 PVC 後,PV 會是 Released。 # 管理員清理底層資料 → 移除 PV 的 claimRef 讓它回到 Available: kubectl patch pv <PV_NAME> --type=json \ -p='[{"op":"remove","path":"/spec/claimRef"}]' # 之後新 PVC(條件相符)即可再次綁定該 PV ``` --- ### 快速選擇建議 * **重要資料(DB、長期狀態)** → 用 **Retain** * **可重建或暫存資料(快取、臨時工作目錄)** → **Delete** 無妨 <br> {%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}