第 10 章 存储¶
配套实验手册:《Kubernetes 实验手册》(manual/ 目录)实验 08「实现基本存储」(4 个 Lab:卷基础/hostPath 应用/PV 静态绑定/StorageClass 动态交付——StorageClass 在 Lab 4 才安装,与本章讲解顺序一致)。本章讲"应用数据放哪、怎么持久化"——从 Pod 级卷到集群级 PV/PVC 再到自动化的 StorageClass,理解存储的抽象层次。
学习目标¶
学完本章,你应该能够:
- 解释容器存储的痛点(文件系统临时性)与三种卷类型(emptyDir/hostPath/配置卷)的适用边界
- 解释 PV/PVC 解耦设计的价值(应用声明需求、管理员提供资源)
- 描述 PV/PVC 的生命周期(Provision/Bind/Use/Reclaim)与访问模式、回收策略
- 区分静态绑定(手动建 PV)与动态供应(StorageClass 自动建)
- 解释 StorageClass 的机制(provisioner/默认类/绑定模式/回收策略)
- 说出 local-path 的本质与局限(单节点),理解为什么多副本共享存储需要 NFS/云盘
- 为有状态应用做出存储选型决策
10.1 存储问题全景¶
10.1.1 容器文件系统为什么"靠不住"¶
第 1 章讲过镜像分层:容器运行时的写入发生在可写层——容器删除,可写层一起消失。这意味着:
- Pod 重建 → 容器内文件全部丢失
- Pod 被调度到其他节点 → 原节点上的数据够不着
10.1.2 三个存储需求¶
- 持久化:数据跨 Pod 生命周期存活(删了重建数据还在)
- 共享:Pod 内多个容器共享数据(sidecar 读主容器日志)
- 解耦:应用不关心存储底层细节(本地盘/网络盘/云盘都一样用)
Kubernetes 用层层抽象回答这三个需求:卷 → PV/PVC → StorageClass。
10.2 卷(Volume):Pod 内的存储抽象¶
卷(Volume) 是 Pod 级的概念:声明在 Pod 里,挂载进容器,生命周期与 Pod 一致。常用类型:
emptyDir:Pod 内的临时共享盘
- 创建时为空目录,Pod 存在期间数据都在(容器重启不丢,Pod 删除即清空)
- 典型用途:Pod 内容器间共享(sidecar 读主容器日志)、临时缓存
- 注意:Pod 被调度到别的节点 = 新 Pod = 新 emptyDir(数据不迁移)
hostPath:宿主机目录(单节点绑定)
- 直接挂宿主机的一个目录(如
/data/mysql) - 数据在节点磁盘上——Pod 删了数据还在,但只在该节点:Pod 漂移到其他节点就找不到数据了
- 典型用途:单节点实验、需要读宿主机文件的系统组件(如 kubelet 自身)
configMap/secret 卷:第 8 章的配置注入(键变文件)——本质也是卷(只读配置卷)。
卷的边界认知:emptyDir 和 hostPath 都绑定节点——它们是"单机思维"的存储。多节点集群里要"数据跟着应用走",需要集群级抽象(PV/PVC)。
10.3 PV 与 PVC:存储解耦(核心)¶
10.3.1 为什么需要两层¶
直接让应用指定"用哪个宿主机目录"(hostPath)的问题:
- 应用 yaml 里写死了存储细节(路径/机器)——换存储就要改应用
- 管理员想统一管理存储资源(哪些盘可用、多大、怎么回收)没有抓手
解耦设计(与 RBAC 的 Subject/Binding 思想同源):
PV(PersistentVolume)——集群级资源:一块"已就绪的存储"
· 由管理员创建(或 StorageClass 自动创建)
· 描述:容量、访问模式、回收策略、底层实现(hostPath/NFS/云盘)
PVC(PersistentVolumeClaim)——命名空间级请求:应用"要一块存储"
· 由应用声明:需要多大、什么访问模式
· 应用只写 PVC,不写底层细节
绑定:PVC 匹配到满足条件的 PV → 状态 Bound → 应用挂载 PVC 使用
10.3.2 PV:存储资源的"货架"¶
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysqldata-pv
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain # 回收策略
hostPath: # 底层实现(也可以是 nfs/云盘 CSI)
path: /data/mysql
访问模式(Access Modes):一块 PV 能被几个节点/几个 Pod 同时用
| 模式 | 含义 |
|---|---|
ReadWriteOnce(RWO) |
单节点读写(数据库标配) |
ReadOnlyMany(ROX) |
多节点只读 |
ReadWriteMany(RWX) |
多节点读写(共享存储如 NFS 才支持) |
回收策略(Reclaim Policy):PVC 删除后,PV 怎么处理
| 策略 | 行为 |
|---|---|
Retain |
保留数据(PV 变 Released,管理员手动处理——数据安全) |
Delete |
自动删除底层存储(云盘等可自动删) |
Recycle(已废弃) |
清理后复用 |
核心认知:访问模式是"底层存储能力"的约束——hostPath 只能 RWO;NFS 支持 RWX。应用声明 PVC 时选的模式必须 PV 支持。
10.3.3 PVC:应用的存储请求¶
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysqldata
spec:
accessModes:
- ReadWriteOnce
storageClassName: "" # 禁用动态供应(§10.4),强制匹配手动 PV
resources:
requests:
storage: 5Gi
匹配规则(静态绑定时):容量 ≥ 请求 && 访问模式匹配 &&(storageClassName 匹配)→ 一个 PV 绑定一个 PVC(一对一)。
⚠️ 实测易错点:集群里存在默认 StorageClass 时,PVC 不写
storageClassName会走动态供应(不匹配手动 PV)——要强制静态绑定必须写storageClassName(实验 08 Lab 3 实测修正)。
10.3.4 生命周期¶
Provision(供应)→ Bind(绑定)→ Use(使用)→ Reclaim(回收)
管理员建 PV / PVC 匹配 PV Pod 挂载 PVC PVC 删除 →
动态供应自动建 状态 Bound 写数据 按回收策略处理 PV
状态流转:
PV: Available(待绑定)→ Bound(已绑定)→ Released(PVC 删了,Retain 后)→ Available/删除
PVC: Pending(等待匹配)→ Bound(匹配成功)→ 删除
排障关联:PVC 一直
Pending→ 看 Events:Pending(没有匹配的 PV)——检查 PV 是否存在/容量/访问模式/SC 是否一致(实验 10 Lab 1 三板斧)。
10.3.5 静态绑定 vs 动态供应¶
| 静态绑定 | 动态供应 | |
|---|---|---|
| 谁建 PV | 管理员手动建 | StorageClass 自动建 |
| 适用 | 存储资源固定/要精细控制 | 大量 PVC、云环境自动扩 |
| 成本 | 每个 PV 都要手工 | 声明即用 |
10.4 StorageClass:动态供应(自动化)¶
10.4.1 为什么需要¶
静态绑定的问题:每个应用都要管理员先手动建好 PV——应用多了根本忙不过来,且 PV 容量是死的(应用要 8G 但只建了 5G 的 PV?)。
StorageClass 让"建 PV"自动化:PVC 声明 storageClassName,provisioner(供应商)自动创建匹配的 PV 并绑定——声明即用。
10.4.2 机制¶
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-path
provisioner: rancher.io/local-path # 谁负责自动建 PV(本课程:本地目录方案)
reclaimPolicy: Delete # 动态供应的 PV 默认回收策略
volumeBindingMode: WaitForFirstConsumer # 绑定时机
PVC(storageClassName: local-path)
│
▼ provisioner 收到请求 → 自动创建 PV(底层:在节点本地目录建文件夹)
│
▼ 自动绑定 → PVC Bound → Pod 挂载使用
默认 StorageClass:storageclass.kubernetes.io/is-default-class: "true" 注解标记默认类——PVC 不写 storageClassName 时自动用它(本课程教学顺序:实验 08 Lab 4 才安装,见下)。
10.4.3 绑定模式(VolumeBindingMode)¶
- Immediate:PVC 创建就绑定(PV 立即建好)——可能建在与 Pod 无关的节点上
- WaitForFirstConsumer:等第一个 Pod 调度后再绑定——PV 建在 Pod 所在节点(local-path 这类"节点本地存储"必须用这个:提前绑定可能建在别的节点,Pod 调度过来时数据在别处)
10.4.4 回收策略¶
动态供应的 PV 回收策略跟随 StorageClass(reclaimPolicy: Delete 常见)——PVC 删除 = 数据删除(local-path 会删掉本地目录)。要保留数据:临时改 PV 的回收策略为 Retain 或用备份。
10.4.5 PVC 在线扩容(Volume Expansion)¶
问题:磁盘满了怎么办?生产刚需——在线扩容(不重建 Pod):
# StorageClass 开启扩容能力
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-path
provisioner: rancher.io/local-path
allowVolumeExpansion: true # 关键:声明支持扩容
# 扩容操作:改 PVC 的 storage 请求(大一点)
kubectl patch pvc mysqldata -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'
- 前提:StorageClass 的
allowVolumeExpansion: true(未开启则 PVC 的 storage 不可改) - 生效:底层存储支持在线扩容时应用无感(local-path 支持);部分存储需要 Pod 重启
- 只能扩不能缩(缩减有数据风险,K8s 不支持)
10.4.6 Volume Snapshots:存储快照与恢复(数据保护)¶
VolumeSnapshot(CSI 快照)给 PVC 打"时间点快照"——数据保护的又一手段(比拷贝文件更一致、更快):
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: mysqldata-snap
spec:
volumeSnapshotClassName: <存储支持的快照类>
source:
persistentVolumeClaimName: mysqldata # 给哪个 PVC 打快照
- 用途:备份前的快速一致快照、数据库迁移、测试环境克隆
- 恢复:从快照创建新 PVC(
VolumeSnapshotContent→ 新 PVC 的VolumeSnapshotContent) - 前提:底层存储(CSI 驱动)支持快照——local-path 不支持;云盘/NFS 类支持
- 与第 14 章 etcd 快照的区别:etcd 快照保"集群状态"、VolumeSnapshot 保"应用数据"——两者互补(Velero 灾备就是组合使用,第 14 章)
教学顺序说明(本课程设计):StorageClass 是"讲到概念再安装"的典型——实验 08 Lab 4 才安装 local-path(安装阶段不装,第 3 章 §3.8 验收清单里它是"延迟项")。学完 10.3(静态)再学 10.4(动态),对比最清晰。
10.5 存储方案选型:本地 vs 共享 vs 云盘¶
10.5.1 local-path:本地方案的本质与局限¶
本课程用的 local-path:PV 就是节点上的一个本地目录(/opt/local-path-provisioner/<pvc名>)。
- 优点:零成本、快(本地盘)、教学演示动态供应足够
- 局限(必须理解):
- 单节点:数据只在创建它的节点上——Pod 漂移/扩容到其他节点 → 数据够不着
- 多副本挂同一 PVC(RWX)不支持:local-path 只能 RWO——第 5 章多副本共享 PVC 的场景它做不了
- 节点故障 = 数据风险:本地盘没有冗余
这正是实验 11(WordPress 综合演练)里"多副本共享 PVC"受限的原因——水平扩展的前提是存储可共享。
10.5.2 共享存储(NFS/云盘):多节点可访问¶
| 方案 | 特点 | 适用 |
|---|---|---|
| NFS | 网络文件系统,一台服务器导出目录,所有节点挂载(支持 RWX) | 自建环境的多副本共享存储(教学扩展首选) |
| 云盘(云厂商 CSI) | 云盘挂到节点,一般 RWO;对象存储 OSS/S3 天然共享 | 云环境生产 |
| 分布式存储(Ceph/Longhorn) | 软件定义存储,强一致/多副本 | 大规模生产 |
选型逻辑(决策树):
flowchart TD
Q1{"应用需要\n持久存储?"}
Q1 -- "否(缓存/临时)" --> EMP["emptyDir / 不挂"]
Q1 -- "是" --> Q2{"多副本\n要共享?"}
Q2 -- "是" --> NFS["NFS / 对象存储\n(RWX 共享)"]
Q2 -- "否" --> Q3{"生产环境?"}
Q3 -- "否(单节点实验)" --> LP["hostPath / local-path"]
Q3 -- "是" --> Q4{"核心数据库?"}
Q4 -- "否" --> DISK["云盘 CSI\n(RWO)"]
Q4 -- "是" --> CEPH["分布式存储\n云盘 + 备份"]
style EMP fill:#F5F5F5,stroke:#666666
style NFS fill:#E8F8E8,stroke:#5BA85B
style LP fill:#E8F4FD,stroke:#4A90D9
style DISK fill:#E8F4FD,stroke:#4A90D9
style CEPH fill:#FFF3E0,stroke:#E08A3C
读图要点:判断顺序:持久与否 → 是否共享 → 是否生产 → 是否核心——"共享"与"生产"是两条最关键的岔路:要共享必须 RWX 方案(local-path 不行)、生产必须可托底的方案(云盘/分布式)。
10.5.3 CSI:标准接口¶
CSI(Container Storage Interface):Kubernetes 与存储厂商之间的标准接口(类似第 3 章的 CRI)——任何存储(云盘/NFS/Ceph)实现 CSI 就能被 K8s 动态供应。生产里"装一个 CSI 驱动"就是接入了某类存储。
分布式存储实例:Rook-Ceph——Ceph(分布式存储,自带多副本/自愈)通过 Rook(K8s 的 Operator,第 18 章展望的模式)部署进集群,对外以 CSI 驱动提供动态供应(StorageClass)。"K8s 里跑一个软件定义存储集群"是自建环境生产存储的常见选择(比 NFS 单点更可靠,比云盘更可控)。知道这个部署模式即可(实施属于进阶)。
10.6 实验演练指引¶
本章机制对应实验 08「实现基本存储」(4 个 Lab,顺序与本章一致):
- Lab 1 卷基础(hostPath/emptyDir):Pod 级卷的挂载与生命周期——emptyDir 随 Pod 消失、hostPath 绑定节点
- Lab 2 hostPath 应用:mysql 数据写到宿主机目录(应用级持久化)
- Lab 3 使用 PVC 和 PV:手动建 PV + PVC 静态绑定(
storageClassName: ""实测修正点) - Lab 4 使用存储类动态交付:安装 local-path + PVC 声明即用(WaitForFirstConsumer 观察)
教学建议:Lab 3 与 Lab 4 对比 = 静态 vs 动态(§10.3.5 的表格亲手验证);Lab 4 安装 local-path 时观察"PV 自动生成、目录自动创建"。
本章小结¶
- 卷(Pod 级):emptyDir(临时共享)/hostPath(宿主机目录,绑节点)/配置卷(CM/Secret)——单机思维
- PV/PVC(集群级):PV=管理员提供的存储资源(容量/访问模式/回收策略),PVC=应用的存储请求,绑定一对一——解耦"提供"与"使用"
- 生命周期:Provision → Bind → Use → Reclaim;访问模式(RWO/ROX/RWX)是底层能力约束;回收策略(Retain 保数据/Delete 自动删)
- StorageClass(自动化):provisioner 自动建 PV——声明即用;默认类(不写 SC 就用它);WaitForFirstConsumer 是节点本地存储的必需
- 选型:local-path 单节点(局限:Pod 漂移/多副本共享都不行)→ NFS/云盘(共享)→ CSI 生态(生产标准);多副本共享的前提是存储可共享
- 本课程教学顺序:StorageClass 在实验 08 Lab 4 才安装(讲概念再动手)
衔接:第 11 章讲安全(认证/授权)——存储的权限控制(谁能用哪个 PVC/Secret)也是安全的一部分;第 18 章综合演练里 WordPress 的持久化(PVC + local-path)就是本章知识的落地。
思考题¶
- 容器内写的文件,Pod 删除后还在吗?emptyDir 和 hostPath 的数据分别在什么情况下会丢?
- PV 与 PVC 各自是谁创建的?为什么应用只写 PVC 不写 PV?
- PVC 一直 Pending,可能的原因有哪些(至少三个)?怎么排查(提示:describe 看 Events)?
- local-path 的 PV 为什么必须是 WaitForFirstConsumer 绑定?
- 一个 3 副本应用要共享同一个 PVC,local-path 行吗?应该用什么方案?
- 为什么说"水平扩展的前提是存储可共享"?(结合第 5 章 StatefulSet 与本章 local-path)
CKA 考点标注(对应域 4:存储 10%): - 必考操作:
kubectl create -f pv.yaml/pvc.yaml、kubectl create -f pv.yaml/pvc.yaml、kubectl create -f pv.yaml/pvc.yaml(看绑定/事件) - 必考机制:PV/PVC 绑定与生命周期、访问模式、回收策略、StorageClass(provisioner/默认类/绑定模式) - 高频场景题:给应用配持久化(静态 vs 动态)、PVC 排障(Pending → 匹配条件)、多副本共享存储选型 - 排障关联(域 5):PVC Pending(Events 报错)、Pod 挂载失败(FailedMount)