课程目录(第 20 章 / 共 33 章)
生产存储:CSI、Rook/Ceph 与本地盘方案
从 PVC 往下一层看:CSI 怎么把存储接进集群,本地盘、块存储、文件存储怎么选,以及扩容、快照、性能验证与数据安全怎么做。
学完这一章,你将能够
- ✓说得出 CSI 的 Controller 与 Node 插件分别负责什么
- ✓从访问模式、性能、可用性三个维度为业务选对存储路线
- ✓会判断 PVC 扩容与快照的前提条件是否满足
- ✓知道删除 PVC 前必须确认哪些事
从 PVC 到真实存储:还差哪一层
前面学过 PVC:你写一份申请,Kubernetes 给你一块卷。但「卷」到底存在哪里?是一块云硬盘、一台 Ceph 集群,还是某台机器上的一块 NVMe?PVC 本身不回答这个问题。答案是 StorageClass 加 CSI 驱动。StorageClass 说「用哪个后端、什么参数」,CSI 驱动负责真的去后端把卷造出来并挂到节点上。理解这条链路之后,「PVC 一直 Pending」「Pod 卡在 ContainerCreating」这类问题才有下手的地方。
CSI 的架构:Controller 与 Node 插件
CSI(Container Storage Interface)是一个标准接口。它解决的问题是:Kubernetes 不可能把每家存储厂商的代码都内置进核心代码里,于是定义一套 RPC,厂商按这套接口实现驱动,Kubernetes 只负责调用。
一个 CSI 驱动通常由两部分组成,运行位置和职责完全不同。
| 组件 | 部署形态 | 负责什么 |
|---|---|---|
| CSI Controller | Deployment(1 副本或主备) | 创建/删除卷、附加/分离卷、扩容、打快照 |
| CSI Node | DaemonSet(每节点一个) | 在节点上把卷格式化并挂载到 Pod |
| external-provisioner | Controller 的 sidecar | 监听 PVC,触发建卷 |
| external-attacher | Controller 的 sidecar | 监听 VolumeAttachment,触发附加 |
| external-resizer | Controller 的 sidecar | 处理 PVC 扩容请求 |
| external-snapshotter | Controller 的 sidecar | 处理 VolumeSnapshot |
| node-driver-registrar | Node 的 sidecar | 把驱动注册到 kubelet |
把 sidecar 理解成「Kubernetes 的通用逻辑」:它们监听 K8s 的对象变化,翻译成 CSI 的标准调用,交给驱动自己的容器去执行。所以驱动厂商只需要实现存储相关的部分,不用重写一遍控制器。
一次挂载的完整流程是这样的:你创建 PVC → provisioner 调 CreateVolume 造出后端卷并生成 PV → Pod 被调度到某个节点 → attacher 调 ControllerPublishVolume 把卷附加到该节点 → 该节点的 kubelet 通过 CSI Node 插件调 NodeStageVolume 与 NodePublishVolume 完成挂载。把这条链路画出来,谁在什么位置做什么就一目了然了:
一次「PVC 变成挂载好的目录」的完整链路
业务侧 控制面(Deployment) 存储后端
─────── ──────────────────── ────────
kubectl apply PVC ──▶ external-provisioner ──CreateVolume──▶ 造出后端卷
│
└─ 写入 PV 对象 ──▶ PV 与 PVC 绑定(Bound)
Pod 调度到 node-2 ──▶ external-attacher ──ControllerPublishVolume──▶ 卷附加到 node-2
│
node-2 上(DaemonSet) │
────────────────────── │
node-driver-registrar ──注册驱动──▶ kubelet │
kubelet ──NodeStageVolume / NodePublishVolume─────────────────────────┘
└─▶ 格式化 + 挂载到 /var/lib/kubelet/pods/<pod>/volumes/...
PV 生命周期:Pending ──▶ Bound ──▶ Released ──▶(Delete 销毁 / Retain 保留)──▶ Failed扩容走 external-resizer,快照走 external-snapshotter,它们的位置和 provisioner 一样在控制面,只是负责的动作不同。所以 PVC 的问题看 Controller 侧日志,挂载的问题看 Node 侧日志——这条分界线能省掉大量排查时间。
排错从这里开始
PVC 停在 Pending,去看 provisioner 容器的日志;Pod 停在 ContainerCreating,先 kubectl describe pod 看 Events,再去看 CSI Node 插件和 kubelet 的日志。位置对了,问题基本就定位一半。
StorageClass 参数如何决定后端行为
StorageClass 是「卷的模板」。它有几个字段决定了你以后能不能扩容、会不会删数据、调度时怎么选节点。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ceph-rbd-ssd
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
clusterID: rook-ceph
pool: replicapool
imageFeatures: layering
csi.storage.k8s.io/fstype: ext4
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer| 字段 | 作用 | 注意点 |
|---|---|---|
provisioner | 用哪个 CSI 驱动 | 名字写错,PVC 直接 Pending |
parameters | 驱动的私有参数 | 由驱动定义,如池名、文件系统类型、副本数 |
reclaimPolicy | 删除 PVC 后怎么处理底层卷 | Delete 连数据一起删,Retain 保留待人工处理 |
allowVolumeExpansion | 是否允许扩容 | 不写就永远不能扩 |
volumeBindingMode | 什么时候绑定卷 | WaitForFirstConsumer 会等 Pod 调度后再建卷,避免跨可用区 |
volumeBindingMode: WaitForFirstConsumer 对网络存储几乎总是正确选择:先决定 Pod 落在哪,再在那个区域创建卷,否则会出现「卷在 A 区、Pod 在 B 区,挂不上」。
三条典型路线:本地盘、块存储、文件存储
生产上的存储方案可以粗分成三类,差别集中在数据放哪、能不能共享、挂了会怎样。
| 路线 | 代表实现 | 数据位置 | 访问模式 | 性能 | 可用性 | 适合 |
|---|---|---|---|---|---|---|
| 本地盘 | local-path-provisioner、local-storage | 节点本地磁盘 | RWO,绑定节点 | 最高 | 节点挂了数据不可用 | 缓存、可重建数据、自带 HA 的数据库 |
| 网络块存储 | Rook/Ceph RBD、云硬盘 CSI | 远端块设备 | RWO | 中,受网络限制 | 高,多副本 | 数据库、有状态服务 |
| 文件存储 | NFS CSI、CephFS | 远端共享目录 | RWX,多 Pod 共享 | 中低 | 取决于后端 | 共享文件、CI 缓存、多副本同时读 |
本地盘怎么选:local-path 还是 Longhorn
两者都「用节点本地盘」,但可用性差一个量级:local-path 只是一个建目录的 provisioner,Longhorn 会在多个节点的本地盘上存副本,节点故障后卷仍能挂载并自动补副本。
| 维度 | local-path-provisioner | Longhorn |
|---|---|---|
| 数据副本 | 1 份,只在一台节点 | 默认 3 副本,分散到不同节点 |
| 节点故障 | Pod 挂不上其他节点,数据可能丢 | 卷可在别的节点挂载,自动重建副本 |
| 前置依赖 | 无 | 每个节点装 iscsid、留独立数据盘 |
| 适合 | 缓存、可重建数据、自带三副本的数据库 | 要 RWO 持久卷又不想运维 Ceph 的中小集群 |
选型逻辑就一句:数据丢了能不能重建。缓存和中间结果用 local-path;必须活下来、又不想养 Ceph 就用 Longhorn(代价是写要复制到 3 个节点,延迟更高)。注意 Longhorn 解决的是节点故障,不解决机柜或机房级故障——副本仍可能受同一台交换机影响。
# local-path:一个 StorageClass 开箱即用,版本号按最新 release 替换
kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.30/deploy/local-path-storage.yaml
kubectl -n local-path-storage rollout status deploy/local-path-provisioner
# Longhorn:装完后在 UI 或 StorageClass 里调 numberOfReplicas
helm repo add longhorn https://charts.longhorn.io && helm repo update
helm install longhorn longhorn/longhorn -n longhorn-system --create-namespace
kubectl -n longhorn-system get pods它给的 StorageClass 叫 local-path,provisioner 是 rancher.io/local-path,volumeBindingMode 是 WaitForFirstConsumer。数据只在那一台节点上:节点下线,Pod 就调度不到别处,除非手工搬数据。所以它适合缓存、临时数据,或者应用层自己做复制(比如三副本的数据库)。
参考:reference/k8s-in-action/storage/local-storage/README.md、reference/k8s-in-action/storage/longhorn/README.md
NFS 路线适合已有 NAS 的场景,用 csi-driver-nfs 接入,优点是多个 Pod 能同时挂载(RWX),缺点是单台 NFS Server 就是单点,自建 NFS 只建议用在小规模或开发环境:
helm repo add csi-driver-nfs https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/charts
helm install csi-driver-nfs csi-driver-nfs/csi-driver-nfs -n kube-system --create-namespace
kubectl -n kube-system get pods -l app.kubernetes.io/name=csi-driver-nfsRook/Ceph:角色、部署与 day-2 运维
要在自建机房里得到「多副本的块存储」,Rook/Ceph 是最常见的开源答案。Rook 是运维 Ceph 的 Operator,它把 Ceph 的组件都变成了 Kubernetes 里的工作负载。先认识几个角色:
| 角色 | 作用 | 数量要求 |
|---|---|---|
| MON | 维护集群地图与仲裁,是 Ceph 的大脑 | 奇数个,3 起 |
| MGR | 监控、指标与 Dashboard | 主备,通常 2 |
| OSD | 真正存数据的进程,每块盘一个 | 至少 3 个 |
| MDS / RGW | CephFS 元数据服务 / S3 对象网关 | 用到对应能力时才需要 |
部署前必须满足两个前提,没有例外:至少 3 台机器(因为默认故障域是 host,副本数 3 意味着数据落在 3 台不同机器上),以及每台机器至少一块独立磁盘(不要和系统盘共用)。有条件的话再给存储集群一条独立网络用于数据复制。
安装顺序是先 Operator、再 CephCluster:
helm repo add rook-release https://charts.rook.io/release && helm repo update
helm install --create-namespace -n rook-ceph rook-ceph rook-release/rook-ceph
kubectl -n rook-ceph rollout status deploy/rook-ceph-operator
kubectl label node cn-10-0-1-1 node-role.kubernetes.io/storage=true # 存储节点打标签
helm install -n rook-ceph rook-ceph-cluster rook-release/rook-ceph-cluster \
--set operatorNamespace=rook-ceph不要直接照抄默认值:生产上要在 values 里写清 cephClusterSpec 的节点选择、用哪些磁盘、以及副本数。集群起来之后,还需要创建 CephBlockPool 和指向 provisioner: rook-ceph.rbd.csi.ceph.com 的 StorageClass,业务才能真正用上(样例见本章前面)。验证要看的是「OSD 有没有都起来」,而不是 Pod 数量:
kubectl -n rook-ceph get cephcluster
kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph -s
kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph osd treeceph -s 里 HEALTH_OK、mon 有 3 个、osd 数量与磁盘数量一致,才算真正可用。
别在生产上用单副本
把 CephBlockPool 的 replicated.size 设成 1 看起来省磁盘,实际含义是「同一份数据只放一块盘」。任何一块盘坏掉就丢数据,同时该 Pool 上的卷会全部不可用。生产至少 size: 3,并且配合 failureDomain: host 保证副本落在不同机器上。
day-2:扩容、换盘与健康检查
集群上线只是开始,生产上真正花时间的是加盘、换坏盘、盯健康状态。装个运维插件就能把 ceph 命令挂到 kubectl 下,不用每次 exec 进 toolbox:
kubectl krew install rook-ceph
kubectl rook-ceph ceph -s
kubectl rook-ceph ceph health detail # 出现 HEALTH_WARN 时先看这里,再决定动不动手
kubectl rook-ceph ceph osd df tree # 单 OSD 使用率长期高于 80% 就该扩容了| 要看什么 | 命令 | 判据 |
|---|---|---|
| 集群健康 | ceph -s | HEALTH_OK;HEALTH_WARN 必须先定位原因 |
| 容量与均衡 | ceph osd df tree | 单 OSD 使用率低于 80%,各 OSD 利用率接近 |
| 数据分布 | ceph pg stat | PG 全部 active+clean,没有 undersized |
| 恢复进度 | ceph -s 的 recovery 行 | 还有数字说明仍在重建,别继续下线节点 |
扩容分两种:加节点(新机器打上 storage 标签即可,空盘会被自动加入)、加盘(插空盘自动加入;如果 cephClusterSpec 里写死了 devices 路径,要手工补上)。扩容后 Ceph 会自己 rebalance,这一步会吃满磁盘与网络 IO,所以要安排在业务低峰,并盯 ceph -s 的 recovery 行直到结束。
换坏盘的顺序不能反:先让 operator 停手,再把 OSD 标成 down,最后才 purge,否则 OSD 会被自动拉回来。
kubectl -n rook-ceph scale deployment rook-ceph-operator --replicas=0
kubectl -n rook-ceph scale deployment rook-ceph-osd-3 --replicas=0
kubectl -n rook-ceph exec -it deploy/rook-ceph-tools -- ceph osd down osd.3
kubectl rook-ceph rook purge-osd 3 --force # 只有 down 状态的 OSD 才允许删除
# 物理换盘,新盘留空即可自动加入
kubectl -n rook-ceph scale deployment rook-ceph-operator --replicas=2
kubectl rook-ceph ceph osd treepurge-osd 会连同该 OSD 的 CRUSH 条目、认证信息与数据一起清掉,数据只能靠其余副本重建,所以同一时间只换一块盘,等 ceph -s 恢复干净再换下一块。整机下线默认 30 分钟后才开始 recovery,短时间重启不用手工干预。磁盘被旧集群占用而加不进 OSD 时,先按 Rook 的 teardown 流程抹盘(sgdisk --zap-all,SSD 用 blkdiscard),再让 operator 重新发现。
参考:reference/k8s-in-action/storage/rook/README.md、reference/k8s-in-action/storage/rook/day-2.md
RGW:在 Ceph 上再提供一层 S3
RBD 给块、CephFS 给文件,第三个能力是对象网关 RGW:对外提供 S3 兼容接口,应用用 SDK 直连,不经过 PVC。Rook 用 CephObjectStore 声明,gateway 至少 2 个实例,元数据池用 SSD 副本池、数据池可以用纠删码省盘。AK/SK 来自 CephObjectStoreUser 生成的 Secret,名字形如 rook-ceph-object-user-<store>-<user>:
apiVersion: ceph.rook.io/v1
kind: CephObjectStore
metadata:
name: rgw
namespace: rook-ceph
spec:
metadataPool:
replicated: {size: 3}
dataPool:
erasureCoded: {dataChunks: 4, codingChunks: 2}
gateway: {port: 80, instances: 2}kubectl -n rook-ceph get cephobjectstore rgw # 等 phase 变成 Ready
kubectl -n rook-ceph get secret | grep object-user # 每个对象用户一个 Secret
SEC=rook-ceph-object-user-rgw-rgw-default-user
kubectl -n rook-ceph get secret $SEC -o jsonpath='{.data.AccessKey}' | base64 -d; echo
kubectl -n rook-ceph get secret $SEC -o jsonpath='{.data.SecretKey}' | base64 -d; echo能取到 AK/SK 才算可用。生产上别省两件事:每个业务一套用户与桶,不要所有应用共用 default 用户(一把钥匙开所有桶,泄露就是全丢);桶的容量与生命周期策略要写进配置,否则对象只增不减。应用侧怎么用 S3 SDK、为什么不该给对象存储挂 PVC,见文件与对象存储。
参考:reference/k8s-in-action/storage/rook/rgw/README.md、reference/k8s-in-action/storage/cephadm/5-deploy-rgw.md
扩容、快照与性能验证
扩容的前提是 StorageClass 打开了 allowVolumeExpansion: true。满足后,改 PVC 的申请容量即可,注意只能变大不能变小;部分驱动还需要重启 Pod 才能完成文件系统扩容,describe 的 Events 里会有提示。
kubectl -n demo patch pvc data --type merge \
-p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
kubectl -n demo get pvc data快照需要两个前提:集群装了 external-snapshotter(提供 VolumeSnapshotClass、VolumeSnapshot 等 CRD),以及 CSI 驱动支持快照。先建一个类,再对 PVC 打快照:
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: ceph-rbd-snapshot
driver: rook-ceph.rbd.csi.ceph.com
deletionPolicy: Delete
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: data-snapshot
namespace: demo
spec:
volumeSnapshotClassName: ceph-rbd-snapshot
source:
persistentVolumeClaimName: data用快照恢复时,新建的 PVC 通过 dataSource 指向它,容量不能小于快照、StorageClass 必须兼容:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-restored
namespace: demo
spec:
storageClassName: ceph-rbd-ssd
dataSource:
name: data-snapshot
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
resources: {requests: {storage: 20Gi}}动手练习:扩容 → 快照 → 从快照恢复
前提是 demo 命名空间里有一个已 Bound 的 PVC(本章示例叫 data,没有的话先按第 10 章 存储卷与 PV/PVC建一个)。顺序不能反,每一步都要先看判据再往下走。
- 扩容并确认生效,
kubectl get pvc的CAPACITY变了才算成功:
kubectl -n demo patch pvc data --type merge -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
kubectl -n demo get pvc data- 把上面的
VolumeSnapshotClass与VolumeSnapshot存成snapshot.yaml后应用,再应用恢复 PVC(存成restored-pvc.yaml)。driver必须是kubectl get csidrivers里真实存在的名字:
kubectl -n demo apply -f snapshot.yaml
kubectl -n demo get volumesnapshot -w # READYTOUSE 变成 true 才能拿去恢复
kubectl -n demo apply -f restored-pvc.yaml
kubectl -n demo get pvc data-restored # 必须变成 Bound- 把
data-restored挂进一个 Pod 读一遍——能挂上不等于数据对,要对比快照时刻的文件列表或校验和。另外记住:不要在源 PVC 上直接回滚,恢复总是新建一块卷,确认无误后再把应用切过去。
性能验证不能靠感觉。先起一个挂着该 PVC 的调试 Pod(镜像 debian:12-slim,command 用 sh -c "apt-get update && apt-get install -y fio && sleep 3600",卷挂到 /data),再跑一轮 4k 随机读和 1M 顺序写:
kubectl -n demo exec -it fio-test -- fio --name=randread --ioengine=libaio --direct=1 \
--rw=randread --bs=4k --iodepth=32 --numjobs=4 --runtime=60 --time_based \
--group_reporting --filename=/data/testfile --size=4G只看三处:IOPS=(随机读的核心数字,本地 NVMe 十万级,Ceph RBD 三副本通常几千到几万,取决于磁盘、网络与副本数)、BW=(带宽)、clat percentiles 里的 99.00th(长尾,才是用户感知到的卡顿)。判据不是绝对值,而是和同一后端的裸盘基线比:如果 RBD 随机读只有裸盘的十分之一,先查网络和 OSD 是否过载,而不是怀疑业务。--direct=1 绕过页缓存,--iodepth 模拟并发深度。裸设备压测可以用 elbencho,-t 经验取值是设备数乘以 4,压测要挑业务低峰——共享存储被打满时,受害的是同一后端上的所有租户:
elbencho -w -b 4M -t 48 --direct -s 100g /dev/nvme{0..11}n1
iostat -xm 1参考:reference/k8s-in-action/storage/elbencho/README.md
数据安全:回收策略与备份
删除 PVC 到底会不会删数据,只取决于 PV 的 reclaimPolicy,别无其他:
kubectl get pv -o custom-columns=NAME:.metadata.name,POLICY:.spec.persistentVolumeReclaimPolicy,STATUS:.status.phaseDelete 会连同后端卷一起销毁;Retain 只是把 PV 变成 Released,数据还在,需要管理员手工清理或重新绑定。生产上给数据库用的 StorageClass,建议显式设成 `Retain`,用磁盘换一次后悔的机会。
还需要注意两点:StatefulSet 的 PVC 模板默认不会随 Pod 删除而删除,这是保护机制,但删 StatefulSet 时要记得手工清理;快照不等于备份,它和源卷往往在同一个存储集群里,存储集群整体故障时两者一起没了。真正的备份要落到另一套介质上,可以用 Velero 做资源加卷的备份,也可以让数据库自己做逻辑导出。
常见坑与排错
存储故障的判断有个好用的分界线:PVC 侧的问题看 CSI Controller,Pod 侧的问题看 CSI Node 和 kubelet。下面这张表按现象给出对照。
| 现象 | 原因 | 怎么确认 | 怎么办 |
|---|---|---|---|
PVC 一直 Pending | StorageClass 不存在或名字拼错、WaitForFirstConsumer 在等 Pod 调度、后端容量不足 | kubectl get storageclass、kubectl describe pvc 看 Events | 修名字、先建 Pod 触发绑定、联系存储侧扩容 |
Pod 卡在 ContainerCreating | 卷还没附加或挂载失败、RWO 卷被别的节点占用 | kubectl describe pod 的 Events、CSI Node 插件与 kubelet 日志 | 检查是否有旧 Pod 占着卷、后端是否可达 |
扩容后 CAPACITY 没变 | StorageClass 没开 allowVolumeExpansion,或驱动要求重启 Pod 完成文件系统扩容 | kubectl get storageclass -o yaml、kubectl describe pvc | 确认前提后重启 Pod;不支持扩容只能新建更大的 PVC 再迁数据 |
VolumeSnapshot 一直不是 true | 没装 external-snapshotter,或驱动不支持快照 | kubectl get volumesnapshotclass、kubectl describe volumesnapshot | 装 snapshot CRD 与控制器,或换支持快照的驱动 |
| 删了 PVC 数据也没了 | PV 的 reclaimPolicy 是 Delete | kubectl get pv 看 RECLAIMPOLICY 列 | 数据库类业务事前改成 Retain,并另做备份 |
ceph -s 显示 HEALTH_WARN 或 HEALTH_ERR | OSD 掉线、副本不足、磁盘将满 | ceph osd tree、ceph health detail | 先补回 OSD,再按提示处理 PG 与容量 |
| 新磁盘加不进 OSD | 磁盘上还有旧集群残留、cephClusterSpec 写死了设备名 | kubectl -n rook-ceph logs <osd-prepare-pod> 里出现 belonging to a different ceph cluster | 先抹盘(sgdisk --zap-all)或设 cleanupPolicy.wipeDevicesFromOtherClusters,再重启 operator 触发重新发现 |
两个最贵的教训
- 单副本 Ceph 不可用:
size: 1的 Pool 一旦有 OSD 下线,整个 Pool 的数据就无法读取,且没有自愈能力。 - 删了 PVC 丢数据:
reclaimPolicy: Delete下,删除 PVC 等于同时删除后端卷;删之前先看策略,再确认备份。
自测题
自测:为什么 Pod 还没建,PVC 就一直是 `Pending`?(点击展开答案)
因为这类 StorageClass 用的是 volumeBindingMode: WaitForFirstConsumer,它故意不在 PVC 创建时就建卷,而是等 Pod 调度完成、知道了 Pod 落在哪个节点或可用区,才去建卷并绑定。这样做的原因是:卷往往有区域属性,如果先建卷再调度,就可能出现「卷在 A 区、Pod 只能落在 B 区」的死局。所以 Pending 在这种模式下是正常状态,建个 Pod 用上这个 PVC,它就会变成 Bound。当然,如果是 Immediate 模式还一直 Pending,那就是 provisioner、容量或参数的问题了。
自测:为什么 `Retain` 之后删掉 PVC,新建的 PVC 绑不上那块 PV?(点击展开答案)
Retain 的语义是「保留数据,但不再自动管理」。删除 PVC 时,PV 不会销毁,而是从 Bound 变成 Released,并且 PV 上还留着 claimRef 指向那个已经不存在的 PVC。Kubernetes 不会把一个 Released 的 PV 自动分配给新的 PVC——这是刻意的保护,避免你以为数据被清了、实际上新 PVC 直接复用了旧数据。要复用必须由管理员人工介入:先确认数据不需要了(或已备份),再删掉 PV 上的 claimRef 让它回到 Available,新的 PVC 才可能绑上。
自测:为什么换坏盘时要先把 operator 缩到 0,而不是直接删 OSD?(点击展开答案)
因为 Rook 的 operator 会持续「纠正」集群状态:它发现某个 OSD 的 deployment 不存在或副本数不对,就会重新创建并把那块盘拉回集群。如果你在 operator 还在跑的时候直接 purge-osd,很可能刚删完它又被自动加回来,磁盘上残留的元数据还会导致新盘加不进集群。正确顺序是先让 operator 停手,再把 OSD 标 down,然后 purge,最后恢复 operator——也就是先拿掉「自动纠偏」,再做不可逆操作。
小结
- PVC 背后是 StorageClass 加 CSI 驱动,CSI 分成 Controller(管卷)与 Node(管挂载)两部分,靠 sidecar 与 Kubernetes 通信。
- StorageClass 的
provisioner、parameters、reclaimPolicy、allowVolumeExpansion、volumeBindingMode决定了后端行为与后续可操作性。 - 本地盘最快但绑节点:local-path 只有一份数据,Longhorn 在节点本地盘上做多副本,代价是延迟与依赖
iscsid。 - Rook/Ceph 需要至少 3 台机器和独立磁盘,生产副本数不低于 3;day-2 的三件事是扩容、换盘、盯
ceph -s与ceph osd df。 - Ceph 除了 RBD 与 CephFS,还能用 RGW 提供 S3 接口;扩容看
allowVolumeExpansion,快照看 external-snapshotter 与驱动支持。 - 删除 PVC 前,先看回收策略,再确认备份。
练习
- 用
kubectl get storageclass -o yaml找出你集群里每个 StorageClass 的reclaimPolicy与allowVolumeExpansion,判断哪些能安全扩容、哪些删了会丢数据。 - 把
data这个 PVC 从 10Gi 扩到 20Gi,观察kubectl get pvc的CAPACITY与 Pod 是否需要重启。 - 假设你要部署一个有状态数据库,说明你会选 local-path、Longhorn 还是 Ceph RBD,以及为什么。
PVC 的入门用法在第 10 章 存储卷与 PV/PVC,集群侧磁盘怎么规划(etcd 独占盘、数据盘拆分)在第 18 章 生产集群部署。存储是否真的在健康工作,最终要靠指标和告警来盯——见第 21 章 可观测性。
数据能可靠地存下来了,可你怎么知道它跑得好不好?下一章我们看可观测性:指标、日志与告警。