Day 14 - StorageClass 与动态供给

📘 Day 14:StorageClass 与动态供给

🎯 今日目标

  • 理解 StorageClass 如何实现”按需创建 PV”
  • 能部署 NFS Provisioner 实现动态供给
  • 掌握 default StorageClass 的设置
  • 能做 PVC 在线扩容
  • 理解 Immediate vs WaitForFirstConsumer

🧠 理论精讲(30 分钟)

为什么需要 StorageClass

静态 PV 的问题:每次创建 PVC 前都要手动创建 PV,管理员负担重。

1
2
动态供给:
PVC 创建 → StorageClass → Provisioner → 自动创建 PV → 绑定 PVC

StorageClass 关键参数

参数 含义
provisioner 供给驱动(如 nfs.csi.k8s.io)
volumeBindingMode Immediate(立即)/ WaitForFirstConsumer(延迟)
reclaimPolicy Delete(默认)/ Retain
allowVolumeExpansion 是否允许 PVC 扩容

volumeBindingMode

模式 行为
Immediate PVC 创建后立即绑定 PV(不考虑 Pod 调度)
WaitForFirstConsumer 等到 Pod 实际使用时才创建 PV(保证 PV 和 Pod 在同一可用区)

🔧 动手实操(120 分钟)

练习 14.1:NFS 动态供给环境准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# === 在 Master 或独立存储节点上安装 NFS Server ===

# 1. 安装 NFS 服务端
sudo apt-get update
sudo apt-get install -y nfs-kernel-server

# 2. 创建共享目录
sudo mkdir -p /srv/nfs/k8s
sudo chown nobody:nogroup /srv/nfs/k8s
sudo chmod 777 /srv/nfs/k8s

# 3. 配置 exports
echo "/srv/nfs/k8s *(rw,sync,no_subtree_check,no_root_squash)" | sudo tee -a /etc/exports
sudo exportfs -rav
sudo systemctl restart nfs-kernel-server

# 4. 验证 NFS 可用(在 worker 节点测试)
# sudo apt-get install -y nfs-common
# sudo mount -t nfs <master-ip>:/srv/nfs/k8s /mnt
# ls /mnt
# sudo umount /mnt

练习 14.2:部署 NFS Subdir External Provisioner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# 1. 创建 RBAC + Deployment
# 这是社区维护的 NFS 动态供给器

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-provisioner
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nfs-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: nfs-provisioner-runner
subjects:
- kind: ServiceAccount
name: nfs-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-provisioner
spec:
replicas: 1
selector:
matchLabels:
app: nfs-provisioner
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-provisioner
spec:
serviceAccountName: nfs-provisioner
containers:
- name: nfs-provisioner
image: registry.cn-hangzhou.aliyuncs.com/google_containers/nfs-subdir-external-provisioner:v4.0.2
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: "10.0.0.1" # 替换为你的 NFS 服务器 IP
- name: NFS_PATH
value: /srv/nfs/k8s
volumeMounts:
- name: nfs-root
mountPath: /persistentvolumes
volumes:
- name: nfs-root
nfs:
server: 10.0.0.1 # 替换为你的 NFS 服务器 IP
path: /srv/nfs/k8s
EOF

# 2. 创建 StorageClass
cat <<EOF | kubectl apply -f -
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-sc
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
reclaimPolicy: Delete
volumeBindingMode: Immediate
EOF

# 3. 验证
kubectl get sc
# NAME PROVISIONER RECLAIMPOLICY
# nfs-sc (default) k8s-sigs.io/nfs-subdir-external-provisioner Delete

kubectl get pod -l app=nfs-provisioner
# Running

练习 14.3:动态创建 PVC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 1. 创建 PVC(不再需要手动创建 PV!)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dynamic-pvc
spec:
storageClassName: nfs-sc
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
EOF

# 2. 观察自动创建 PV
kubectl get pvc dynamic-pvc
# STATUS: Bound

