☰
OpenShift Local Storage实战:用本地磁盘构建持久化存储方案
2026/10/7 3:01:29 网站建设 项目流程

1. 项目概述

1.1 核心需求解析

先说清楚一件事:OpenShift里的Local Storage并不是什么高大上的新功能,它是Kubernetes生态中原生能力的一种延伸,解决的问题非常具体——让集群里节点上的本地磁盘变成持久化存储,给有状态应用使用。

我在实际交付过的项目里,最常见的使用场景有三类:

  • 数据库类应用(PostgreSQL、MongoDB、Redis)需要低延迟的本地盘,不想走网络存储
  • 大数据组件(Kafka、Elasticsearch、ZooKeeper)对IOPS要求高,而且本身就是分布式的,不太依赖共享存储
  • 中间件和缓存服务,数据量可控,但希望有独立的持久卷管理,不想用hostPath裸奔

说白了,Local Storage就是一层更安全的hostPath。它解决的核心痛点在于:hostPath把节点上的目录直接暴露给Pod,但完全没有生命周期管理,Pod迁移后数据路径不一致、没有容量告警、没有磁盘分配约束,运维起来非常痛苦。Local Storage通过OpenShift的Local Storage Operator把“节点磁盘”变成“标准PVC”,让应用可以用正常声明式的方式消费节点本地盘。

1.2 适用人群与前置条件

这篇内容主要写给这几种人:

  • OpenShift集群管理员,正在规划有状态应用存储方案
  • 应用开发或运维人员,需要为中间件类应用申请本地持久化存储
  • 正在从裸Kubernetes迁移到OpenShift平台的团队,想了解OpenShift在存储这块和原生Kubernetes的差异

在开始之前,你需要具备的基础能力包括:了解OpenShift的基本集群管理操作(oc命令、集群节点查看)、对PersistentVolume/PersistentVolumeClaim/Pod调度的概念有基本认知、能处理YAML资源文件。

需要前置确认的条件有几个:

  • 集群节点上确实有未使用的磁盘,或者存在空闲分区
  • 磁盘是节点本地挂载的(非网络存储),路径清晰可预期
  • 你对节点上磁盘的用途有明确规划——哪些盘给系统用,哪些盘给容器数据用

如果这些条件不满足,Local Storage方案在落地上会很别扭。

1.3 概念范畴与术语说明

在进入实操前,先把几个名称理清楚,避免后面被绕晕:

术语含义
LocalVolumeOpenShift Local Storage Operator管理的自定义资源,描述“哪些节点上的哪些磁盘要变成PV”
LocalVolumeSetLocalVolume的增强形态,可以用字符串模式自动匹配设备路径,不用一个个指定
StorageClass标准Kubernetes接口,OpenShift会把LocalVolume绑定的PV关联到一个StorageClass上,应用通过PVC来消费
hostPathKubernetes原生卷类型,直接把节点目录映射给Pod,没有生命周期管理
Local PersistentVolume标识为local的PersistentVolume,通常由静态Provisioner创建

简单理解:Local Storage Operator是一个控制循环,它监听LocalVolume/LocalVolumeSet的定义,自动在节点上发现磁盘、创建PV、关联StorageClass。你只需要声明磁盘位置和数据目录,剩下的它来。


2. 整体设计与方案选型

2.1 为什么OpenShift需要独立的Local Storage方案

在OpenShift里,很多团队一开始会选择直接用hostPath或者emptyDir来跑有状态应用,图省事。我接触的实际项目中,这么干的人不少,但他们最后都会被几个问题找上门:

首先是容量失控。hostPath没有配额,应用在里面写多少都不受控,磁盘满了整个节点直接进入NotReady状态。这个故障我见过不止一次,中招的团队最后都是靠迁移节点才能恢复。

其次是调度问题。常规的PV调度是“先有存储,再绑定Pod”,但hostPath根本没有这个机制,Pod走了目录还在,数据没有统一管理。OpenShift虽然有nodeSelector可以辅助,但本质上是人工在管。

