最近在给一套 OpenShift 4.14 集群做持久化存储方案时,碰到一个很实际的选型问题:业务方要跑一个单副本的日志服务,数据写到本地 NVMe 上完全够用,不想为了网络存储额外付出硬件和运维成本,也不追求 Pod 崩溃后在别的节点上恢复。当时我翻了半天 OperatorHub,最后落地的方案就是Local Storage Operator(LSO)。这个方案说穿了并不复杂:把节点上的本地盘通过 Operator 统一发现、统一暴露成 PersistentVolume,交给集群内的 PVC/Pod 使用。但真要在生产环境把它配置得稳、不挖坑,中间有不少细节值得记录。
这篇文章会围绕 OpenShift 使用 Local Storage 展开,讲清楚 LSO 的底层逻辑、部署前的规划要点、从 Operator 安装到 PV 落地的完整操作,以及我在实际排障中积累的典型问题和处理手段。适合正在规划存储方案、或者已经在集群里折腾本地盘但遇到各种奇怪问题的 OpenShift 管理员和 SRE 同学参考。
1. 为什么要在 OpenShift 里使用 Local Storage
1.1 本地存储与网络存储在 Kubernetes 里的本质差异
在 Kubernetes 生态里,我们平时用得最多的是网络存储:Ceph RBD、NFS、云厂商的云盘等等。这类存储的典型特征是数据不跟某个节点绑定,Pod 在任意节点启动后都能挂载同一份数据,天然支持跨节点故障迁移和容量弹性扩展。代价是每一次 I/O 都要经过网络协议栈,延迟、吞吐都受网络环境影响,而且存储系统自身的运维复杂度往往不低。
本地存储(Local Storage)则是把节点上真实存在的磁盘设备直接暴露给 Pod 使用,数据落盘不经过网络,延迟和吞吐表现接近物理设备的极限。比如 NVMe 盘的延迟可以做到几十微秒级别,这在网络存储方案里很难达到。但硬币的另一面是,数据一旦写入本地盘,就跟当前节点牢牢绑定:Pod 漂移后带不走数据,节点故障可能导致数据丢失。如果用一句话概括差异:网络存储买的是灵活性和可用性,本地存储买的是性能和确定性。
下面用一张表直观对比两者:
| 对比维度 | 网络存储 | 本地存储(LSO方案) |
|---|---|---|
| 访问延迟 | 受网络协议栈影响,通常毫秒级 | 接近裸设备性能,微秒到亚毫秒级 |
| 容量扩展 | 可动态扩容 | 不可动态扩容,跟单盘容量绑定 |
| 数据迁移 | Pod 可跨节点挂载 | Pod 与节点绑定,无法平滑迁移 |
| 故障影响 | 存储端提供冗余机制 | 节点磁盘故障可能导致数据丢失 |
| 运维成本 | 需要维护存储集群 | 需要关注设备健康、容量和替换流程 |
| 典型成本 | 较高,含网络和冗余设计 | 较低,直接使用服务器自带盘位 |
1.2 哪些业务场景值得把数据放在本地盘
先泼一盆冷水:不是所有有状态应用都适合本地盘。我见过不少团队把 MySQL 单实例直接丢到本地盘上,磁盘一故障,整条业务线跟着挂,这种用法风险极高。真正适合本地盘的场景,通常具备两个特征之一:要么数据可以丢失或重建,要么应用自身已经解决了高可用。
比较典型的落地场景包括:
- 日志与可观测性组件:比如 Fluentd、Loki、Elasticsearch 的数据节点。日志数据量大、写入频繁,但对丢失容忍度较高,本地盘能高效缓解 I/O 压力。
- 自带副本机制的数据系统:Elasticsearch、Cassandra、MongoDB 副本集,它们会在多个节点之间复制数据,单个节点上的本地盘故障可以由其他副本顶上,这个时候本地盘的高性能优势就能完全发挥出来。
- 缓存型中间件:Redis、Kafka 这类组件如果不追求持久化,或者业务能容忍从上游重建缓存,本地盘是非常合适的选择。
- 临时计算资源:批处理任务、模型推理缓存、数据清洗作业,需要高性能临时读写,对最终一致性不敏感。
反过来,如果你的业务是无副本的单体数据库、没有跨节点冗余的有状态服务,或者需要频繁进行节点维护和滚动升级,那我建议老老实实上网络存储,别拿本地盘赌运气。
1.3 Local Storage Operator 到底解决了什么痛点
在没有 LSO 之前,想让 OpenShift 使用本地盘也有办法,但体验非常原始:要么在节点上手工创建 PV,要么用 hostPath 直接挂目录。前者问题在于 PV 的节点亲和性、设备路径、容量信息全靠手工维护,稍不留神就写错,后续扩容换盘更是噩梦;后者则根本没有 PV 语义,Kubernetes 调度器不会感知节点和容量,Pod 可能被调度到没有对应数据的节点上。
LSO 做的事可以理解成三层:自动发现节点上的可用设备;按用户配置选择合适的设备并以 PV 形式注册到集群;为每个 PV 生成稳定的符号链接和 StorageClass,屏蔽设备名漂移问题。它负责的是把"裸设备"变成"可被 Kubernetes 调度和消费的持久卷",不需要你再去手工写一堆 PV YAML。尤其在生产环境,设备路径会因为 BIOS 识别顺序变化在重启后从 /dev/sdb 变成 /dev/sdc,LSO 默认用 /dev/disk/by-id 这类稳定路径建立符号链接,这个细节能避免很多半夜三更的故障。
2. 动手前的规划与节点准备
2.1 节点、设备命名的规划思路
配置 LSO 之前,第一步不是急着安装 Operator,而是先把节点和设备的规划做清楚。我习惯先在纸上(或者文档里)把下面这些内容确认下来:
- 涉及哪些节点:哪些节点上的本地盘要交给集群使用?建议给这些节点打上明确的标签,例如
storage=local,之后所有 LocalVolumeSet 的 nodeSelector 都通过这个标签选择,而不是直接用主机名。用标签的好处是后续扩容节点时,只需要给新节点打标签,LSO 会自动发现新节点上的满足条件的设备。 - 设备路径怎么写:永远不要用
/dev/sda、/dev/nvme0n1这种内核动态分配的设备名。Linux 在每次重启后扫描设备的总线顺序可能变化,同一块盘今天叫 /dev/sdb,明天可能就变成 /dev/sdc。要使用稳定路径,比如/dev/disk/by-id/下的nvme-eui.xxxxx或wwn-0x....。LSO 本身也会做符号链接,但规划时就统一用 by-id 路径,能少踩很多坑。 - 每块盘的归属和容量:一台机器上如果有多块盘,是都交给集群,还是留一部分给系统本身?LSO 的 LocalVolumeSet 支持用容量范围过滤设备,建议在一开始就根据业务需求定好 minSize 和 maxSize,避免把系统盘或者 RAID 卡虚拟出来的逻辑卷也纳入进来。
2.2 在节点上准备裸盘
规划完之后,需要到节点上确认设备状态。建议使用如下步骤:
- 登录节点,执行
lsblk -o NAME,SIZE,TRAN,TYPE,MOUNTPOINTS查看所有块设备。重点关注 TRAN 字段,NVMe 盘显示 nvme,SATA 盘显示 sata,USB 盘显示 usb。MOUNTPOINTS 列能直接看到设备是否已被挂载。 - 对要交给集群的设备,检查上面是否残留文件系统或分区表:执行
lsblk -f看 FSTYPE 列,如果显示 xfs、ext4 或者存在分区,需要先清理。 - 清理旧数据时执行
wipefs -a /dev/sdX,这会擦除设备的文件系统签名和分区表。如果设备上有需要永久销毁的数据,可以在 wipefs 之后再执行dd if=/dev/zero of=/dev/sdX bs=1M count=100,避免残留超级块信息让后续识别混乱。
这里有一个关键点:不要提前在设备上创建文件系统。本地盘交给 LSO 之后,设备会以裸设备的形式暴露给 PVC,当 Pod 真正调度到该节点时,Kubelet 会根据 PV 的 fsType 配置自动完成格式化。如果手工提前格式化,反而可能在设备发现阶段造成误判,或者与后续挂载参数不一致。
另外,设备上如果还有未卸载的挂载点,LSO 默认不会接管这类设备。所以在准备裸盘时,一定要确认设备没有挂载在任何目录下,可以用mount | grep sdX或findmnt /dev/sdX检查。
2.3 定义 StorageClass 和容量模型
本地存储因为本质上不支持动态扩容,所以在首次规划时就要确定好 StorageClass 的设计。我通常按下面的维度来定义:
- 存储类名称:建议带上介质和文件系统类型,例如
local-nvme-xfs、local-ssd-ext4。名称要能让业务方一眼看出底层是什么盘。 - volumeMode:文件系统模式(Filesystem)适合大多数普通应用,块模式(Block)适合数据库这类希望自行管理文件系统、避免文件系统层开销的场景。
- volumeBindingMode:本地存储必须设置成
WaitForFirstConsumer。如果不设置,PVC 创建后调度器会立刻在集群里挑选一个满足容量的 PV 进行绑定,这时很可能绑定到别的节点的设备上,而真正使用 PVC 的 Pod 却调度不到那个节点,导致 Pod 一直 Pending。设置为 WaitForFirstConsumer 后,PVC 会等 Pod 真正创建时再决定绑定哪个节点上的 PV,调度逻辑会同时考虑 Pod 和 PV 的位置约束。 - reclaimPolicy:强烈建议使用
Retain。本地设备上的数据在 PV 被释放后并不会自动清除,如果回收策略是 Delete,PVC 删除后 PV 会被删掉,但设备上残留的文件系统还会在,下次同一块设备被 LSO 重新发现并创建 PV 后,挂载时可能出现文件系统损坏或数据残留。用 Retain 至少能让管理员明确知道这块 PV 已经释放,需要人工处理设备数据后再删除 PV。
容量模型上,LSO 是每台设备对应一个 PV,不会做磁盘条带化,也没有跨盘聚合能力。比如节点上有 1 块 2TB 的 NVMe,LSO 就创建一个 2TB 的 PV;如果应用只需要 500GB,PVC 声明 500GB 即可,剩下的空间无法被其他 PVC 共享。这是本地存储的天然限制,规划时要让业务理解清楚。
3. 完整部署实操:从 Operator 到 PV 落地
3.1 安装 Local Storage Operator
在 OpenShift 4.x 里安装 Operator 最省事的方式是 Web 控制台的 OperatorHub,搜索 "Local Storage" 就能找到红帽官方维护的 Local Storage Operator。安装时注意目标命名空间必须是openshift-local-storage,这个命名空间是 LSO 约定俗成的部署位置,别改到别的 namespace 下。
如果习惯用命令行,也可以直接用 Subscription 方式安装。下面是一段完整的安装配置,包含 Namespace、OperatorGroup 和 Subscription:
apiVersion: v1 kind: Namespace metadata: name: openshift-local-storage --- apiVersion: operators.coreos.com/v1 kind: OperatorGroup metadata: name: local-storage namespace: openshift-local-storage spec: targetNamespaces: - openshift-local-storage --- apiVersion: operators.coreos.com/v1alpha1 kind: Subscription metadata: name: local-storage-operator namespace: openshift-local-storage spec: channel: stable name: local-storage-operator source: redhat-operators sourceNamespace: openshift-marketplace执行后等待 ClusterServiceVersion 进入 Succeeded 状态,确认相关 Pod 正常运行:
oc get csv -n openshift-local-storage oc get pods -n openshift-local-storage正常情况下你会看到 diskmaker-manager、local-storage-provisioner 相关的 Pod 在运行。安装完成后,LSO 就会开始监听集群里的设备状态。
3.2 使用 LocalVolume 精确指定设备
LSO 提供两种核心 CRD:LocalVolume和LocalVolumeSet。LocalVolume 适合设备路径不多、希望完全手工控制每一条设备路径的场景,比如一台节点上只有两块专用数据盘,你想明确指定就是这两块。
下面是一个基于 LocalVolume 的配置示例:
apiVersion: local.storage.openshift.io/v1 kind: LocalVolume metadata: name: local-disks namespace: openshift-local-storage spec: nodeSelector: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - worker-1.example.com storageClassDevices: - storageClassName: local-xfs volumeMode: Filesystem devicePaths: - /dev/disk/by-id/nvme-eui.xxxxx - /dev/disk/by-id/nvme-eui.yyyyy fsType: xfs这个配置的含义是:在 worker-1.example.com 这台上,将两块 NVMe 设备暴露为一个名为 local-xfs 的 StorageClass,卷模式为文件系统,格式化类型 xfs。注意devicePaths我用的是/dev/disk/by-id/路径,强烈不建议写成/dev/nvme0n1这种。
创建后,等待几十秒到几分钟,LSO 会在集群中为每一块设备创建一个 PV:
oc apply -f localvolume.yaml oc get pv | grep local-xfs3.3 使用 LocalVolumeSet 批量选择设备
LocalVolumeSet 是更推荐的方式,尤其当集群里有多个节点、多个设备时,你不需要逐一列出每个设备的路径,而是通过属性筛选让 LSO 自动发现和纳入。如下配置:
apiVersion: local.storage.openshift.io/v1 kind: LocalVolumeSet metadata: name: local-nvme namespace: openshift-local-storage spec: nodeSelector: nodeSelectorTerms: - matchExpressions: - key: storage operator: In values: - "true" storageClassName: local-nvme volumeMode: Filesystem fsType: xfs deviceInclusionSpec: deviceTypes: - disk deviceMechanicalProperties: - NonRotational minSize: 500Gi maxSize: 4000Gi maxDevices: 4 deviceFilters: - /dev/disk/by-id/nvme-*这里逐项解释一下关键字段:
deviceTypes:可选磁盘类型,常见值是 disk(整块盘)、part(分区)。我用 disk 表示只处理整块裸设备,不处理分区。deviceMechanicalProperties:磁盘机械属性,NonRotational对应 SSD/NVMe,Rotational对应机械盘。判断依据是/sys/block/<device>/queue/rotational,这个值是 0 表示固态。minSize/maxSize:磁盘容量过滤区间,能把系统盘、过小的 USB 盘排除掉。maxDevices:单个节点最多纳入多少设备。这个字段能防止节点上其他未被预期的盘被误纳入。deviceFilters:基于路径的 glob 过滤,比如只匹配nvme-*设备,或者排除dm-*。
这里需要注意,LocalVolumeSet 一旦创建,后续修改deviceInclusionSpec时,LSO 对已创建 PV 的处理策略可能会触发设备的重新发现,修改前建议先查看对应文档和 Operator 的 warning 信息,避免生产环境出现意外重新扫描。
3.4 验证 StorageClass、PV 和 Pod 使用
配置完成后,验证环节不能省。按下面顺序检查:
# 查看自动创建的 StorageClass oc get sc local-nvme # 查看 LSO 创建的本地 PV,PV 名字通常带节点和设备标识 oc get pv | grep local-nvme # 查看任一 PV 的详细信息,重点看 Node Affinity 和 Path oc describe pv <pv-name>PV 的 Node Affinity 会自动指向设备所在节点,这个信息对调度器至关重要。然后创建一个 PVC 验证端到端流程:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: local-nvme-pvc spec: storageClassName: local-nvme accessModes: - ReadWriteOnce resources: requests: storage: 100Gi由于 StorageClass 是 WaitForFirstConsumer,创建 PVC 后它的状态会是 Pending,这很正常。接着创建一个 Pod 来消费这个 PVC:
apiVersion: v1 kind: Pod metadata: name: local-nvme-test spec: containers: - name: app image: registry.access.redhat.com/ubi8/ubi command: ["/bin/sh", "-c", "while true; do sleep 3600; done"] volumeMounts: - name: data mountPath: /data volumes: - name: data persistentVolumeClaim: claimName: local-nvme-pvc创建后观察 Pod 是否正常运行,oc get pod local-nvme-test。如果调度成功,PVC 会从 Pending 变成 Bound,并且绑定到 Pod 所在节点上的那块本地盘。写入测试文件后,在节点上能看到对应挂载点。
3.5 块设备模式与文件系统模式的选择
上面的示例都是基于 Filesystem 模式。如果你的应用想绕开文件系统层,直接在裸块设备上工作,比如某些数据库希望自己管理日志块,可以使用volumeMode: Block。
LocalVolumeSet 的配置只需改一行:
spec: volumeMode: Block对应的 PVC 也要声明 Block:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: local-block-pvc spec: storageClassName: local-nvme-block accessModes: - ReadWriteOnce volumeMode: Block resources: requests: storage: 100GiPod 里使用的方式和文件系统卷完全不同,是通过volumeDevices将块设备直接暴露给容器:
apiVersion: v1 kind: Pod metadata: name: local-block-test spec: containers: - name: app image: registry.access.redhat.com/ubi8/ubi command: ["/bin/sh", "-c", "while true; do sleep 3600; done"] volumeDevices: - name: data devicePath: /dev/xvda volumes: - name: data persistentVolumeClaim: claimName: local-block-pvc块设备模式的问题在于可观测性和运维管理更麻烦,文件系统层的监控、扩容、故障排查工具全部用不上。除非应用确实有特殊需求,多数情况下文件系统模式已经足够。
4. 常见问题与排查技巧实录
4.1 设备一直不被 LSO 发现
如果你确认设备已经在节点上,但 LSO 就是不创建对应的 PV,通常原因在下面几个方向。
首先看节点选择器。LocalVolumeSet 的 nodeSelector 如果匹配不到任何节点,设备自然不会被发现。用oc get nodes --show-labels确认节点标签。其次看设备是否处于可被接管的状态。LSO 的发现逻辑会忽略已经被挂载的设备,执行lsblk -f查看设备的 MOUNTPOINTS 列,如果非空,先umount卸载再试。
还需要注意机械属性匹配。有些服务器上的 SSD 在/sys/block/xxx/queue/rotational下报告为 1(部分 SAS SSD 或 RAID 控制器下的盘会有这种情况),如果 LocalVolumeSet 里写了NonRotational,这类设备就不会被选中。排查时可以直接在节点上执行:
cat /sys/block/nvme0n1/queue/rotational cat /sys/block/sdb/queue/rotational最后再看 LSO 日志。diskmaker-manager负责设备发现和 PV 创建,日志里通常有明确提示。排查命令:
oc logs -n openshift-local-storage deployment/diskmaker-manager oc get localvolumediscovery -n openshift-local-storage如果日志显示设备确实被看到了但被过滤规则排除,对照 LocalVolumeSet 里的 deviceTypes、minSize、deviceFilters 逐项检查。
4.2 PVC 一直 Pending 且 Pod 无法调度
这种问题我遇到得最多,80% 的原因出在 StorageClass 的volumeBindingMode上。如果某个 StorageClass 不是 LSO 创建的,而是你手工仿照写的,很容易漏掉 WaitForFirstConsumer,导致 PVC 创建后在集群范围内提前绑定到某个节点上的 PV。等真正创建 Pod 时,PV 的节点亲和性却把 Pod 绑死在另一个节点,调度总是失败,表现就是 PVC 已经 Bound 但 Pod 一直 Pending。
解决办法:先确认 StorageClass 配置:
oc get sc local-nvme -o yaml如果 volumeBindingMode 是 Immediate,改成 WaitForFirstConsumer:
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-nvme provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer reclaimPolicy: Retain另外,本地盘的 accessModes 几乎都是 ReadWriteOnce,PVC 不能声明 ReadWriteMany。如果你创建了一个 RWX 的 PVC,会发现一直无法绑定。还有一点容易被忽略:如果 PV 所在节点上有污点(taint),Pod 如果没有对应 toleration,也会调度失败,这时oc describe pod会给出明确的调度错误信息。
4.3 删除 PVC 后 PV 一直 Released,无法复用
这是 LSO 场景下管理动作最频繁的一步。因为推荐 reclaimPolicy 使用 Retain,所以每次 PVC 删除后,PV 不会自动消失,而是进入 Released 状态。这个状态下 PV 不能直接复用,因为它还带着上一任 PVC 的数据和挂载语义。
正确的处理流程是:
- 确认已经没有 Pod 使用该 PVC。
- 登录 PV 对应的节点,对底层设备做数据清理,例如
wipefs -a /dev/disk/by-id/xxxxx。 - 删除 PV:
oc delete pv <pv-name>。 - LSO 会重新发现该设备并创建新的 PV。
这里有个小坑:如果你直接删除 PV 而不清理设备数据,LSO 重新发现设备时,由于设备上还有上一轮的文件系统,新 PV 挂载后 Pod 可能会看到旧数据。如果你就是故意想复用旧数据,那没问题;但如果你以为数据已经没了,就会踩到比较严重的"脏数据"坑。
4.4 节点重启后设备路径漂移
设备名漂移是本地盘运维里最典型的问题。容器平台依赖/dev/disk/by-id的稳定符号链接,重启后即使内核给盘分配了不同的 sdX 名称,by-id 路径也不会变。但如果你在早期配置中图省事,直接在 LocalVolume 里写了/dev/sdb、/dev/nvme0n1,重启后很可能出现 PV 指向的设备已经不是原来那块盘的情况。
判断方法:在节点上执行:
ls -l /dev/disk/by-id/ | grep nvme然后对比 PV 里的 path 字段指向的符号链接目标是否还真实存在。如果符号链接已失效,需要确认是否是盘序变更导致。对于 LSO 自动创建的 PV,它会维护自己的 symlink,一般不会出问题;反而是手工创建的 PV 风险最大。
所以,再次强调,所有设备路径一律使用 by-id 或 by-path。这也是我在生产环境强制执行的规范。
4.5 卸载 LSO 后 PV 残留
当你不再需要某个节点的本地存储,准备删除 LocalVolumeSet 或者卸载整个 LSO 时,需要注意 LSO 创建的 PV 不会自动跟着删除。这是很多初用者没想到的。LocalVolumeSet 删除后,已经注册到集群的 PV 仍然存在,PVC 还能继续绑定这些 PV,只是新的发现逻辑停了。
彻底清理的步骤:
- 删除所有使用该 StorageClass 的 PVC 和对应工作负载。
- 删除 PV,如果 PV 处于 Released 状态,先清理设备数据再删。
- 删除 LocalVolumeSet / LocalVolume CR。
- 如果需要卸载 Operator,再删除 Subscription 和 ClusterServiceVersion。
如果 PV 一直处于 Terminating 删不掉,查看它的 finalizers 和是否还有 PVC 引用。常见原因是kubernetes.io/pv-protection这个 finalizer 阻止删除,这时先把引用它的 PVC 删掉,finalizer 会自动移除。不要手动 edit 去掉 finalizer,除非你确认底层已经没有数据访问。
5. 实战经验与避坑总结
5.1 哪些场景我劝你别用本地盘
本地盘虽好,但不是万能。以我这几年的观察,下面几类情况用本地盘会非常痛苦:
- 无应用层冗余的单体数据库:MySQL 单实例、PostgreSQL 单实例,只要节点和磁盘二选一出问题,业务直接中断,数据可能永久丢失。
- 期望滚动升级、节点随意维护的集群:本地盘把数据和节点绑死,节点维护时 Pod 无法漂移,只能先停业务,维护完再恢复。
- 容量需求频繁变化的应用:本地盘的 PV 创建后容量固定,不能在线扩容。业务一增长,可能就要换盘、迁移数据,非常折腾。
- 强调跨可用区高可用的关键链路:本地盘无法跨节点,这在金融级或大型在线业务链路里很难满足可用性要求。
5.2 本地盘管理的小技巧和监控建议
这块我想分享几个亲测有效的细节。第一,给节点打专用标签。在 LocalVolumeSet 的 nodeSelector 里只筛选专用标签的节点,能防止将来新节点加入时被误纳入,也能区分不同用途的存储池。第二,监控设备健康状态。本地盘不在存储集群的可控范围内,磁盘寿命、坏道、温度这些信号需要额外采集。SSD/NVMe 可以通过smartctl、nvme-cli检查健康度和磨损度,建议周期性地把关键指标接入监控告警。第三,容量告警要做在 PVC 侧。本地 PV 满盘后不会自动扩容,需要在 PVC 使用率达到阈值时及时告警,给运维留足时间处理。
还有一个实践细节:LSO 创建的 PV 默认名字可能比较长,和业务对不上号。我建议在命名 StorageClass 时就用业务语义,比如local-mysql-logs、local-kafka-data,同时在 PVC 上写好清晰的名称,这样oc get pv一眼就能看出哪块盘属于哪个业务,排障时效率会高很多。
5.3 后续扩展思路:换盘与新增存储池
本地盘集群的运维不是一锤子买卖,后续一定会遇到换盘或者增加存储池的需求。
换盘的标准流程:先确认应用不再使用该 PVC,删除相关工作负载和 PVC,等待 PV 变成 Released,登录节点确认设备故障状态,拔掉故障盘换上新盘,新盘做裸盘准备,然后删除旧的 PV 对象。如果使用的是 LocalVolumeSet,LSO 会自动发现新盘并创建对应 PV,但容量和卷模式需要和业务预期一致,最好提前用一个测试 PVC 验证。
新增存储池时,建议改造 LocalVolumeSet 或新建一个 LocalVolumeSet,用不同的 StorageClass 名称区分介质类型。比如现在有一个 NVMe 池,想再加一个 SATA SSD 池,就创建第二个 LocalVolumeSet,设置deviceMechanicalProperties: [Rotational]或不同的 deviceFilters,并给对应的节点打上新的标签。这样业务方可以根据不同 StorageClass 选择适合自己的存储类型,互不干扰。
最后说点个人经验。其实 LSO 本身的坑不算多,最让人上头的是维护节奏。本地盘一旦绑定业务,节点就不能随便重启和替换。我在生产上吃过一次亏:当时没有用 by-id,直接在 LocalVolume 里写了/dev/nvme0n1,机器重启后盘符变了,PV 的 symlink 指到了另一个空盘,幸好提前关了自动挂载才没把数据搞坏。从那以后,我所有本地盘配置一律用/dev/disk/by-id,StorageClass 一律 WaitForFirstConsumer + Retain,节点也打了严格标签。这套组合用了很久都很省心,希望这篇记录也能帮你少踩几个坑。