kubectl get pv
# 看到自动创建的 PV(名称如 pvc-xxx-xxx-xxx)

# 3. 在 Pod 中使用
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: dynamic-pod
spec:
containers:
- name: app
image: busybox:1.36
command:
- sh
- -c
- |
echo "Dynamic provision test" > /data/test.txt
cat /data/test.txt
sleep 3600
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: dynamic-pvc
EOF

# 4. 验证 NFS 服务器上的实际文件
# ssh 到 NFS 服务器
ls /srv/nfs/k8s/
# 看到自动创建的目录(命名格式:<namespace>-<pvc-name>-<uuid>)

# 5. 清理
kubectl delete pod dynamic-pod
kubectl delete pvc dynamic-pvc
# PV 会自动删除(因为 reclaimPolicy: Delete)

练习 14.4:PVC 扩容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 1. 首先确保 StorageClass 允许扩容
kubectl get sc nfs-sc
# 修改 StorageClass 允许扩容
kubectl patch sc nfs-sc -p '{"allowVolumeExpansion":true}'

# 2. 创建 PVC
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: expandable-pvc
spec:
storageClassName: nfs-sc
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 1Gi
EOF

# 3. 扩容 PVC(修改 requests.storage)
kubectl patch pvc expandable-pvc -p '{"spec":{"resources":{"requests":{"storage":"3Gi"}}}}'

# 4. 观察 PVC 状态
kubectl get pvc expandable-pvc -w
# CAPACITY 从 1Gi 逐渐变为 3Gi

# 5. 验证
kubectl get pvc expandable-pvc
kubectl get pv | grep expandable-pvc

# 6. 清理
kubectl delete pvc expandable-pvc

🐛 排错练习(30 分钟)

场景 1:PVC Pending — Provisioner 未运行

1
2
3
4
5
6
7
8
9
10
11
12
# 排查:
# 1. Provisioner Pod 是否运行?
kubectl get pod -l app=nfs-provisioner

# 2. 查看 Provisioner 日志
kubectl logs -l app=nfs-provisioner --tail=50

# 3. 检查 NFS 连通性
kubectl exec -it <provisioner-pod> -- sh -c "showmount -e <NFS_SERVER>"

# 4. 检查 StorageClass 的 provisioner 名称是否匹配
kubectl get sc nfs-sc -o yaml | grep provisioner

场景 2:扩容失败

1
2
3
4
5
6
7
8
# 原因 1:StorageClass 不允许扩容
kubectl get sc <name> -o yaml | grep allowVolumeExpansion

# 原因 2:请求的容量小于当前容量
# PVC 只能扩容不能缩容

# 原因 3:PVC 正在被 Pod 使用
# 某些 CSI 驱动要求在 Pod 运行时才能扩容

🏆 赛题模拟(40 分钟)

⚠️ 严格限时 40 分钟

题目:动态存储与扩容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
【前置条件】
NFS 服务器已部署(IP: 10.0.0.1),共享路径 /srv/nfs/k8s

【操作要求】

1. 部署 NFS Subdir External Provisioner
2. 创建 StorageClass nfs-retain(provisioner 指向 NFS,reclaimPolicy=Retain)
3. 将此 StorageClass 设为默认
4. 创建 PVC data-pvc:5Gi, ReadWriteMany
5. 验证自动创建了 PV
6. 创建 Pod data-app 使用此 PVC,挂载到 /app-data,写入测试数据
7. 将 PVC 扩容到 8Gi
8. 验证数据在扩容后仍然完整
9. 删除 Pod 和 PVC
10. 确认 PV 变为 Released 状态(因为 reclaimPolicy=Retain)

【评分标准】
- Provisioner 部署正确(25 分)
- StorageClass 配置正确(20 分)
- 动态供给工作正常(20 分)
- 扩容成功 + 数据完整(25 分)
- ReclaimPolicy 验证(10 分)