第三是多节点一致性。你在应用里写死了节点路径,其他节点没有这个目录或没有对应磁盘,Pod一申警就翻车。数据写在某个特定节点上,Pod被调度到别的节点就全完蛋。

Local Storage方案从根本上解决这些问题:PV由Operator统一发现并注册到集群,PVC会触发节点亲和性(nodeAffinity)自动选择有对应磁盘的节点,回收策略可控,容量可见。

2.2 Local Storage Operator vs 手动创建PV

手动创建Local PV的方式是存在的,比如你在Kubernetes里手工写PV的YAML,设置local类型和nodeAffinity,在OpenShift里也能用。但我不建议这么干,原因很实际:

对比维度Local Storage Operator手动创建PV
磁盘发现自动扫描节点上指定的磁盘路径手工指定,节点多了容易漏
生命周期管理PV由Operator统一管理,删除LocalVolume即回收手工作业,删漏了容易留下孤儿PV
存储类绑定自动创建并提供StorageClass需要手工配置和关联
多节点扩展加节点后只要label匹配,自动发现每个节点的PV都需要手工创建
故障恢复PV状态异常时Operator会重新同步没有自动化,出了问题只能手动处理

手动创建PV在测试环境、单节点环境下其实够用,但一旦上了生产环境,有一个真实节点群,你就体会到自动化的价值了。

2.3 为什么用块设备而不是直接给目录

Local Storage有一个容易踩坑的点:配置LocalVolume时,到底是给“磁盘路径”还是给“目录路径”?

我推荐的做法是:在节点上把物理磁盘(或分区)格式化好,挂载到一个独立目录,然后在LocalVolume里指定这个挂载点路径。举例说,节点上有/dev/sdb,你把它格式化成xfs并挂载到/mnt/local-storage,然后在LocalVolume的path字段写/mnt/local-storage。

这样做的好处是:PV的容量就是整个磁盘的容量,容量语义清晰;磁盘满了不会拖垮系统盘;重新挂载和替换盘也比较干净。

如果直接把一个系统盘下的大目录(比如/var/lib/data)交给LocalVolume,问题就很大:系统盘空间被容器数据吃光,节点告警信息基本都是红海。所以务必用独立磁盘挂载路径。

注意:LocalVolume定义里的path字段,是节点上已经存在并挂载好的目录路径,不是设备路径。很多第一次接触的人以为可以写/dev/sdb,这是错的。


3. 部署流程与核心配置

3.1 环境准备与节点规划

先说我的一个典型环境,供参考:

  • OpenShift版本:4.10+
  • 三个master节点,两个worker节点
  • worker节点各自有独立磁盘:/dev/sdb(未分区,1TB)、/dev/sdc(未分区,500GB)
  • 计划把/dev/sdb格式化为一个数据盘挂载到/mnt/disk1,用LocalVolumeSet自动匹配/dev/sdb这种类型的设备路径

在部署前,需要先确认节点上磁盘状态。在OpenShift节点上执行:

# 查看节点上的块设备 lsblk # 确认磁盘未被使用 df -hT /mnt

3.2 安装Local Storage Operator

在OpenShift 4.x中,Operator通常通过OperatorHub安装。这里给出核心过程:

  1. 在OpenShift Web Console中,进入Operators -> OperatorHub
  2. 搜索“Local Storage Operator”
  3. 选择安装,我一般把安装模式选为“All namespaces on the cluster”,命名空间选择openshift-local-storage
  4. 更新策略选手动或自动都行,生产环境我建议手动,踩过地球的坑

命令行方式也可以做,如果你是全命令行工作流,可以这样干:

# 创建命名空间 oc create namespace openshift-local-storage # 创建OperatorGroup cat <<EOF | oc apply -f - apiVersion: operators.coreos.com/v1 kind: OperatorGroup metadata: name: local-storage-operator-group namespace: openshift-local-storage spec: targetNamespaces: - openshift-local-storage EOF # 创建Subscription cat <<EOF | oc apply -f - 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 EOF

安装完成后,检查Operator的Pod状态:

oc get pods -n openshift-local-storage

3.3 配置LocalVolumeSet

