深色模式
本地存储与拓扑感知
摘要:本文面向平台工程师,讲解 Kubernetes 本地存储(local volume)的提供方式与拓扑约束:原生
local卷类型、local-static-provisioner(GA 自 1.14)如何把节点本地盘变成标准 PVC、为何本地盘必须带nodeAffinity、以及WaitForFirstConsumer/allowedTopologies如何实现拓扑感知调度。适用 Kubernetes v1.28+。
适用版本与前提
- Kubernetes:v1.28+(local-static-provisioner GA 自 1.14;推荐 provisioner 2.9.0 对应 K8s 1.21+)
- 工具:
kubectl、helm - 前提:已理解 StorageClass 动态供给 与 排障
背景与问题
本地盘(NVMe/SSD 直挂节点)提供最低延迟、最高 IOPS、最低成本,适合 etcd、本地缓存、时序数据库、Ceph OSD 等。但它与云盘本质不同:本地盘不跨节点、不跨可用区、节点没了数据就没了。Kubernetes 必须把这个硬约束显式建模进调度,否则 Pod 会被调度到没有该盘的其他节点而无法启动。
原生 local 卷类型
local 卷指向节点上已挂载的目录或块设备,必须带 nodeAffinity 告诉调度器"这个卷只在某节点可用":
yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv-ssd1
spec:
capacity:
storage: 500Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /mnt/disks/ssd1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node-11
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
关键约束
local卷默认不支持动态供给(除非借助 local-static-provisioner)。- 缺少
nodeAffinity的 local PV 调度器无法正确放置 Pod,会失败。 local不同于hostPath:local 有 PV 生命周期与调度约束,hostPath 无,生产应避免 hostPath 做持久数据。
local-static-provisioner 工作机制
原生 local 卷需要手工建 PV,运维不可持续。local-static-provisioner(kubernetes-sigs,GA 自 1.14)自动发现节点上的本地盘并管理 PV 生命周期。
工作方式:
- 管理员在每节点把本地盘挂载到"发现目录"(如
/mnt/disks/ssd1); - provisioner 通过
discoveryConfig扫描这些目录,为每个盘创建带nodeAffinity的 PV; - Block 模式 PV 需在发现目录下建指向块设备的符号链接;
- PVC 删除后,provisioner 负责清理/回收盘(不支持动态供给,只管理预配置盘)。
配置要点(Helm values / ConfigMap):
storageClassMap:把发现目录映射到 StorageClass(如local-storage);discoveryConfig.paths:发现目录列表;nodeLabelsForPV:把节点 label 复制到 PV(便于拓扑调度)。
厂商特定 / 版本相关
推荐 provisioner 版本 2.9.0 对应 K8s 1.21+。部署方式(Helm vs 裸 YAML)、storageClassMap 字段名随版本变化,落地前核对目标版本文档 [未实测,建议 staging 验证]。
拓扑感知调度
本地盘/云盘都有拓扑约束,Kubernetes 用标签(如 topology.kubernetes.io/zone、kubernetes.io/hostname)表达拓扑,并用两种机制落地:
WaitForFirstConsumer
StorageClass 设 volumeBindingMode: WaitForFirstConsumer 后,PV 的供给/绑定延迟到"使用该 PVC 的 Pod 被调度"之后,从而把卷放到 Pod 所在拓扑。本地盘强烈建议配合此模式(通常 local StorageClass 也用它)。
生产危险
本地盘 StorageClass 若误用 Immediate,PV 会在任意节点预创建,而 Pod 可能调度到别的节点 → node(s) had volume node affinity conflict。本地盘务必 WaitForFirstConsumer。
allowedTopologies
当 WaitForFirstConsumer 仍不足,可显式限制供给拓扑(替代旧 zone/zones 参数):
yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner # 本地盘静态供给
volumeBindingMode: WaitForFirstConsumer
allowedTopologies:
- matchLabelExpressions:
- key: kubernetes.io/hostname
values:
- node-1
- node-21
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
注意
WaitForFirstConsumer 下 Pod 不要用 spec.nodeName(绕过调度器会让 PVC 永久 pending),改用 nodeSelector: kubernetes.io/hostname。
节点故障下的数据风险
生产危险
本地 PV 的数据与节点生命周期绑定。当承载本地 PV 的节点被删除,数据很可能丢失,但 PV 对象仍存在,调度器会无限尝试把 Pod 调度到已删除的节点(Pod 卡 Pending)。必须用 node cleanup 机制或人工清理 PV。
应对:
- 部署 local volume node cleanup controller(provisioner 配套,标记失效 PV 为
Released/Available触发重建); - 关键数据用复制(如 etcd 3 副本、Ceph 多 OSD)而非单副本本地盘;
- 把本地盘用于"可重建缓存/临时数据"或"有副本的有状态服务",而非单点数据源。
生产实践
- 本地盘用 local-static-provisioner 统一管理,避免手工建 PV;
- StorageClass 设
Retain+WaitForFirstConsumer; - 用
nodeAffinity/拓扑把本地盘负载分散到多节点,避免热点; - 配合 Pod
topologySpreadConstraints让有状态副本跨节点/跨 zone 分布; - 监控节点磁盘 SMART/IO 与 PV 容量,本地盘故障无云冗余兜底。
验证
bash
kubectl get pv -o wide | grep local # 看 PV 的 nodeAffinity/引用节点
kubectl get storageclass local-storage
kubectl describe pv local-pv-ssd1 # 确认 nodeAffinity 与路径
# 部署测试 PVC + Pod,确认被调度到正确节点
kubectl get pod <pod> -o wide1
2
3
4
5
2
3
4
5
回滚与清理
生产危险
删除本地 PV 不会删除节点上数据;删除节点对象会释放其全部本地卷引用并可能触发重调度。清理本地盘数据需登录节点手动操作,务必确认 Pod 已彻底下线。
bash
# 手动使失效本地 PV(节点已失联时)
kubectl patch pv <pv-name> -p '{"status":{"phase":"Released"}}' # 仅应急,需配合 cleanup 控制器
# 实际回收由 node cleanup controller 或人工删 PV + 清节点目录1
2
3
2
3
性能、容量与成本
- 本地 NVMe 延迟可低至亚毫秒、IOPS 数十万,远胜网络块存储,适合延迟敏感负载;
- 成本最低(无网络存储溢价),但无跨节点冗余,HA 必须靠应用层副本;
- 容量受单节点盘位限制,扩容需加盘并扩展 discovery 目录,较云盘不便。
常见坑
- 忘了
nodeAffinity→ 调度失败; - 本地盘 StorageClass 用
Immediate→ 拓扑冲突; - 节点删除后 PV 残留导致 Pod 卡 Pending;
- 把单副本数据库放单节点本地盘,节点故障即数据全失。
参考资料
- Kubernetes 官方文档 - Volumes (local),访问日期:2026-10-08。
- Kubernetes 官方文档 - Storage Classes(volumeBindingMode / allowedTopologies),访问日期:2026-10-08。
- local-static-provisioner README (kubernetes-sigs),访问日期:2026-10-08。
- Kubernetes 官方文档 - Topology-Aware Volume Scheduling,访问日期:2026-10-08。