[元件][儲存] 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 %}