LocalVolumeSet的用法我比较推荐,它可以通过设备路径的字符串模式批量匹配磁盘。下面是示例YAML:

apiVersion: local.storage.openshift.io/v1 kind: LocalVolumeSet metadata: name: local-disks namespace: openshift-local-storage spec: nodeSelector: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - worker-0 - worker-1 storageClassDevices: - storageClassName: "local-sc" volumeMode: Filesystem devicePaths: - /dev/sdb tolerations: - key: node.ocs.openshift.io/storage operator: Exists volumeMode: Filesystem

重要参数解释:

  • nodeSelector:限定哪些节点参与本地存储。别省,否则每个节点都会被扫一遍
  • devicePaths:匹配设备路径。可以写一个路径,也可以写通配符模式
  • storageClassName:这个LocalVolumeSet关联的StorageClass名称
  • volumeMode:Filesystem或Block。数据库和高IO应用用Block,普通文件存储用Filesystem
  • tolerations:如果你的节点有污点,需要加容忍,否则PV创建不了

创建命令:

oc apply -f localvolumeset.yaml

创建后,Operator会自动检查节点上的磁盘状态,并为每个满足条件的节点创建Local PV。

3.4 创建StorageClass

LocalVolumeSet创建后,Operator会自动创建一个StorageClass,名称就是你在storageClassDevices里写的那样。通常不需要手工创建。但如果你需要更细的控制,比如设置回收策略,可以手工创建,需要注意provisioner是kubernetes.io/no-provisioner,这是因为Local PV是静态供应,动态供应的意义不大:

apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: local-sc provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer reclaimPolicy: Delete

其中volumeBindingMode: WaitForFirstConsumer是Local Storage的关键参数。它意味着PV和PVC的绑定会延迟到第一个Pod被调度时,确保Pod被调度到有对应本地磁盘的节点,而不是先绑定了再发现节点不匹配。这个特性是Local Storage能正常工作的核心逻辑,没有它,Pod会被调度到一个没有对应磁盘的节点上,然后一直ContainerCreating。

3.5 验证PV与PVC联动

创建完成后,核验一下:

# 查看PV列表 oc get pv # 查看PV详情 oc describe pv <pv-name>

你应当看到类似下面的信息:

Name: local-pv-xxxx StorageClass: local-sc Status: Available Capacity: 100Gi Access Modes: RWO PersistentVolume Reclaim Policy: Delete Node Affinity: Required Terms: Term 0: kubernetes.io/hostname in [worker-0]

注意看Node Affinity字段,它自动带上了节点选择器,这就是你应用PVC时,Pod会被正确调度到worker-0或worker-1的关键。

如果PV没有出现,先用下面命令查看Operator轮询的状态:

oc get localvolumeset -n openshift-local-storage oc describe localvolumeset local-disks -n openshift-local-storage

最常见的状态是Discovering或Failed,可以根据事件里的报错定位(通常是设备路径写错、磁盘已经被占用等)。


4. 应用接入与实测记录

4.1 声明PVC

现在模拟一个应用场景:部署一个单节点PostgreSQL,数据存在本地磁盘上。

首先创建PVC:

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: postgres-data namespace: default spec: accessModes: - ReadWriteOnce storageClassName: local-sc resources: requests: storage: 80Gi

注意这里的storageClassName必须和LocalVolumeSet里配置的一致。accessModes必须是ReadWriteOnce,因为本地盘只能被单个节点读写。

4.2 部署测试应用

部署一个简单Pod来验证PVC绑定和调度:

apiVersion: v1 kind: Pod metadata: name: test-local-pod namespace: default spec: containers: - name: app image: registry.access.redhat.com/ubi8/ubi-minimal:latest command: ["/bin/sh", "-c"] args: - while true; do echo "$(date) writing to local storage" >> /mnt/data/test.log; sleep 5; done volumeMounts: - name: data mountPath: /mnt/data volumes: - name: data persistentVolumeClaim: claimName: postgres-data

创建后,观察Pod调度情况:

oc get pod test-local-pod -o wide

你会看到Pod被调度到了有对应PV的节点上。这是因为PVC绑定的是Local PV,而Local PV自带nodeAffinity,调度器会根据它选择节点。

