课程目录(第 20 章 / 共 33 章)
课程/进阶:生产实践

生产存储:CSI、Rook/Ceph 与本地盘方案

20 章 / 共 33·22 分钟·进阶存储CSIRook快照

从 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 ControllerDeployment(1 副本或主备)创建/删除卷、附加/分离卷、扩容、打快照
CSI NodeDaemonSet(每节点一个)在节点上把卷格式化并挂载到 Pod
external-provisionerController 的 sidecar监听 PVC,触发建卷
external-attacherController 的 sidecar监听 VolumeAttachment,触发附加
external-resizerController 的 sidecar处理 PVC 扩容请求
external-snapshotterController 的 sidecar处理 VolumeSnapshot
node-driver-registrarNode 的 sidecar把驱动注册到 kubelet

把 sidecar 理解成「Kubernetes 的通用逻辑」:它们监听 K8s 的对象变化,翻译成 CSI 的标准调用,交给驱动自己的容器去执行。所以驱动厂商只需要实现存储相关的部分,不用重写一遍控制器。

一次挂载的完整流程是这样的:你创建 PVC → provisioner 调 CreateVolume 造出后端卷并生成 PV → Pod 被调度到某个节点 → attacher 调 ControllerPublishVolume 把卷附加到该节点 → 该节点的 kubelet 通过 CSI Node 插件调 NodeStageVolumeNodePublishVolume 完成挂载。把这条链路画出来,谁在什么位置做什么就一目了然了:

text
一次「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 是「卷的模板」。它有几个字段决定了你以后能不能扩容、会不会删数据、调度时怎么选节点。

yaml
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-provisionerLonghorn
数据副本1 份,只在一台节点默认 3 副本,分散到不同节点
节点故障Pod 挂不上其他节点,数据可能丢卷可在别的节点挂载,自动重建副本
前置依赖每个节点装 iscsid、留独立数据盘
适合缓存、可重建数据、自带三副本的数据库要 RWO 持久卷又不想运维 Ceph 的中小集群

选型逻辑就一句:数据丢了能不能重建。缓存和中间结果用 local-path;必须活下来、又不想养 Ceph 就用 Longhorn(代价是写要复制到 3 个节点,延迟更高)。注意 Longhorn 解决的是节点故障,不解决机柜或机房级故障——副本仍可能受同一台交换机影响。

bash
# 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-pathprovisionerrancher.io/local-pathvolumeBindingModeWaitForFirstConsumer数据只在那一台节点上:节点下线,Pod 就调度不到别处,除非手工搬数据。所以它适合缓存、临时数据,或者应用层自己做复制(比如三副本的数据库)。

参考:reference/k8s-in-action/storage/local-storage/README.mdreference/k8s-in-action/storage/longhorn/README.md

NFS 路线适合已有 NAS 的场景,用 csi-driver-nfs 接入,优点是多个 Pod 能同时挂载(RWX),缺点是单台 NFS Server 就是单点,自建 NFS 只建议用在小规模或开发环境:

bash
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-nfs

Rook/Ceph:角色、部署与 day-2 运维

要在自建机房里得到「多副本的块存储」,Rook/Ceph 是最常见的开源答案。Rook 是运维 Ceph 的 Operator,它把 Ceph 的组件都变成了 Kubernetes 里的工作负载。先认识几个角色:

角色作用数量要求
MON维护集群地图与仲裁,是 Ceph 的大脑奇数个,3 起
MGR监控、指标与 Dashboard主备,通常 2
OSD真正存数据的进程,每块盘一个至少 3 个
MDS / RGWCephFS 元数据服务 / S3 对象网关用到对应能力时才需要

部署前必须满足两个前提,没有例外:至少 3 台机器(因为默认故障域是 host,副本数 3 意味着数据落在 3 台不同机器上),以及每台机器至少一块独立磁盘(不要和系统盘共用)。有条件的话再给存储集群一条独立网络用于数据复制。

