Kubernetes PV、PVC、StorageClass与CSI存储全过程
Pod和容器是可替换计算实例,容器可写层跟随容器/Pod生命周期,不能承载需要长期保存的数据。Kubernetes通过Volume、PersistentVolume、PersistentVolumeClaim、StorageClass和CSI,把应用的存储需求与云盘、SAN、NFS、分布式文件系统等具体实现解耦。
“PVC Bound”只说明申请和PV已经绑定,不代表磁盘已附加到目标Node、文件系统已挂载、权限正确、数据库数据一致或已经有备份。本页从PVC提交开始,追踪动态供应、调度拓扑、Attach、Stage、Publish、扩容、快照和故障恢复。
学习目标
学完后应能:
- 区分容器可写层、emptyDir、hostPath、Local PV和持久PVC。
- 解释Volume、PV、PVC、StorageClass、VolumeAttachment和CSI职责。
- 解释静态供应与动态供应的绑定全过程。
- 区分Immediate与WaitForFirstConsumer的拓扑语义。
- 解释Scheduler怎样把Pod约束和存储Zone共同纳入决策。
- 解释CSI Controller/Node插件及Create、Attach、Stage、Publish调用链。
- 准确区分RWO、ROX、RWX和RWOP,而不是把RWO误解为单Pod。
- 区分Filesystem与Block VolumeMode。
- 解释ReclaimPolicy、Finalizer、Released和底层磁盘生命周期。
- 设计在线扩容、文件系统扩容和不支持缩容的处理。
- 区分快照、数据库一致性备份、PITR和灾备复制。
- 解释StatefulSet volumeClaimTemplates和PVC保留边界。
- 根据PVC/PV/VolumeAttachment/Event/CSI日志排查Pending、Multi-Attach和FailedMount。
- 设计容量、性能、加密、权限、监控和恢复演练。
一、数据究竟写到哪里
应用写文件
├── 容器可写层:容器删除通常丢失
├── emptyDir:Pod删除丢失,容器重启通常仍在
├── hostPath:绑定某Node路径,跨Node不可移植
├── Local PV:受控本地盘,带Node拓扑和PV生命周期
├── PVC/PV:由存储系统提供持久卷
└── 对象存储/数据库:应用通过网络API访问的外部系统不是所有持久数据都应该写PVC:
- 用户图片/视频常适合对象存储。
- 应用日志适合stdout并集中采集。
- Session适合Redis/数据库等共享系统。
- 数据库数据目录才可能使用块存储PVC。
- 临时排序/解压缓存可使用emptyDir并设置容量。
二、核心对象关系
flowchart TD
A["Pod声明volume使用PVC"] --> B["PVC表达容量、访问模式和StorageClass"]
B --> C["PVC绑定PV"]
C --> D["PV描述底层Volume Handle和策略"]
D --> E["StorageClass定义Provisioner和供应参数"]
E --> F["CSI Driver管理真实磁盘/文件系统"]| 对象 | Namespace | 主要消费者/维护者 | 含义 |
|---|---|---|---|
| Pod Volume | Namespaced PodSpec内 | 应用/kubelet | 容器看到的挂载/设备声明 |
| PVC | Namespaced | 应用 | 对存储容量和能力的申请 |
| PV | Cluster-scoped | 控制器/管理员 | 已供应存储资源的K8s表示 |
| StorageClass | Cluster-scoped | 平台团队 | 动态供应的类别和参数 |
| VolumeAttachment | Cluster-scoped | Attach/Detach与CSI | 卷与Node附加关系 |
| VolumeSnapshot | Namespaced CRD | 应用/备份系统 | 某PVC/卷的快照请求 |
PVC只能被同Namespace的Pod直接引用;PV和StorageClass是集群级对象。
三、为什么需要PV和PVC解耦
应用只声明:
需要100Gi
需要单Node读写
需要fast-rwo存储类别
需要Filesystem平台决定:
- 使用哪家云盘/阵列。
- IOPS和吞吐规格。
- 加密Key。
- Zone。
- ReclaimPolicy。
- CSI Driver和升级。
这样应用清单不直接携带云盘ID和厂商API,但StorageClass名称/能力仍是平台契约,不能完全忽略存储差异。
四、PVC完整结构
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: order-db-data
namespace: data
labels:
app: order-db
spec:
accessModes:
- ReadWriteOnce
volumeMode: Filesystem
storageClassName: fast-rwo
resources:
requests:
storage: 100Gi主要字段:
| 字段 | 作用 |
|---|---|
accessModes | 请求挂载访问语义 |
volumeMode | Filesystem或Block |
storageClassName | 指定存储类别 |
requests.storage | 最低申请容量 |
selector | 静态供应时筛选PV,可能阻止动态供应 |
volumeName | 预绑定指定PV,需谨慎 |
PVC请求100Gi,实际PV可能大于100Gi;绑定不是从一个大PV切一部分给PVC,一个PV通常绑定一个PVC。
五、PV完整结构
CSI PV简化示例:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pvc-2f9c-example
spec:
capacity:
storage: 100Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: fast-rwo
claimRef:
namespace: data
name: order-db-data
csi:
driver: block.csi.example.com
volumeHandle: provider-volume-id-12345
fsType: ext4volumeHandle是CSI Driver识别真实卷的ID,通常不能随意修改。PV对象删除与真实磁盘删除是否联动,由ReclaimPolicy、CSI和Finalizer共同决定。
六、StorageClass不是一块磁盘
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-rwo
provisioner: block.csi.example.com
parameters:
type: premium-ssd
encrypted: "true"
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer它是供应模板/类别,字段解释:
provisioner:哪一个CSI/存储插件处理。parameters:厂商特有磁盘类型、加密等参数。reclaimPolicy:动态PV默认回收策略。allowVolumeExpansion:是否允许PVC请求扩容。volumeBindingMode:何时供应/绑定以及是否等待调度拓扑。mountOptions:平台可设置挂载选项,错误值可能导致Mount失败。
业务团队通常只引用平台审核后的StorageClass,不应自行创建带任意加密Key、NFS地址或危险Mount Option的Class。
七、默认StorageClass和空字符串边界
PVC未写 storageClassName 时,集群可能通过默认StorageClass供应。显式写:
storageClassName: ""通常表示不使用StorageClass动态供应,尝试绑定无Class的静态PV。未写和空字符串不是同一语义。
多个默认StorageClass或默认Class变更可能让同一清单在不同集群供应不同磁盘。生产PVC应显式选择受控类别。
八、静态供应全过程
管理员先创建PV,应用再创建PVC:
flowchart TD
A["管理员创建带容量/模式/Class的PV"] --> B["应用创建PVC"]
B --> C["PV Controller查找Available PV"]
C --> D["匹配StorageClass、AccessMode、VolumeMode、容量和Selector"]
D --> E{"是否找到唯一适合PV"}
E -- "否" --> F["PVC保持Pending并记录Event"]
E -- "是" --> G["写claimRef并双向绑定"]
G --> H["PVC/PV进入Bound"]静态供应适合已有磁盘、迁移数据或特殊硬件。预绑定时PVC volumeName和PV claimRef必须一致,且不能绕过Node Affinity、Class、VolumeMode等必要约束。
九、动态供应全过程
当PVC引用StorageClass且没有现成PV,External Provisioner观察PVC并调用CSI:
flowchart TD
A["创建PVC storageClass=fast-rwo"] --> B["External Provisioner观察未绑定PVC"]
B --> C["读取StorageClass和拓扑要求"]
C --> D["调用CSI Controller CreateVolume"]
D --> E["存储系统创建真实卷"]
E --> F["Provisioner创建对应PV"]
F --> G["PV与PVC绑定"]
G --> H["PVC状态Bound"]如果存储API超时,Provisioner通常重试,CreateVolume需要幂等,避免一次请求创建多个孤儿磁盘。排查应同时看PVC Event、Provisioner日志和云存储事件。
十、CSI架构
CSI把Kubernetes与存储厂商实现分离。典型部署:
flowchart TD
A["CSI Controller Deployment"] --> A1["External Provisioner"]
A --> A2["External Attacher"]
A --> A3["External Resizer"]
A --> A4["Snapshotter等Sidecar"]
A --> A5["Driver Controller Plugin"]
B["每Node的CSI Node DaemonSet"] --> B1["Node Driver Registrar"]
B --> B2["Driver Node Plugin"]
C["Kubernetes Controller/kubelet"] --> A
C --> BController侧常见能力
- Create/Delete Volume。
- ControllerPublish/Unpublish,即Attach/Detach。
- Expand Volume。
- Snapshot/Create Clone(若支持)。
Node侧常见能力
- NodeStage/Unstage。
- NodePublish/Unpublish。
- NodeExpand。
- 获取Node拓扑和能力。
不是所有存储都需要Attach:NFS等网络文件系统可能直接由Node Mount;云块盘通常需要先附加到虚拟机。
十一、Immediate绑定模式
volumeBindingMode: ImmediatePVC创建后立即供应和绑定,哪怕还没有Pod。对于有Zone限制的块盘,可能先在Zone A创建卷,而Pod因CPU、Affinity等只能调度到Zone B,最终无法同时满足。
Immediate适合无拓扑限制的共享存储或平台已确保拓扑一致的场景。
十二、WaitForFirstConsumer为什么重要
volumeBindingMode: WaitForFirstConsumerPVC先保持Pending,直到有Pod引用它,Scheduler同时考虑:
- Pod CPU/内存。
- NodeSelector/Affinity。
- Taint/Toleration。
- Pod拓扑分散。
- 存储允许的Zone/Node拓扑。
flowchart TD
A["创建PVC,暂不供应卷"] --> B["创建引用PVC的Pod"]
B --> C["Scheduler筛选计算与存储可行Node"]
C --> D["选定候选Zone/Node拓扑"]
D --> E["Provisioner在对应拓扑CreateVolume"]
E --> F["PV/PVC绑定"]
F --> G["Pod完成Node绑定"]此时PVC Pending可能是正常等待首个消费者,不能看到Pending就手工创建随机PV。
十三、Scheduler的Volume Binding阶段
Scheduler插件会检查:
- PVC是否Bound。
- 未绑定PVC是否可在候选Node拓扑供应。
- 已绑定PV Node Affinity是否匹配。
- 访问模式/Attach限制。
- 同一Pod多个PVC能否在一个Node同时满足。
调度失败Event可能显示Volume Node Affinity Conflict、未绑定Immediate PVC等。必须读完整FailedScheduling消息,不能只归因“节点资源不足”。
十四、从Bound到容器可读写还有多远
PVC Bound之后,Pod在Node侧仍要经过:
flowchart TD
A["Pod绑定目标Node"] --> B["Attach/Detach Controller确认卷需要附加"]
B --> C["External Attacher调用ControllerPublishVolume"]
C --> D["云/存储系统把卷Attach到Node"]
D --> E["VolumeAttachment状态Attached"]
E --> F["kubelet调用NodeStageVolume"]
F --> G["必要时格式化并挂载到Node全局Stage路径"]
G --> H["kubelet调用NodePublishVolume"]
H --> I["Bind Mount/设备映射到Pod路径"]
I --> J["容器启动并访问Volume"]所以:
PVC=Bound
但Pod=ContainerCreating/FailedMount完全可能。问题在Attach、Node插件、文件系统、权限或Mount,而不是PVC绑定。
十五、ControllerPublish与NodePublish区别
- ControllerPublish:让存储系统把卷附加到某Node,例如云盘挂到虚拟机。
- NodeStage:在Node上准备设备/文件系统到全局Stage路径,可被多个Pod挂载复用。
- NodePublish:把Stage后的Volume发布到具体Pod的目标路径。
文件存储可能没有传统Attach;块存储则需处理设备出现、格式化和Mount。CSI调用应幂等,因为Controller/kubelet可能重试。
十六、VolumeMode:Filesystem与Block
Filesystem
volumeMode: FilesystemNode插件将块设备格式化为ext4/xfs等并Mount,容器看到目录。首次格式化是高风险动作,Driver必须识别已有文件系统,不能误格式化有数据卷。
Block
volumeMode: BlockPod通过 volumeDevices看到原始块设备:
volumeDevices:
- name: data
devicePath: /dev/xvda应用负责文件系统/数据库原始设备管理。使用门槛高,错误写入可直接破坏数据;不要把Block设备当普通目录mountPath。
PVC和PV的VolumeMode必须兼容。
十七、四种AccessMode准确语义
| 模式 | 准确语义 | 常见实现 |
|---|---|---|
RWO ReadWriteOnce | 单个Node读写挂载 | 云块盘 |
ROX ReadOnlyMany | 多Node只读挂载 | 共享文件/镜像数据 |
RWX ReadWriteMany | 多Node读写挂载 | NFS/分布式文件系统 |
RWOP ReadWriteOncePod | 集群范围单Pod读写 | 支持该能力的CSI |
17.1 RWO不等于单Pod
RWO主要限制单Node读写。同一Node上的多个Pod在驱动和工作负载允许时可能同时挂载同一PVC。需要严格单Pod写入时评估RWOP,并确认CSI Sidecar/Driver和Kubernetes版本支持。
17.2 AccessMode不是应用锁
即使RWX允许多个Pod写,文件系统也不会自动解决:
- 多进程并发写同一文件。
- 数据库锁和缓存一致性。
- 文件重命名/锁语义差异。
- NFS缓存和故障恢复。
应用必须支持共享文件系统语义。
17.3 驱动是否强制执行
AccessMode用于匹配、调度和CSI请求,但底层是否严格阻止越权挂载取决于Driver/存储。安全性不能只依赖PVC YAML。
十八、emptyDir原理和风险
volumes:
- name: cache
emptyDir:
sizeLimit: 2Gi生命周期:
- Pod分配到Node时创建。
- 同Pod容器重启时通常保留。
- Pod删除/重新调度时删除。
- Node故障可能丢失。
适合临时文件、Sidecar共享和可重建缓存,不适合数据库持久数据。
Memory emptyDir
emptyDir:
medium: Memory
sizeLimit: 512Mi通常使用tmpfs,写入会占用Node/容器内存记账,可能导致内存压力或OOM。sizeLimit不等于额外免费内存。默认磁盘emptyDir还会消耗Node Ephemeral Storage和inode。
十九、hostPath为什么危险
hostPath:
path: /var/lib/example
type: Directory风险:
- Pod换Node看到不同数据。
- Node磁盘损坏即数据丢失。
- 绕过存储配额/生命周期。
- 挂载宿主机敏感目录可权限突破。
- 多租户Pod访问Node文件。
hostPath适合必须访问Node文件的受控DaemonSet等特殊组件,不是生产业务持久化的默认选择。尽量只读、限制路径、禁止privileged并使用Admission策略。
二十、Local PersistentVolume
Local PV比hostPath多了PV/PVC、容量和Node Affinity管理:
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values: ["worker-storage-01"]性能好但绑定Node故障域。Pod不能随意漂移到其他Node继续使用同一盘;需要应用复制、多副本、备份和Local Volume Manager等运维能力。
Local PV通常应配WaitForFirstConsumer,避免PVC先绑定到与Pod约束冲突的Node。
二十一、Generic/CSI Ephemeral Volume
除emptyDir外,Kubernetes支持通用临时PVC模板和CSI Ephemeral等模式,Volume随Pod创建/删除但可使用存储插件能力。适合每Pod临时空间,不等于长期持久数据。
字段成熟度和Driver支持随版本变化,使用前验证回收、配额和Pod删除行为。
二十二、ReclaimPolicy
Delete
PVC删除后,动态PV和底层卷通常由Controller删除。优点是自动清理,风险是误删PVC可能快速删除真实数据。
Retain
PVC删除后PV进入Released并保留底层卷,需要人工审计、清理claimRef、恢复或删除。优点是防误删,风险是孤儿盘、费用和敏感数据残留。
Recycle
旧式策略,现代集群基本不推荐/不支持。
生产数据库不能只凭“Retain更安全”结束设计;仍需备份、权限和恢复流程。Delete也可通过平台删除保护、快照和审批治理。
二十三、删除PVC时发生什么
flowchart TD
A["用户删除PVC"] --> B["PVC保护Finalizer检查是否仍被Pod使用"]
B --> C["解除PVC与PV绑定"]
C --> D{"PV ReclaimPolicy"}
D -- "Delete" --> E["Provisioner调用CSI DeleteVolume"]
E --> F["底层卷和PV删除"]
D -- "Retain" --> G["PV变Released,底层卷保留"]
G --> H["人工恢复、再绑定或安全擦除"]实际异步步骤、Finalizer名称和顺序受版本/CSI影响。对象长期Terminating时不要直接清Finalizer;先确认Pod使用、VolumeAttachment和底层API状态,强删可能留下正在挂载的卷或孤儿资源。
二十四、PV Released为什么不能自动给新PVC用
Released PV仍带旧claimRef和旧数据,Controller不会随便绑定给其他租户。恢复流程通常需要:
- 确认底层数据和旧PVC身份。
- 将ReclaimPolicy设为Retain,防止误删。
- 按受控流程移除/调整claimRef或创建预绑定对象。
- 新PVC
volumeName指向该PV。 - 验证Namespace、容量、AccessMode、VolumeMode和Node Affinity。
- 以只读/隔离方式检查数据后再开放。
不要在不知数据归属时把Released PV交给新业务。
二十五、PVC删除保护不等于数据保护
PVC Protection Finalizer可阻止正在被Pod使用的PVC立即删除,但不能防:
- 删除整个Namespace后的最终清理。
- 有权限用户强删Finalizer。
- 底层云盘被云控制台删除。
- 应用逻辑删除/覆盖数据。
- 勒索/凭据泄露。
它是对象生命周期保护,不是备份。
二十六、StatefulSet volumeClaimTemplates
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: fast-rwo
resources:
requests:
storage: 100Gi每个Ordinal得到独立PVC:
data-mysql-0
data-mysql-1
data-mysql-2Pod mysql-1重建后重新使用 data-mysql-1。StatefulSet默认通常保留PVC,缩容/删除不会自动清理数据;较新版本支持PVC保留策略,需按目标版本验证。
StatefulSet不会自动实现数据库复制、备份和选主。三个PVC只是三块存储。
二十七、强制删除有状态Pod的风险
强制删除API对象不保证旧Node进程已停止。Controller可能创建同名替代Pod并尝试挂载同一卷,出现:
- Multi-Attach。
- 两个实例声称同一身份。
- 文件系统并发写损坏。
- 数据库脑裂。
先隔离旧Node、确认进程和Attach状态,再按数据库/存储恢复协议处理。
二十八、在线扩容全过程
前提:StorageClass allowVolumeExpansion: true,Driver和文件系统支持。
修改PVC只能增大:
resources:
requests:
storage: 200Giflowchart TD
A["PVC请求从100Gi改到200Gi"] --> B["External Resizer观察PVC"]
B --> C["调用CSI ControllerExpandVolume"]
C --> D["底层卷容量扩大"]
D --> E["PV Capacity更新"]
E --> F{"是否还需Node/文件系统扩容"}
F -- "是" --> G["kubelet调用NodeExpandVolume/扩文件系统"]
F -- "否" --> H["PVC容量完成"]
G --> H块设备变大不等于文件系统自动可用全部空间。检查PVC Conditions、PV Capacity、Node扩容和容器内 df/文件系统工具。
二十九、为什么PVC不能直接缩容
Kubernetes通常不支持把PVC请求从200Gi缩到100Gi,因为底层块设备和文件系统缩容可能截断数据,很多文件系统也不支持在线缩小。
需要缩容时:
- 创建较小新PVC。
- 停写或建立一致性点。
- 使用数据库/文件级工具迁移数据。
- 校验完整性。
- 切换应用。
- 经过回滚窗口后删除旧卷。
不能只修改PV/PVC Capacity字段假装底层磁盘已缩小。
三十、扩容失败为什么会反复重试
如果请求超过配额/存储最大值,Resizer会持续尝试。检查:
- PVC Event和Condition。
- StorageClass expansion能力。
- CSI Resizer/Controller日志。
- 云盘最大容量和配额。
- Zone/卷类型限制。
- 文件系统支持。
不要手工编辑PV Capacity掩盖失败,这会造成控制面与真实设备不一致。
三十一、VolumeSnapshot对象链路
Snapshot API通常由CRD、Snapshot Controller、CSI Snapshotter和Driver共同实现:
flowchart TD
A["创建VolumeSnapshot引用PVC"] --> B["Snapshot Controller处理"]
B --> C["External Snapshotter调用CSI CreateSnapshot"]
C --> D["存储系统创建快照"]
D --> E["VolumeSnapshotContent记录Handle"]
E --> F["VolumeSnapshot ReadyToUse"]
F --> G["从Snapshot创建新PVC恢复"]没有安装CRD/Controller或Driver不支持时,写YAML不会自动获得快照。
三十二、快照不是备份
快照可能:
- 与源卷位于同一存储系统/账号。
- 依赖源卷元数据。
- 被同一管理员凭据删除。
- 只保证Crash Consistent。
- 不包含数据库日志/PITR链。
备份应有:
- 独立故障域/账号/区域。
- 不可变或防删除策略。
- 数据库一致性/PITR。
- 加密和密钥备份。
- 定期恢复演练。
- RPO/RTO验收。
三十三、Crash Consistent与Application Consistent
Crash Consistent
像突然断电时的磁盘状态。文件系统/数据库依赖Journal、redo/WAL恢复,可能丢失内存中尚未落盘的数据。
Application Consistent
快照前由应用:
- Flush/Checkpoint。
- 暂停或冻结写入。
- 锁定备份点。
- 记录WAL/binlog位置。
- 多卷建立一致性组。
然后拍快照并恢复写入。不能仅因为存储快照状态Ready就声明数据库可恢复。
三十四、数据库备份为什么不能只复制挂载目录
运行中数据库的数据页、redo/WAL、元数据和内存状态可能不在同一时间点。普通文件复制会跨越写入,得到逻辑不一致集合。
正确选择:
- 数据库逻辑备份。
- 数据库物理热备工具。
- 数据库配合的存储快照。
- 主从/PITR归档日志。
所有方案必须做恢复验证,而不是只看备份Job成功。
三十五、从Snapshot恢复PVC
标准PVC dataSource引用同Namespace中的VolumeSnapshot。下面示例假设平台已经把受控快照对象导入/创建在 restore-lab;不能直接用名称跨Namespace引用原业务Snapshot。某些新版本支持更丰富的跨Namespace DataSource机制,但需要显式授权和Driver支持,应按目标集群验证。
示意:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: order-db-restore-test
namespace: restore-lab
spec:
storageClassName: fast-rwo
dataSource:
name: order-db-snapshot-20260715
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Gi恢复到隔离Namespace/网络,验证:
- PVC Bound和Mount。
- 文件系统一致性。
- 数据库能启动恢复。
- 表数量/行数/校验。
- binlog/WAL位置。
- 应用关键查询。
- 数据脱敏和访问控制。
不要直接覆盖生产PVC做首次恢复实验。
三十六、Volume Clone
部分CSI支持PVC作为dataSource克隆新卷,适合测试副本/快速环境。Clone不是长期备份,通常同StorageClass/Namespace/拓扑有约束,且会复制敏感数据。需要脱敏、配额和生命周期清理。
三十七、存储加密和密钥
存储加密可能包括:
- 云盘/阵列静态加密。
- 文件系统/应用层加密。
- 传输加密(NFS/TLS等)。
- 数据库TDE。
StorageClass参数声明 encrypted: "true" 只在对应Driver实现中有意义。还需验证:
- 实际卷加密状态。
- 使用哪一个KMS Key。
- Key权限和审计。
- Key轮换。
- Snapshot/备份是否同样加密。
- 灾备时Key是否可恢复。
删除KMS Key可能让所有卷和备份不可读,比删除PVC影响更大。
三十八、文件权限和fsGroup
Pod以非root运行时,挂载卷可能由root拥有。Pod SecurityContext:
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
fsGroupChangePolicy: OnRootMismatchkubelet/CSI可能递归调整所有权,大卷包含百万文件时启动会很慢。OnRootMismatch可减少不必要递归,但支持和行为按版本/Driver验证。
不要用Init Container无条件 chmod -R 777:慢、不安全,并可能每次启动重扫全盘。
三十九、性能不只看容量
100Gi卷可能有不同:
- IOPS。
- Throughput。
- P50/P99 Latency。
- Burst Credit。
- Queue Depth。
- 单卷/单Node上限。
- 多租户噪声。
数据库慢时同时看:
- 应用/数据库IO等待。
- Node设备延迟。
- CSI/云盘指标。
- 文件系统容量和inode。
- Throttle/配额。
- Snapshot/备份争用。
把PVC从100Gi扩到200Gi是否提高IOPS取决于存储类型,不能一概而论。
四十、容量和inode耗尽
df -h /var/lib/mysql
df -i /var/lib/mysql可能出现:
- 字节空间充足但inode用尽,无法创建新文件。
- 文件已删除但进程仍持有FD,空间未释放。
- 容器看到的Filesystem与云盘指标口径不同。
- Thin Provisioning后端池耗尽。
监控PVC使用率、增长速率、inode、预测耗尽时间和存储后端容量。不能等95%才第一次告警。
四十一、完整StatefulSet商业结构Demo
前提:平台已提供 fast-rwo StorageClass和备份能力。示例只展示存储结构,不等于完整数据库高可用。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: order-db
namespace: data
spec:
serviceName: order-db
replicas: 3
selector:
matchLabels:
app: order-db
template:
metadata:
labels:
app: order-db
spec:
terminationGracePeriodSeconds: 120
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
fsGroupChangePolicy: OnRootMismatch
containers:
- name: database
image: registry.example.com/order-db@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
volumeMounts:
- name: data
mountPath: /var/lib/order-db
resources:
requests:
cpu: "1"
memory: 2Gi
limits:
memory: 4Gi
volumeClaimTemplates:
- metadata:
name: data
labels:
app: order-db
spec:
accessModes: ["ReadWriteOnce"]
volumeMode: Filesystem
storageClassName: fast-rwo
resources:
requests:
storage: 100Gi镜像必须支持非root和目标数据目录权限。数据库复制、选主、配置、Secret、Probe和备份需要额外完整设计。
四十二、PVC Pending Runbook
kubectl get pvc -n data
kubectl describe pvc order-db-data -n data
kubectl get storageclass
kubectl get pv
kubectl get events -n data --sort-by=.metadata.creationTimestamp检查:
- StorageClass是否存在/默认。
- Provisioner名称与CSI Controller是否运行。
- Immediate还是WaitForFirstConsumer。
- 是否有引用PVC的Pod。
- AccessMode/VolumeMode支持。
- 容量和配额。
- Selector是否阻止动态供应。
- Zone/Topology和AllowedTopologies。
- 云API权限、配额和错误。
WaitForFirstConsumer下没有Pod时Pending可能正常。
四十三、Pod Pending的Volume调度故障
kubectl describe pod <pod> -n data常见Event:
- unbound immediate PersistentVolumeClaims。
- volume node affinity conflict。
- no available topology。
- exceeded attach volume limit。
检查Pod Affinity/NodeSelector、PV Node Affinity、StorageClass拓扑和Node每实例最大Attach数。不能只扩CPU Node数量,如果新Node仍在错误Zone或Attach上限已满。
四十四、Multi-Attach Error
RWO云盘仍附加在Node A,却要在Node B挂载:
Multi-Attach error for volume ...原因:
- 旧Pod/Node未正常终止。
- VolumeAttachment状态陈旧。
- 云API Detach慢。
- 强制删除Pod造成控制面与真实设备不一致。
- 两个Workload错误引用同PVC。
检查:
kubectl get volumeattachment
kubectl describe volumeattachment <name>
kubectl get pod -A -o wide
kubectl describe node <node>确认旧进程停止、文件系统已安全卸载后,再按CSI/云厂商Runbook强制Detach。对数据库盲目Detach可能造成写入中断和文件系统损坏。
四十五、FailedAttachVolume
检查:
- CSI Attacher/Controller日志。
- VolumeAttachment状态和错误。
- 云盘是否在同Zone。
- Node实例是否达到Attach上限。
- 云IAM权限和API配额。
- 卷是否被锁定/正在快照或迁移。
- Node ID是否与云实例匹配。
PVC Bound不排除这些问题。
四十六、FailedMount
常见原因:
- CSI Node插件未运行/Socket异常。
- NodeStage/NodePublish失败。
- 文件系统损坏或fsType错误。
- Mount Option不支持。
- NFS/DNS/网络/权限失败。
- Secret/凭据缺失。
- 目标路径冲突。
- fsGroup递归耗时。
证据:
kubectl describe pod <pod> -n data
kubectl get pod -n <csi-namespace> -o wide
kubectl logs -n <csi-namespace> <csi-node-pod> -c <driver-container>CSI Namespace和容器名按平台实际值。不要随意在Node手工mount生产卷,可能与kubelet状态冲突。
四十七、挂载后Permission Denied
检查:
- 容器UID/GID。
- fsGroup和fsGroupChangePolicy。
- 文件系统现有Owner/Mode/ACL。
- NFS root_squash。
- SELinux/AppArmor。
- CSI是否处理Mount Group。
- 应用写入路径是否正确。
先在隔离副本验证权限变更。对TB级卷执行递归chown可能造成长时间不可用。
四十八、Node故障后的卷恢复
流程可能包括:
- Node心跳丢失,Pod状态未知。
- 控制器等待Node/Pod驱逐超时。
- 确认旧Node不会继续写盘(Fencing)。
- Detach旧Node。
- 在同Zone新Node调度替代Pod。
- Attach、Mount和数据库恢复。
- 验证数据一致性和业务。
Fencing是有状态恢复关键:如果旧Node只是网络分区但仍运行并写磁盘,新Node同时挂载/启动会脑裂。存储和应用必须有单写保护。
四十九、Volume满了怎么处理
先判断:
- 数据增长还是日志/临时文件异常。
- inode还是字节容量。
- 删除文件是否仍被进程打开。
- 存储后端是否允许扩容。
- 数据库是否支持在线扩容。
- 备份/快照是否占用配额。
紧急止损可能包括清理可重建临时数据、受控扩容和限流。不要直接删除数据库文件或redo/WAL。
五十、存储监控
对象层:
- PVC Phase/Condition和请求容量。
- PV Phase/ReclaimPolicy。
- VolumeAttachment错误。
- CSI Controller/Node可用性。
- Provision/Attach/Mount/Resize延迟与失败。
数据层:
- 使用量、inode、增长速度。
- IOPS、Throughput、Latency、Queue。
- 错误/重试/Throttle。
- Snapshot/Backup成功与年龄。
- 恢复演练RPO/RTO。
- 存储费用和孤儿卷。
PVC Bound不是健康指标的终点。
五十一、常见误区
- PVC Bound等于可用:后续还有Attach、Stage、Publish和权限。
- RWO等于单Pod:它主要是单Node读写。
- RWX自动解决并发一致性:应用仍需并发协议。
- StorageClass是一块磁盘:它是动态供应模板。
- Pending一定故障:WaitForFirstConsumer可能在等Pod。
- Delete策略一定不安全、Retain一定安全:都需生命周期和备份治理。
- Finalizer等于备份:只保护对象删除顺序。
- 快照等于备份:可能同故障域且只有Crash Consistent。
- 卷扩大后文件系统自动全部可用:可能还需NodeExpand。
- 可以直接缩小PVC:多数场景不支持,需迁移。
- StatefulSet自动提供数据库HA:它只提供身份和PVC。
- 强制Detach能快速修复:未Fencing可能损坏数据。
五十二、面试标准回答
PV、PVC和StorageClass关系
PVC是Namespace内应用的容量、AccessMode、VolumeMode和Class申请;PV是集群级底层卷表示;StorageClass定义Provisioner、参数、Reclaim、扩容和绑定模式。动态供应时External Provisioner调用CSI创建真实卷和PV,再与PVC绑定。
PVC Bound后Pod为什么仍ContainerCreating
Bound只表示PVC与PV绑定。Pod调度后,块盘还要ControllerPublish附加Node、NodeStage准备文件系统、NodePublish挂到Pod路径,再处理权限;任一步都可能FailedAttach/FailedMount,应查VolumeAttachment、Pod Event和CSI Controller/Node日志。
RWO和RWOP区别
RWO是单Node读写,同Node多个Pod在某些场景仍可共享;RWOP才表达集群范围单Pod读写,并要求CSI和Kubernetes版本支持。AccessMode也不是应用并发锁,底层强制能力依Driver。
Immediate和WaitForFirstConsumer区别
Immediate在PVC创建时立即供应,拓扑卷可能先落在错误Zone;WaitForFirstConsumer等待引用PVC的Pod,Scheduler把计算约束和存储Zone一起决策,再在选定拓扑供应卷,PVC在没有消费者时Pending可能正常。
Delete和Retain区别
Delete通常在PVC删除后由Provisioner删除PV和底层卷;Retain保留底层数据并让PV Released,需人工恢复或清理。两者都不是备份:Delete要防误删,Retain要防孤儿盘、费用和数据泄露。
快照为什么不等于备份
快照可能与源卷同系统/账号、可被同凭据删除,且通常只Crash Consistent。备份要独立故障域、不可变/防删除、数据库一致性或PITR,并定期恢复验证;Snapshot Ready不能证明数据库能恢复。
Multi-Attach怎么处理
先确认旧Pod/Node是否仍运行并完成Fencing,再查VolumeAttachment、云盘真实附加和CSI日志;安全卸载后按厂商Runbook Detach并在同拓扑重建。不能直接强制Detach仍在写入的数据库卷,否则可能脑裂或文件系统损坏。
五十三、学习实验与验收
实验一:动态供应
创建PVC,观察Provisioner日志、PV、claimRef、volumeHandle和Bound过程;删除前记录ReclaimPolicy,确认底层卷结果。
实验二:WaitForFirstConsumer
创建WFFC PVC但不创建Pod,观察Pending;再创建带Zone Affinity的Pod,观察选定拓扑、供应和调度。
实验三:Attach/Mount故障
在隔离环境制造错误fsType/Mount Option或停止测试CSI Node插件,保存VolumeAttachment、Event和CSI日志,恢复后验证幂等重试。
实验四:RWO边界
分别在同Node/不同Node引用测试RWO PVC,观察Driver行为;再用支持的RWOP验证单Pod约束。不得对生产数据库实验。
实验五:扩容
把测试PVC从10Gi扩到20Gi,记录ControllerExpand、PV Capacity、PVC Condition、NodeExpand和容器内文件系统容量。
实验六:快照恢复
写入校验数据,创建Snapshot,恢复到隔离PVC,启动只读验证应用并记录RPO/RTO;再说明为什么这仍不等于数据库PITR备份。
验收清单
- 能画出PVC动态供应和CSI组件图。
- 能解释WFFC与Scheduler拓扑。
- 能画出Attach、Stage、Publish全过程。
- 能区分四种AccessMode和两种VolumeMode。
- 能说明emptyDir、hostPath、Local PV和PVC边界。
- 能解释Delete、Retain、Released和Finalizer。
- 能完成扩容并验证文件系统。
- 能区分快照、备份、PITR和复制。
- 能解释StatefulSet PVC保留和强删风险。
- 能排查Pending、Multi-Attach、FailedAttach、FailedMount。
- 能设计容量、性能、权限、加密和监控。
- 能在隔离环境完成真实恢复演练。