进入Pod验证数据写入:

oc exec -it test-local-pod -- tail -f /mnt/data/test.log

看到日志在持续写入,PV和Pod联动正常。

4.3 验证Pod重建后的数据持久性

删除这个测试Pod,再重新创建,数据应该还在:

oc delete pod test-local-pod oc apply -f test-local-pod.yaml oc exec -it test-local-pod -- tail -5 /mnt/data/test.log

数据依然存在。这个验证很关键,它说明应用Pod无论怎么重建,只要PVC还在,数据就不会丢。

4.4 Block模式配置对比

如果应用需要的是裸块设备(比如某些数据库需要独占磁盘块),配置稍有不同。LocalVolumeSet部分修改如下:

spec: storageClassDevices: - storageClassName: "local-block-sc" volumeMode: Block devicePaths: - /dev/sdc volumeMode: Block

然后应用侧挂载方式也不是传统的文件路径,而是设备路径:

volumes: - name: data persistentVolumeClaim: claimName: block-pvc

容器内设备会出现在/dev/下,具体路径由卷挂载决定。这种模式多用于对IO延迟极其敏感的场景。


5. 常见问题与排查

5.1 PV创建了但一直是Pending状态

PV已经出现,但PVC一直Pending。我的排查顺序是:

  1. 先看PVC的Events:
oc describe pvc postgres-data
  1. 查看Pod的事件:
oc describe pod test-local-pod

这类问题的核心原因通常是volumeBindingMode: Immediate导致的。当StorageClass的volumeBindingMode不是WaitForFirstConsumer时,PVC的绑定过程可能发生在调度前,导致Pod被调度到没有本地PV的节点上,PV一直Pending。解决方案就是把StorageClass的volumeBindingMode改为WaitForFirstConsumer。

另外一个常见原因是节点污点没被容忍。如果节点上有污点,而LocalVolumeSet没有配置对应的tolerations,PV创建流程会被中断。

5.2 磁盘删了PV还在

Local Storage的PV由Operator管理,当节点上的磁盘被物理移除时,PV不会被立刻删除,它会进入Released状态。这是Local PV和动态存储的一个明显差异——动态存储(如Ceph RBD)目录存储删除PV是常态,但Local Storage里你想要清理旧PV,需要手动删除PV并清理节点上的数据目录。

这里有一个操作上的经验:LocalVolumeSet删除后,如果PV状态仍然是Released或Available,你需要在节点上手动清理数据目录,否则PV虽然删除了,但磁盘空间并没有释放。我在交付项目时,都会在删除PV的流程文档里加上这一步:rm -rf /mnt/local-storage/xxx。

5.3 Pod被调度到了错误的节点

有时Pod没有被调度到你期望的有磁盘的节点上。原因是PV的nodeAffinity指向的节点没问题,但可能的坑是:PVC绑定的PV确实在一个节点,但由于Pod有额外的标签选择器或亲和性设置,反而把Pod推到了其他节点。这时PV就只能在你期望的节点上待命,Pod就会一直Pending。

排查方法是把Pod调度器的最终决策过程看一遍:

oc describe pod test-local-pod | grep -A20 Events

看到类似node(s) didn't match Pod's node affinity/selector,就顺着Pod调度器所看到的节点标签来检查。

5.4 节点重启后PV不见了

这个现象在一些基于老版本Kubernetes/OpenShift的Local PV实现中出现过,重启后PV处于Lost状态。原因是节点重启过程中,磁盘设备路径可能发生变化(特别是在有多块盘且没有使用UUID挂载的场景)。建议是在节点上通过/dev/disk/by-uuid或/dev/disk/by-id来确认挂载路径,保证设备路径不会因为重启而漂移。

5.5 容量监控与告警

Local Storage不像Ceph或GlusterFS那样有集中的容量监控。它的容量是分散在各个节点上的。我建议的运维手段是:在集群层面对每个Local PV设置Alert,比如Prometheus里按PV的容量使用率做告警:

( sum by (persistentvolumeclaim, namespace) ( kubelet_volume_stats_used_bytes ) / sum by (persistentvolumeclaim, namespace) ( kubelet_volume_stats_capacity_bytes ) ) > 0.85

这样至少能在磁盘被写满之前收到预警,避免节点进入NotReady状态。

5.6 误删LocalVolumeSet引发的连锁问题

这个踩过坑:直接删掉正在使用的LocalVolumeSet,而没有先确认PVC的引用。这会导致PV被回收,PVC跟着报错,应用全部挂掉。

正确的操作顺序是:

  1. 先删除所有使用该StorageClass的Deployment/StatefulSet/Pod
  2. 删除PVC
  3. 最后删除LocalVolumeSet

如果已经误删了,别慌,先检查PV状态,如果PV状态还是Bound,说明Operator还没触发回收,马上重新创建同名的LocalVolumeSet,让PV重新接管。


6. 生产实践的经验总结

6.1 我常用的配置组合参考

场景推荐配置
单节点数据库(PostgreSQL)Filesystem模式,WaitForFirstConsumer,容量按磁盘的80%申请
消息队列(Kafka)Block模式,多PV分摊,每个PV使用率控制在70%以下
缓存服务(Redis)Filesystem模式,独立数据盘,不跟监控日志共用目录
大数据组件(ES)Block或Filesystem均可,ES官方对块设备支持更好,建议Block

6.2 关于扩容和迁移

Local Storage有一个先天的限制:单节点上的PV扩容很困难。因为物理磁盘容量固定,PVC因物理盘满而饱和后,就只能把PV删除后重新创建。这在做容量规划时要特别仔细,我给客户的建议是:按业务数据增长率的3倍预留容量。

同样,PV迁移也麻烦,本地盘的数据要迁移,只能先备份到外部,再恢复到新节点的PV里,与Ceph这类分布式存储的在线迁移体验差异巨大。所以Local Storage的选择标准就是:数据增长可控,且接受单节点故障停机恢复时间。

6.3 与OpenShift其他存储方案的取舍

很多团队会问我和OpenShift Data Foundation(ODF)怎么选。我的判断标准很简单:

需要跨节点高可用和在线扩容,选ODF或外部存储;应用能容忍单节点故障,且追求极致的IO性能,选Local Storage。Local Storage没有副本,没有RAID,没有分布式能力,它就是直通物理盘。它的优点是延迟极低、带宽高、无额外网络开销,缺点就是没冗余。

如果你做的是中等规模集群且已经有外部存储(比如企业级SAN或NAS),那Local Storage更适合做加速盘,而不是主存储。如果做的是边缘节点或小规模集群,Local Storage完全可以扛起有状态应用的主存储职责,前提是你能接受前述的运维限制。

6.4 日常运维清单

最后分享一份我落地到团队里的日常运维清单:

  • 每周检查一次Local PV的容量使用率和对应节点的磁盘使用率
  • 每次节点维护前,确认该节点上是否有Local PV正在承载业务,先迁移或摘除Pod
  • 节点下线流程中,先删PVC和LocalVolumeSet,再清盘,最后才摘除节点
  • 以设备UUID方式在/etc/fstab里固定挂载点,避免重启后设备路径漂移
  • 在告警规则中单独针对WaitForFirstConsumer类型的PVC做绑定状态告警,避免PVC绑定异常未被发现
  • 不要在同一个磁盘上既跑Local Storage又跑其他业务数据,一旦PV容量写满,节点会直接受影响

我实际踩过的最大一个坑,就是在一次节点维护时直接把节点重启了,以为是“正常操作”,结果节点上有好几个Local PV对应的磁盘因为设备路径变化全部失联,应用全部起不来。后来把挂载方式改成UUID固定后才稳定下来。这种经验,文档里一般不写,只有实际做过的人才知道痛。

如果你正在规划OpenShift集群的有状态应用存储,Local Storage是一个很值得认真评估的选项。它不像分布式存储那样自带光环,但它简单、直接、高性能,运维好了能支撑不少核心业务。把上面的配置和排查方法吃透,剩下的就是在实践中慢慢积累感觉了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询