安装顺序是先 Operator、再 CephCluster:

bash
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 数量:

bash
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 tree

ceph -sHEALTH_OKmon 有 3 个、osd 数量与磁盘数量一致,才算真正可用。

别在生产上用单副本

CephBlockPoolreplicated.size 设成 1 看起来省磁盘,实际含义是「同一份数据只放一块盘」。任何一块盘坏掉就丢数据,同时该 Pool 上的卷会全部不可用。生产至少 size: 3,并且配合 failureDomain: host 保证副本落在不同机器上。

day-2:扩容、换盘与健康检查

集群上线只是开始,生产上真正花时间的是加盘、换坏盘、盯健康状态。装个运维插件就能把 ceph 命令挂到 kubectl 下,不用每次 exec 进 toolbox:

bash
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 -sHEALTH_OKHEALTH_WARN 必须先定位原因
容量与均衡ceph osd df tree单 OSD 使用率低于 80%,各 OSD 利用率接近
数据分布ceph pg statPG 全部 active+clean,没有 undersized
恢复进度ceph -srecovery还有数字说明仍在重建,别继续下线节点

扩容分两种:加节点(新机器打上 storage 标签即可,空盘会被自动加入)、加盘(插空盘自动加入;如果 cephClusterSpec 里写死了 devices 路径,要手工补上)。扩容后 Ceph 会自己 rebalance,这一步会吃满磁盘与网络 IO,所以要安排在业务低峰,并盯 ceph -srecovery 行直到结束。

换坏盘的顺序不能反:先让 operator 停手,再把 OSD 标成 down,最后才 purge,否则 OSD 会被自动拉回来。

bash
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 tree

purge-osd 会连同该 OSD 的 CRUSH 条目、认证信息与数据一起清掉,数据只能靠其余副本重建,所以同一时间只换一块盘,等 ceph -s 恢复干净再换下一块。整机下线默认 30 分钟后才开始 recovery,短时间重启不用手工干预。磁盘被旧集群占用而加不进 OSD 时,先按 Rook 的 teardown 流程抹盘(sgdisk --zap-all,SSD 用 blkdiscard),再让 operator 重新发现。

参考:reference/k8s-in-action/storage/rook/README.mdreference/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>

yaml
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}
bash
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.mdreference/k8s-in-action/storage/cephadm/5-deploy-rgw.md

扩容、快照与性能验证

扩容的前提是 StorageClass 打开了 allowVolumeExpansion: true。满足后,改 PVC 的申请容量即可,注意只能变大不能变小;部分驱动还需要重启 Pod 才能完成文件系统扩容,describe 的 Events 里会有提示。

bash
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 打快照:

yaml
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 必须兼容

yaml
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建一个)。顺序不能反,每一步都要先看判据再往下走。

  1. 扩容并确认生效,kubectl get pvcCAPACITY 变了才算成功:
bash
kubectl -n demo patch pvc data --type merge -p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'
kubectl -n demo get pvc data
  1. 把上面的 VolumeSnapshotClassVolumeSnapshot 存成 snapshot.yaml 后应用,再应用恢复 PVC(存成 restored-pvc.yaml)。driver 必须是 kubectl get csidrivers 里真实存在的名字:
bash
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
  1. data-restored 挂进一个 Pod 读一遍——能挂上不等于数据对,要对比快照时刻的文件列表或校验和。另外记住:不要在源 PVC 上直接回滚,恢复总是新建一块卷,确认无误后再把应用切过去。

性能验证不能靠感觉。先起一个挂着该 PVC 的调试 Pod(镜像 debian:12-slimcommandsh -c "apt-get update && apt-get install -y fio && sleep 3600",卷挂到 /data),再跑一轮 4k 随机读和 1M 顺序写:

bash
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,压测要挑业务低峰——共享存储被打满时,受害的是同一后端上的所有租户:

bash
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,别无其他:

bash
kubectl get pv -o custom-columns=NAME:.metadata.name,POLICY:.spec.persistentVolumeReclaimPolicy,STATUS:.status.phase

Delete 会连同后端卷一起销毁;Retain 只是把 PV 变成 Released,数据还在,需要管理员手工清理或重新绑定。生产上给数据库用的 StorageClass,建议显式设成 `Retain`,用磁盘换一次后悔的机会。

还需要注意两点:StatefulSet 的 PVC 模板默认不会随 Pod 删除而删除,这是保护机制,但删 StatefulSet 时要记得手工清理;快照不等于备份,它和源卷往往在同一个存储集群里,存储集群整体故障时两者一起没了。真正的备份要落到另一套介质上,可以用 Velero 做资源加卷的备份,也可以让数据库自己做逻辑导出。

常见坑与排错

存储故障的判断有个好用的分界线:PVC 侧的问题看 CSI Controller,Pod 侧的问题看 CSI Node 和 kubelet。下面这张表按现象给出对照。

现象原因怎么确认怎么办
PVC 一直 PendingStorageClass 不存在或名字拼错、WaitForFirstConsumer 在等 Pod 调度、后端容量不足kubectl get storageclasskubectl describe pvc 看 Events修名字、先建 Pod 触发绑定、联系存储侧扩容
Pod 卡在 ContainerCreating卷还没附加或挂载失败、RWO 卷被别的节点占用kubectl describe pod 的 Events、CSI Node 插件与 kubelet 日志检查是否有旧 Pod 占着卷、后端是否可达
扩容后 CAPACITY 没变StorageClass 没开 allowVolumeExpansion,或驱动要求重启 Pod 完成文件系统扩容kubectl get storageclass -o yamlkubectl describe pvc确认前提后重启 Pod;不支持扩容只能新建更大的 PVC 再迁数据
VolumeSnapshot 一直不是 true没装 external-snapshotter,或驱动不支持快照kubectl get volumesnapshotclasskubectl describe volumesnapshot装 snapshot CRD 与控制器,或换支持快照的驱动
删了 PVC 数据也没了PV 的 reclaimPolicyDeletekubectl get pvRECLAIMPOLICY数据库类业务事前改成 Retain,并另做备份
ceph -s 显示 HEALTH_WARNHEALTH_ERROSD 掉线、副本不足、磁盘将满ceph osd treeceph 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 的 provisionerparametersreclaimPolicyallowVolumeExpansionvolumeBindingMode 决定了后端行为与后续可操作性。
  • 本地盘最快但绑节点:local-path 只有一份数据,Longhorn 在节点本地盘上做多副本,代价是延迟与依赖 iscsid
  • Rook/Ceph 需要至少 3 台机器和独立磁盘,生产副本数不低于 3;day-2 的三件事是扩容、换盘、盯 ceph -sceph osd df
  • Ceph 除了 RBD 与 CephFS,还能用 RGW 提供 S3 接口;扩容看 allowVolumeExpansion,快照看 external-snapshotter 与驱动支持。
  • 删除 PVC 前,先看回收策略,再确认备份。

练习

  1. kubectl get storageclass -o yaml 找出你集群里每个 StorageClass 的 reclaimPolicyallowVolumeExpansion,判断哪些能安全扩容、哪些删了会丢数据。
  2. data 这个 PVC 从 10Gi 扩到 20Gi,观察 kubectl get pvcCAPACITY 与 Pod 是否需要重启。
  3. 假设你要部署一个有状态数据库,说明你会选 local-path、Longhorn 还是 Ceph RBD,以及为什么。

PVC 的入门用法在第 10 章 存储卷与 PV/PVC,集群侧磁盘怎么规划(etcd 独占盘、数据盘拆分)在第 18 章 生产集群部署。存储是否真的在健康工作,最终要靠指标和告警来盯——见第 21 章 可观测性

数据能可靠地存下来了,可你怎么知道它跑得好不好?下一章我们看可观测性:指标、日志与告警。