OpenEBS — 云原生 CNS 高性能存储
本文全面介绍了OpenEBS这一Kubernetes原生开源存储解决方案,重点分析了其三种主要存储类型:LocalPV HostPath、LocalPV LVM和ReplicatedPV Mayastor。OpenEBS采用Container Attached Storage(CAS)架构,将存储组件容器化并由Kubernetes编排管理。 文章详细阐述了各存储类型的技术原理、部署配置和适用场景:
一、OpenEBS概述
1.1 OpenEBS的核心概念
OpenEBS 是一个完全 Kubernetes 原生(Cloud Native Storage) 的开源存储解决方案,它能将 Kubernetes 节点上的可用存储资源转化成 Persistent Volumes(持久卷) 供容器化应用使用。它通过 Container Attached Storage (CAS) 架构直接把存储引擎以容器方式部署在 Kubernetes 集群中,由 Kubernetes 自己来调度与管理。
简而言之:
-
OpenEBS 把每个 Kubernetes 节点变成一个存储服务器;
-
通过 Kubernetes API 进行存储的生命周期管理(创建、绑定、扩容、删除等);
-
完全不用额外的存储集群或者传统外部 SAN/NAS。
1.2. OpenEBS核心架构与组件
OpenEBS 架构大致分成两个主要部分:
🔹 1) Data Engines(数据引擎)
这是处理实际 读写 I/O 的核心组件,负责:
-
将节点上提供的存储资源(本地磁盘、分区、文件系统)聚合成可用容量;
-
对外提供卷服务(通过 CSI 驱动暴露卷给 Pod);
-
管理数据、复制、容错、故障重建等。
🔹 2) Control Plane(控制平面)
这是控制 OpenEBS 运转的“大脑”,负责:
-
管理 Kubernetes 节点上的存储资源;
-
与 CSI(Container Storage Interface)集成;
-
响应 PVC 请求并根据 StorageClass 动态生成 PV;
-
处理卷的扩容、快照、备份恢复等控制功能;
-
所有配置都通过 Kubernetes Custom Resource Definitions (CRDs) 托管。
📌 所有 OpenEBS 控制组件和数据引擎本身就是 Kubernetes Pod,由 Kubernetes 调度和管理。
| 组别 | Pod 名称示例 | 容器数 | 作用说明 |
|---|---|---|---|
| CSI 通用组件 | openebs-csi-controller-* openebs-csi-node-* | 6/6 2/2 | CSI 控制器(集群级,处理卷 provision、snapshot 等)和节点插件(处理 attach/detach/mount)。所有 OpenEBS 引擎共用,是与 Kubernetes 集成的桥梁。 |
| LocalPV 引擎 | openebs-localpv-provisioner-* openebs-lvm-localpv-controller-* openebs-lvm-localpv-node-* openebs-zfs-localpv-controller-* openebs-zfs-localpv-node-* | 1/1 5/5 2/2 5/5 2/2 | Dynamic LocalPV 通用 provisioner。 LVM/ZFS 专用控制器(集群级)和节点插件(处理本地卷创建、格式化)。提供节点本地持久卷(低延迟、无复制)。 |
| Replicated PV Mayastor(复制高可用引擎) | openebs-io-engine-node-* openebs-agent-core-* openebs-agent-ha-node-* openebs-operator-diskpool-* | 2/2 2/2 1/1 1/1 | Mayastor 数据平面核心 DaemonSet,负责实际 IO 处理、同步复制、NVMe-oF 暴露卷(高性能)。你看到 3 个都在 node2 上,说明只有该节点被标记为可用磁盘节点。 Mayastor 核心 agent 和 HA agent,负责节点注册、故障检测。 磁盘池(DiskPool)Operator,管理底层磁盘资源。 |
| Mayastor 基础设施 | openebs-nats-* openebs-etcd-* | 3/3 1/1 | NATS 集群(消息总线),Mayastor 控制平面用于事件发布/订阅、分布式协调。 etcd 集群,持久化 Mayastor 控制平面状态(卷元数据、池配置等)。 |
| 可观测性(Observability)栈 | openebs-alloy-* openebs-loki-* openebs-minio-* | 2/2 2/2 1/1 | Grafana Alloy(OpenTelemetry Collector),收集指标、痕迹,发送到 Prometheus/Grafana。 Loki 分布式日志系统,集中收集所有 OpenEBS 组件日志。 MinIO 分布式对象存储,作为 Loki 的后端存储(也可能用于快照/备份)。 |
| 其他辅助组件 | openebs-api-rest-* openebs-obs-callhome-* | 1/1 2/2 | REST API 服务,提供管理接口(可能用于 Mayastor 或整体控制平面)。 遥测/呼叫回家组件,匿名上报使用统计(可禁用)。 |
🔹 3) OpenEBS架构图

🔹 4) 简单的工作流程概述
你在 YAML 里写一个 PVC(说“我要 10G 存储”)。
CSI 组件收到请求,去问控制平面。
控制平面(Operators)决定用哪个引擎(LocalPV 还是 Mayastor),然后在有磁盘的节点上创建卷。
数据平面(io-engine 或 LocalPV node 插件)真正把数据写到磁盘上。
卷挂载到你的应用 Pod,你就正常读写数据了。
如果用 Mayastor,它还会自动把数据复制到其他节点,确保安全。
总体交互流程:Kubernetes 通过 PVC/StorageClass 发起卷请求 → 控制平面(CSI + Operators)provision 卷并部署副本/目标 Pod → 数据引擎的分层结构处理实际存储和访问 → 应用通过标准方式读写数据。
你的集群 Pod 这么多,是因为默认安装把“全家桶”都开了(LocalPV + Mayastor + 监控全上)。
实际用时可以只开一种引擎,比如只想高可用就留 Mayastor(io-engine 那堆),其他可以卸载,省资源。
1.3. OpenEBS的主要存储类型
1) Local Volumes(本地卷)
-
数据仅驻留在创建该卷的单个节点上;
-
Pod 必须调度到该节点才能访问卷;
-
性能低延迟、高吞吐;
-
常用于分布式数据库或有自身高可用机制的应用。
Local 类型有多个实现,比如 HostPath、LVM、ZFS 等,选择不同引擎会影响性能和特性。
2) Replicated Volumes(副本卷/复制卷)
-
数据在多个节点上同步复制;
-
支持跨节点容错、节点故障自动恢复;
-
通常具备更强的数据可靠性与可用性;
-
适用于需要高耐久性和跨可用区支持的场景。
这些副本卷可以提供快照、克隆、扩容等企业级存储特性。
为什么本地存储更适合分布式工作负载?
这些分布式数据库(服务)自身已经实现了数据分片、复制和容错机制
本地存储无额外数据路径开销,延迟更低
复制存储(如OpenEBS的Replicated Volumes)更适合:
非分布式应用(如MySQL单机版)
这些应用本身不具备分布式容错能力
1.4 CNS插曲
1) 什么是 Container Native Storage (CNS)?
CNS 是一种将存储软件完全容器化并由 Kubernetes 编排的存储架构模式,它使存储成为 Kubernetes 原生应用的一部分,而非传统外部共享存储系统。
存储软件完全容器化 + 由 Kubernetes 原生编排 + 每个存储卷拥有专属容器化控制器
CNS(Container Native Storage)的核心优势在于:将存储完全转化为Kubernetes原生工作负载,通过为每个持久卷配备专属的容器化控制器,实现细粒度策略管控、故障影响域最小化、跨集群无厂商锁定迁移的能力,使存储运维获得与无状态应用同等的敏捷性、可移植性与可靠性,彻底摆脱传统存储的复杂架构与供应商依赖。
每个 Persistent Volume(PV)在 Kubernetes 中都拥有一个“专属管家 Pod” —— 这个 Pod 就是容器化的存储控制器。它专属于该卷,只处理这个卷的读写请求、快照、复制等逻辑,与其他卷完全隔离。
OpenEBS 是 CNS 概念的实现,它通过容器化存储控制器和 Kubernetes 原生 API,使 Kubernetes 有状态工作负载的存储管理变得简单、高效且可扩展。
二、安装与运行OpenEBS
OpenEBS 支持:
-
任何 Kubernetes 集群(云上、裸金属、开发环境如 Minikube/MicroK8s);
-
使用 Helm 或 kubectl 直接部署;
-
支持主流监控(Prometheus、Grafana)与工具集成。
这里使用Helm一键部署与安装
2.1 安装前置准备
请安装前一定要仔细阅读,并不是要求所有的环境都要准备,这取决于你要使用哪种存储模式!
1.K8S管理权限
你必须有 cluster-admin(集群管理员权限) 上下文(admin context)才能安装 OpenEBS。
[root@k8s-master ~/openEBS]# cat /root/.kube/config
...
kind: Config
preferences: {}
users:
- name: kubernetes-admin ##
2.节点需要能访问物理设备或目录
OpenEBS 会在 Kubernetes 节点上使用物理磁盘、文件系统目录、LVM、ZFS 池等做持久存储。因此你需要:
-
决定哪些磁盘或目录将被 OpenEBS 使用;
-
如果需要 LVM 或 ZFS,需要提前 创建 Volume Group 或 ZFS 池;
-
在一些托管 Kubernetes 平台(如 Rancher/RKE、MicroK8s)上,还要正确配置 bind mounts(绑定挂载)。
3.各种存储引擎的准备工作
OpenEBS 提供多种数据引擎,不同引擎对节点环境有不同的前置条件。以下是官方说明的主要引擎及准备要点:
A. Local PV Hostpath
本地 Hostpath 引擎用于最简单的本地卷:
-
需要在每个节点创建一个目录作为存储基础路径(BasePath)。
-
默认 BasePath 是:
/var/openebs/local。如果采用HostPath的方式,这个默认目录必须存在。 -
BasePath 也可以是其他挂载点,如 SSD 挂载目录或云盘挂载目录。
B. Local PV LVM
使用 LVM 做本地块设备卷时,节点必须满足:
-
所有节点安装 lvm2 工具包;
-
节点内核加载 dm-snapshot module;
-
在所有节点上创建一个 Volume Group(VG),供 OpenEBS LVM 引擎使用。
💡 指令示例(官方): 创建一个测试用 Volume Group:
truncate -s 1024G /tmp/disk.img
losetup -f /tmp/disk.img --show
pvcreate /dev/loop0
vgcreate lvmvg /dev/loop0
lvmvg 是创建的卷组名字。
C. Local PV ZFS
使用 ZFS 做本地存储时:
-
所有节点必须安装 zfsutils-linux;
-
所有节点上需要先创建一个 ZFS zpool 作为存储池;
-
这个池可以是各种类型(striped、mirror、raidz)按需求选择。
💡 示例流程:
apt-get install zfsutils-linux
zpool create zfspv-pool /dev/sdb
zpool status
zfspv-pool 是创建的 ZFS 池名称。
D. Replicated PV Mayastor(复制卷,高级引擎)
这个是 OpenEBS 用于高可靠性和高性能的引擎,对环境要求更严格:
-
通用要求
-
x86-64 CPU,支持 SSE4.2 指令集;
-
推荐 Linux kernel ≥ 5.15;
-
节点必须满足 SPI-NVMe、ext4/xfs 等模块加载;
-
Helm 版本 ≥ v3.7(安装 Mayastor 组件用)。
-
-
资源要求
-
Io-engine pod 每个节点至少:
-
2 个 CPU 核心
-
1GiB RAM
-
支持 HugePages(大页)(至少 2GiB 2MiB 页面)。
-
-
-
网络要求
-
确保某些端口不被占用,如 gRPC 服务端口 10124;
-
如果用 NVMe-oF TCP 协议,必须启用并保证连接无防火墙阻断。
-
-
集群规模
-
至少 3 个 worker 节点(尤其使用复制/镜像卷时)(如果没有3worker测试环境可以用master作为第3个存储节点)。
-
-
协议限制
-
Replicated PV Mayastor 只支持 NVMe-oF over TCP 方式挂载卷。
-
OpenEBS 官方说明的基础兼容条件:
| 要素 | 要求 |
|---|---|
| Kubernetes | 1.23 或更高 |
| Linux Kernel | 5.15 或更高 |
| 支持操作系统 | Ubuntu / RHEL 8.8 |
| LVM 版本 | LVM2 |
| ZFS 版本 | ZFS 0.8+ |
本次安装采用的LocalVolumes是HostPath和LVM-Volumes
2.1.1 HostPath环境准备
3个从节点配置默认目录(如果是一主两从可以先去除master的污点。然后主节点也创建目录。)
[root@k8s-master ~]# kubectl describe nodes |grep -A 5 'Tai'
Taints: node-role.kubernetes.io/control-plane:NoSchedule
Unschedulable: false
Lease:
HolderIdentity: k8s-master
AcquireTime: <unset>
RenewTime: Mon, 02 Feb 2026 16:33:06 +0800
--
Taints: <none>
Unschedulable: false
Lease:
HolderIdentity: k8s-node1
AcquireTime: <unset>
RenewTime: Mon, 02 Feb 2026 16:33:06 +0800
--
Taints: <none>
Unschedulable: false
Lease:
HolderIdentity: k8s-node2
AcquireTime: <unset>
RenewTime: Mon, 02 Feb 2026 16:33:05 +0800
[root@k8s-master ~]# kubectl taint node k8s-master node-role.kubernetes.io/control-plane:NoSchedule-
node/k8s-master untainted
上述文中,已经提到,针对于Local-Volumes的存储模式,我们使用HostPath和LVM-Volumes的方式,所以这里我们要提前准备好环境。
创建HostPath的默认存储目录
mkdir -p /var/openebs/local
chown root:root /var/openebs/local
chmod 777 /var/openebs/local
Local PV HostPath 的 basePath 必须具备 x 权限,至少 755,否则 provisioner 无法创建 PV 目录。
2.1.2 LVM-Volumes环境准备
- 可以使用loopback 虚拟磁盘(官方推荐的测试方式,不会占用真实存储)。
- 也可以给服务器加一块全新未使用的磁盘,如果你是VM虚拟环境可以直接,然后开机

安装 lvm2 工具包 所有节点(k8s-master、k8s-node1、k8s-node2)必须安装 lvm2 包。 执行命令(推荐用 apt,因为我是 Ubuntu):
[root@k8s-master ~/openEBS]# apt update
[root@k8s-master ~/openEBS]# apt install lvm2 -y
[root@k8s-master ~/openEBS]# lvm version
LVM version: 2.03.31(2) (2025-02-27)
Library version: 1.02.205 (2025-02-27)
Driver version: 4.50.0
加载内核模块:所有节点必须加载 dm-snapshot 内核模块(LVM snapshot 支持所需)。
# 1. 检查模块是否已加载(注意:模块名在系统中显示为下划线形式)
lsmod | grep dm_snapshot
# 2. 若未加载,执行
modprobe dm_snapshot
# 3. 验证加载成功
lsmod | grep dm_snapshot # 应有输出
# 4. 设置开机自启(追加到 /etc/modules)
echo "dm_snapshot" | sudo tee -a /etc/modules
创建 Volume Group (VG) 这是最关键的一步:所有希望使用 LVM LocalPV 的节点上,必须提前创建一个 Volume Group(VG),OpenEBS 才会从中动态 provision PV。
-
生产环境:推荐使用独立的物理磁盘或分区(避免与系统盘混用)。
-
测试环境:可以用loopback虚拟磁盘(官方推荐的测试方式,不会占用真实存储)。
三台节点(master、node1、node2)磁盘布局相同,都有一块独立的 sdb(20G),这很理想——你可以在每台节点上用 sdb 创建相同名称的 VG,这样 OpenEBS LVM LocalPV 就能在所有节点上正常动态 provision PV。
确认 sdb 确实空闲且无重要数据
lsblk -f /dev/sdb # 检查是否已有文件系统(应该什么都没有)
fdisk -l /dev/sdb # 确认无分区表
# 创建 Physical Volume (PV)
pvcreate /dev/sdb
# 创建 Volume Group (VG)
# 建议统一用同一个名字(如 openebs-lvm-vg):
vgcreate openebs-lvm-vg /dev/sdb
# 验证创建成功
pvdisplay
vgdisplay openebs-lvm-vg
lvdisplay # 目前应该没有 LV
你应该看到 VG 大小约为 20G(减去少量 overhead)。
三台节点全部操作完后的效果
-
每台节点都有一个独立的 openebs-lvm-vg(容量各 20G)。
-
OpenEBS LVM provisioner 会自动发现这些 VG,并在对应节点上创建 Local PV(PV 会绑定到具体节点,不可跨节点迁移,这是 LocalPV 的特性)。
2.2 使用Helm安装OpenEBS4.3.3
Helm版本必须是3.2及以上
# 添加 Helm 仓库
[root@k8s-master ~/openEBS]# helm repo add openebs https://openebs.github.io/openebs
"openebs" has been added to your repositories
[root@k8s-master ~/openEBS]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "openebs" chart repository
Update Complete. ⎈Happy Helming!⎈
[root@k8s-master ~/openEBS]# helm search repo openebs --versions |head
NAME CHART VERSION APP VERSION DESCRIPTION
openebs/openebs 4.4.0 4.4.0 Containerized Attached Storage for Kubernetes
openebs/openebs 4.3.3 4.3.3 Containerized Attached Storage for Kubernetes
openebs/openebs 4.3.2 4.3.2 Containerized Attached Storage for Kubernetes
openebs/openebs 4.3.1 4.3.1 Containerized Attached Storage for Kubernetes
openebs/openebs 4.3.0 4.3.0 Containerized Attached Storage for Kubernetes
openebs/openebs 4.2.0 4.2.0 Containerized Attached Storage for Kubernetes
openebs/openebs 4.1.3 4.1.3 Containerized Attached Storage for Kubernetes
openebs/openebs 4.1.2 4.1.2 Containerized Attached Storage for Kubernetes
openebs/openebs 4.1.1 4.1.1 Containerized Attached Storage for Kubernetes
2.2.1 只安装OpenEBS Local Volumes模式
helm install openebs openebs/openebs \
--version 4.3.3 \
--namespace openebs \
--create-namespace \
--set engines.replicated.mayastor.enabled=false
等待所有的Pod都完成Running即可。
三、Local PV Hostpath 概览
OpenEBS Local PV Hostpath 是一种 轻量级、简单易用的本地持久存储引擎,它通过使用 Kubernetes Local Persistent Volume(Local PV)的能力,把节点上的特定目录(hostPath)封装成可由 StorageClass/PVC 请求的持久存储。
| 特性 | K8S原生HostPath | OpenEBS LocalVolumes |
|---|---|---|
| 数据存放位置 | 节点本地目录 | 节点本地盘 + PV 封装 |
| 生命周期 | 依赖目录存在 | 依赖 PV/PVC 声明周期 |
| 调度扩展 | 新节点需手动创建目录 | Kubernetes 自动绑定 PV/PVC |
| 数据一致性 | 无保障 | PVC 引用保证 Pod 访问同一数据 |
3.1 定义与用途
-
Local PV Hostpath:OpenEBS 提供的一种 Local Persistent Volume 类型
-
Local Persistent Volume:由 Kubernetes 本地磁盘或目录创建的 PV,只能在创建它的节点上访问
-
Hostpath:节点文件系统上的某个目录,如
/var/openebs/local -
动态 Provisioning:在需要存储时自动创建对应 PV,而不是手动提前创建 PV 文件。
3.2 官方推荐的工作流程
安装 OpenEBS 并启用 Local PV Hostpath Provisioner
安装 Helm chart 或 manifest
确认 BasePath 存在
默认是节点上的
/var/openebs/local创建 StorageClass(如果需要自定义)
修改 BasePath 或 NodeAffinity 配置
创建 PVC 引用该 StorageClass
创建 Pod 使用 PVC 进行挂载
验证数据持久性 / Backup/Restore(可用 Velero)
| 官方术语 | 通俗解释 |
|---|---|
| Local PV Hostpath | 在节点本地目录上创建的 OpenEBS 管理的持久卷 |
| BasePath | OpenEBS 存放 Hostpath PV 的根目录 |
| StorageClass | 用于定义 Local PV Hostpath 的策略入口 |
| Dynamic Provisioning | 自动在节点上创建目录和 PV,无需手动 PV 定义 |
| Node Affinity | Pod 必须调度到拥有该 PV 的节点 |
| Velero Backup/Restore | 集群级备份和恢复方案,可用于数据保护 |
3.3 自定义StorageClass
3.3.1 为什么要自定义 StorageClass?
OpenEBS 安装完成后通常会自动创建一个默认的 LocalPV Hostpath StorageClass:
openebs-hostpath
默认的配置中:
-
BasePath=/var/openebs/local也就是说 LocalPV 会在该目录下为每个 PV 创建子目录。
但在实际场景中,你可能希望:
✅ 把数据放到不同目录(比如 /mnt/data/hostpath)
✅ 给不同用途的应用分不同 StorageClass
✅ 修改回收策略 / 亲和策略
✅ 指定自定义节点标识来做更精细的调度
这时就需要 自定义 StorageClass 了。
3.3.2 StorageClass 基本结构
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-hostpath
annotations:
openebs.io/cas-type: local
cas.openebs.io/config: |
- name: StorageType
value: hostpath
- name: BasePath
value: /var/local-hostpath
provisioner: openebs.io/local
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
👉 核心字段解释:
| 字段 | 意义 |
|---|---|
metadata.name |
StorageClass 名称(自定义的) |
openebs.io/cas-type: local |
表示使用 OpenEBS LocalPV 引擎 |
StorageType: hostpath |
指明使用 Hostpath 类型存储 |
BasePath: /xxx |
这个 StorageClass 生成 PV 时在节点上用哪个根目录 |
provisioner: openebs.io/local |
使用 LocalPV 动态 Provisioner |
reclaimPolicy: Delete |
PVC 删除后 PV 会被自动删除 |
volumeBindingMode: WaitForFirstConsumer |
延后 PV 创建到 Pod 调度时机(最安全) |
# volumeBindingMode: WaitForFirstConsumer
这句非常重要:
✔ 它告诉 Kubernetes:不要在 PVC 创建时就绑定 PV
✔ Kubernetes 先等 Pod 调度决定在哪个节点运行
✔ Provisioner 看到 Pod 的节点后才创建 PV
这样做的原因是:
👉 Local PV 需要和 Pod 在同一个节点,否则数据“本地”就不成立
# Node Affinity(可选)
默认 Hostpath 是通过节点的 kubernetes.io/hostname 来做亲和(NodeAffinity):
spec:
local:
path: /var/openebs/local/pvc-xxx
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
📌 但 hostname 并不是总是你想要的节点唯一标识,特别是:
节点被替换后 hostname 改变
节点有多个角色,但你想限定存储节点
这种情况下可以加入自定义标签方式:
- name: NodeAffinityLabels
list:
- "openebs.io/custom-node-unique-id"
这样做后:
✔ 用你定义的标签作为节点唯一标识
✔ 即使 hostname 变了,只要标签一致,Local PV 仍能定位数据
✔ 更加稳定可靠(特别在节点变更 / 集群重建场景)
NodeAffinityLabels 不能替代调度策略
使用 NodeAffinityLabels 只是为了让 PV 绑定时引用一个稳定的 node identity。
❌ 它 不会影响 Pod 的调度
3.3.3 示例StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-hostpath
annotations:
openebs.io/cas-type: local
cas.openebs.io/config: |
- name: StorageType
value: hostpath
- name: BasePath
value: /var/local-hostpath
provisioner: openebs.io/local
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
3.3.4 部署一个有状态的应用
官方 PVC 示例(LocalPV Hostpath)
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: local-hostpath-pvc
spec:
storageClassName: openebs-hostpath
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5G
创建 Pod 来使用 PVC
[root@k8s-master ~/openEBS]# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: hello-local-hostpath-pod
spec:
volumes:
- name: local-storage
persistentVolumeClaim:
claimName: local-hostpath-pvc
containers:
- name: hello-container
image: myweb:v1
command:
- sh
- -c
- 'while true; do echo "`date` Hello from OpenEBS." >> /mnt/store/greet.txt; sleep 10; done'
volumeMounts:
- mountPath: /mnt/store
name: local-storage
[root@k8s-master ~/openEBS]# kubectl get po,pvc
NAME READY STATUS RESTARTS AGE
pod/hello-local-hostpath-pod 1/1 Running 0 13s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
persistentvolumeclaim/local-hostpath-pvc Bound pvc-29e78edd-d851-49b2-9fbd-4f95bc625952 5G RWO openebs-hostpath <unset> 5m36s
[root@k8s-master ~/openEBS]# kubectl exec hello-local-hostpath-pod -- cat /mnt/store/greet.txt
Mon Feb 2 08:51:10 UTC 2026 Hello from OpenEBS.
Mon Feb 2 08:51:20 UTC 2026 Hello from OpenEBS.
Mon Feb 2 08:51:30 UTC 2026 Hello from OpenEBS.
测试LocalVolumes的持久化数据
# 删除Pod重新创建,测试数据是否还存在。
[root@k8s-master ~/openEBS]# kubectl delete pod hello-local-hostpath-pod
pod "hello-local-hostpath-pod" deleted
# 没问题
[root@k8s-master ~/openEBS]# kubectl apply -f pod.yaml
pod/hello-local-hostpath-pod created
[root@k8s-master ~/openEBS]# kubectl get po,pvc
NAME READY STATUS RESTARTS AGE
pod/hello-local-hostpath-pod 1/1 Running 0 2s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
persistentvolumeclaim/local-hostpath-pvc Bound pvc-29e78edd-d851-49b2-9fbd-4f95bc625952 5G RWO openebs-hostpath <unset> 13m
[root@k8s-master ~/openEBS]# kubectl exec hello-local-hostpath-pod -- cat /mnt/store/greet.txt
Mon Feb 2 08:51:10 UTC 2026 Hello from OpenEBS.
Mon Feb 2 08:51:20 UTC 2026 Hello from OpenEBS.
Mon Feb 2 08:51:30 UTC 2026 Hello from OpenEBS.
Mon Feb 2 08:51:40 UTC 2026 Hello from OpenEBS.
Mon Feb 2 08:51:50 UTC 2026 Hello from OpenEBS.
Mon Feb 2 08:52:00 UTC 2026 Hello from OpenEBS.
Mon Feb 2 08:52:10 UTC 2026 Hello from OpenEBS.
Mon Feb 2 08:52:20 UTC 2026 Hello from OpenEBS.
Mon Feb 2 08:52:30 UTC 2026 Hello from OpenEBS.
Mon Feb 2 08:52:40 UTC 2026 Hello from OpenEBS.
# 验证数据发现Pod调度在node1,那么数据就只存在node1
[root@k8s-master ~/openEBS]# kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
hello-local-hostpath-pod 1/1 Running 0 30s 10.200.36.101 k8s-node1 <none> <none>
[root@k8s-master ~/openEBS]# kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
hello-local-hostpath-pod 1/1 Running 0 30s 10.200.36.101 k8s-node1 <none> <none>
HostPath和LocalVolumes的看法,首先HostPath不具备持久化存储,如果Pod运行在node1节点产生了数据,后续如果集群的资源不够,扩展了一些节点,然后如果Pod也一并扩展到新节点上面,但是新节点没有创建HostPath对应的目录,那么Pod的数据就会不一致,目录被删除,数据的声明周期就会结束。但是openEBS的LocalVolumes针对于HostPath他不一样,他是封装在PV\PVC这个存储系统上面的hostPath,他的周期不依赖于目录是否存在,而是PV\PVC的声明周期,无论扩展多少节点,只要是一个集群内,指向PV\PVC那么数据就是一致的。
四、LocalVolumes-LVM概括
4.1 创建存储类SC
创建 SC 的关键在于指定 LVM 存储类型、Volume Group(VG)名称,以及可选的调度策略、文件系统类型等参数。
这样,当你创建 PVC(PersistentVolumeClaim)时,OpenEBS 会自动在匹配的节点上从指定的 VG 中切出一个 Logical Volume(LV)作为 Local PV。
文档强调:
必须参数:指定 LVM 类型和 VG(用 volgroup 或 vgpattern)。
可选参数:文件系统类型(fsType,默认 ext4)、薄配置(thinProvision)、调度器(scheduler,默认 SpaceWeighted,选择剩余空间最大的节点)。
支持标准 Kubernetes StorageClass 参数,如卷扩展、拓扑限制、绑定模式等。
文档提供了两个官方案例 YAML,我会结合你的实际情况(每台节点 VG 名为 openebs-lvm-vg,容量 20G)进行分析和适配。
4.1.1 基础 StorageClass(最简)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: openebs-lvm-sc # 自定义名字,建议带 lvm 标识
provisioner: local.csi.openebs.io # 顶层字段(CSI 驱动)
parameters:
storage: "lvm" # 指定 LVM 类型
volgroup: "openebs-lvm-vg" # 直接用你的 VG 名,三台节点都匹配
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer # 强烈推荐,避免调度失败
4.1.2 带 scheduler 参数的SC
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: openebs-lvm-balanced-sc
provisioner: local.csi.openebs.io
parameters:
storage: "lvm"
volgroup: "openebs-lvm-vg" # 你的 VG
thinProvision: "yes" # 推荐:薄配置,节省空间
fsType: "ext4" # 可改 xfs
scheduler: "SpaceWeighted" # 默认就是这个,可显式写;或换 CapacityWeighted
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
三种 scheduler 的通俗对比
SpaceWeighted(默认,推荐入门):Pod 需要存储时,自动选当前剩余空间最大的节点(比如 node1 剩 15G,node2 剩 10G,就选 node1)。最均衡。
CapacityWeighted:选“已用比例最低”的节点(适合你想均匀分配已用容量)。
VolumeWeighted:选当前 LV 数量最少的节点(如果你有很多小卷,防止某个节点卷太多影响性能)。
4.1.3 存储类选项
| 参数 | 是否必填 | 默认值 | 可能值 | 描述 |
|---|---|---|---|---|
| storage | 是 | 无 | "lvm" | 存储后端类型 |
| vgpattern | 条件必填 | 无 | 正则 (e.g., "lvmvg.*") | 匹配 Volume Group 的模式 |
| volgroup | 条件必填 | 无 | 字符串 (e.g., "lvmvg") | 确切 Volume Group 名称(将来废弃) |
| allowVolumeExpansion | 否 | false | true / false | 是否支持卷扩容 |
| mountOptions | 否 | -o default | 选项列表 (e.g., debug) | 挂载选项 |
| fsType | 否 | ext4 | ext2, ext3, ext4, xfs, btrfs | 文件系统类型 |
| shared | 否 | No | "yes" | 是否允许多 Pod 共享 |
| thinProvision | 否 | "no" | "yes", "no" | 是否薄配置 |
| volumeBindingMode | 否 | Immediate | Immediate, WaitForFirstConsumer | 卷绑定时机 |
| reclaimPolicy | 否 | Delete | Delete, Retain | PVC 删除行为 |
| allowedTopologies | 否 | 无 | 拓扑标签表达式 | 限制 provision 节点 |
4.1.4 示例SC
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: openebs-lvm-advanced
provisioner: local.csi.openebs.io
parameters:
storage: "lvm"
vgpattern: "openebs-lvm-vg" # 精确匹配你的 VG(或 "openebs.*" 更宽松)
thinProvision: "yes" # 强烈推荐:省空间(记得检查 dm_thin_pool 模块)
fsType: "ext4" # 或改 xfs 追求性能
shared: "no" # 按需改 yes
allowVolumeExpansion: true # 支持扩容
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
为什么这样配?
-
用 vgpattern 而非 volgroup:官方明确推荐,避免将来废弃。
-
开启 thinProvision: 你的 20G 盘测试用很合适,实际用数据少时不浪费。
-
WaitForFirstConsumer:Local PV 必须(防止 PV 绑错节点)。
-
如果你想限制只在 worker 节点(node1/node2)用,可以加 allowedTopologies(需先给节点打标签)。
| 模式 | PVC 创建后立刻干嘛? | Pod 调度后会怎样? | 适合 Local PV? | 常见问题 |
|---|---|---|---|---|
| Immediate (默认) | 立刻在某个节点创建 PV | 如果 PV 和 Pod 不在同一节点 → Pod Pending | 不推荐 | 调度失败、反复重试 |
| WaitForFirstConsumer | 啥都不干,等着 | 在 Pod 落位的节点创建 PV → 成功启动 | 强烈推荐 | 几乎没问题,专为本地存储设计 |
4.2 创建持久体积声明-PVC
核心流程:
- PVC 是标准 Kubernetes 对象,没有 LVM 专属参数。
- 关键只需指定 storageClassName 为之前创建的 LVM StorageClass(SC)。
- 当 PVC 被 Pod 使用(尤其是 WaitForFirstConsumer 模式下),OpenEBS 会自动在合适节点(有匹配 VG 的)的 openebs-lvm-vg 上从 VG 切出一个 Logical Volume (LV) 作为 PV。
- 页面提供了一个最简官方案例 YAML,没有额外步骤或复杂参数说明(因为 PVC 创建本身很简单)。
- 强调:accessModes 通常用 ReadWriteOnce(RWO),因为 Local PV 是节点本地卷,不支持多节点读写(RWX)。
页面没有详细警告或交互解释,但隐含:PVC 请求的存储大小必须 ≤ 节点 VG 剩余空间(你的每节点 20G VG),否则 provision 失败。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-lvm-pvc # 自定义名字
annotations: # 可选:加点描述,便于排查
volume.kubernetes.io/selected-node: k8s-node1 # 测试时强制指定节点(可选,调试用)
spec:
accessModes:
- ReadWriteOnce # 必须 RWO
resources:
requests:
storage: 5Gi # 从小开始测试(我们的 VG 20G,够用)
storageClassName: openebs-lvm-advanced # 用你之前创建的高级 SC 名(或 basic)
4.3 LVM部署Pod示例
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-lvm-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: openebs-lvm-advanced
resources:
requests:
storage: 2Gi
---
apiVersion: v1
kind: Pod
metadata:
name: fio
spec:
restartPolicy: Never
nodeSelector:
kubernetes.io/hostname: k8s-node1
containers:
- name: perfrunner
image: myweb:v1
command: ["/bin/sh"]
args: ["-c", "while true ;do sleep 50; done"]
volumeMounts:
- mountPath: /datadir
name: fio-vol
tty: true
volumes:
- name: fio-vol
persistentVolumeClaim:
claimName: test-lvm-pvc
# 验证
[root@k8s-master ~/openEBS]# kubectl get po,pvc
NAME READY STATUS RESTARTS AGE
pod/fio 1/1 Running 0 3m47s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
persistentvolumeclaim/local-hostpath-pvc Bound pvc-29e78edd-d851-49b2-9fbd-4f95bc625952 5G RWO openebs-hostpath <unset> 21h
persistentvolumeclaim/test-lvm-pvc Bound pvc-746225fe-1a80-4151-ab6f-9aeb92557375 2Gi RWO openebs-lvm-advanced <unset> 4m15s
[root@k8s-master ~/openEBS]#
4.4 LVM高级功能
4.4.1 FSGroup
核心目的是解决 非 root 用户/进程访问卷的权限问题:默认情况下,LVM 卷以 root 权限创建,非 root Pod 可能无法读写(权限拒绝)。通过 FSGroup 配置,CSI 驱动会在挂载时自动调整卷的所有者和权限,让指定 GID(组 ID)的进程能正常访问。
FSGroup 就好比给卷“换门锁”:默认卷是 root 专属,非 root Pod 敲门进不去(权限错误)。配置后,CSI 驱动会在 Pod 挂载卷时自动“改锁”,让指定组(GID)的用户能顺利进出。
为什么需要:
-
Kubernetes 安全最佳实践:很多应用以非 root 用户运行(runAsUser/runAsGroup)。
-
LVM 卷默认 chown 为 root:root,非 root 进程写文件会报 “Permission denied”。
-
FSGroup 解决这个:自动 chown/chmod 卷目录,让 fsGroup 指定 GID 拥有卷。
配置位置:
-
CSI Driver
(全局设置,控制驱动行为):
fsGroupPolicy-
有三个值:
-
None:啥都不改(非 root 必失败)。
-
ReadWriteOnceWithFSType:只有 RWO + 指定 fsType 时调整(LVM 下等同 File)。
-
File:总是调整权限(推荐,无条件)。
-
其他固定:attachRequired: false、podInfoOnMount: true。
-
-
Pod securityContext
(每个 Pod 单独指定):
-
fsGroup: 必须写,指定目标 GID(卷将 chown 为这个组)。
-
fsGroupChangePolicy
-
Always:每次挂载都强制改权限。
-
OnRootMismatch:只在根目录权限不匹配时改(更高效)。
-
-
常配合 runAsUser/runAsGroup 用。
-
4.4.1.1 官方案例 1:CSI Driver 配置
apiVersion: storage.k8s.io/v1
kind: CSIDriver
metadata:
name: local.csi.openebs.io # OpenEBS LVM 的固定驱动名
spec:
attachRequired: false
fsGroupPolicy: File # 推荐这个
podInfoOnMount: true
volumeLifecycleModes:
- Persistent
推荐将fsGroupChangePolicy修改为File,可以按照如下操作完成:
[root@k8s-master ~/openEBS]# kubectl get csidriver local.csi.openebs.io -o yaml # 查看当前 fsGroupPolicy
apiVersion: storage.k8s.io/v1
kind: CSIDriver
metadata:
annotations:
meta.helm.sh/release-name: openebs
meta.helm.sh/release-namespace: openebs
creationTimestamp: "2026-02-03T05:51:39Z"
labels:
app.kubernetes.io/managed-by: Helm
name: local.csi.openebs.io
resourceVersion: "67827"
uid: 1ea17b00-ed62-4da4-832c-1b1072f4f875
spec:
attachRequired: false
fsGroupPolicy: ReadWriteOnceWithFSType
podInfoOnMount: true
requiresRepublish: false
seLinuxMount: false
storageCapacity: true
volumeLifecycleModes:
- Persistent
[root@k8s-master ~/openEBS]# kubectl edit csidriver local.csi.openebs.io
...
找到fsGroupPlociy: File ## 修改为File
...
保存退出
改成 File 后,所有后续 LVM 卷挂载都会支持 FSGroup。
它会自动支持 FSGroup:当 Pod 指定 securityContext.fsGroup 时,OpenEBS LVM 驱动会在挂载卷时自动 chown/chmod 卷目录,让非 root 进程正常读写(解决 Permission denied)。
4.4.1.2 官方案例 2:Pod 配置(带 securityContext)
apiVersion: v1
kind: Pod
metadata:
name: security-context
spec:
securityContext:
runAsUser: 2000
runAsGroup: 3000
fsGroup: 6000 # 关键:卷将属于这个 GID
fsGroupChangePolicy: OnRootMismatch
containers:
- name: security-context
image: busybox
volumeMounts:
- name: data
mountPath: /data
# ... 其他
volumes:
- name: data
persistentVolumeClaim:
claimName: csi-volume
4.4.2 使用建议
-
立即检查你的 CSIDriver:大概率是 None,导致潜在权限坑。
-
测试:创建一个非 root Pod(runAsUser: 1000),不加 FSGroup 会失败;加了 + 驱动 File 就成功。
-
生产必配:配合 PodSecurityAdmission 或 securityContext 限制 root。
这个配置是非 root 安全运行的关键,页面就是为了解决这个常见痛点。如果你遇到权限错误(Permission denied),99% 是这里的问题!有输出贴我帮看。
4.5 Raw Block Volume(裸块设备卷)
4.5.1 核心区别于普通模式
-
默认是 Filesystem 模式(卷上自动格式化 ext4 等文件系统,Pod 用 volumeMounts 挂载目录)。
-
Raw Block 模式:不创建文件系统,直接提供裸块设备(/dev/xxx),应用自己管理分区、格式化、数据布局。
-
适用场景:数据库(如 MySQL、PostgreSQL)、软件定义存储、需要极致性能的工作负载(避免文件系统 overhead)。
-
好处:减少文件系统层开销,提升 I/O 性能;应用完全控制块设备。
-
限制:不支持 fsType 参数;必须用 volumeDevices(而非 volumeMounts);仍只支持 ReadWriteOnce(RWO);应用必须能处理裸设备。
页面提供完整 StorageClass、PVC、Deployment 示例,步骤清晰:创建专用 SC → PVC 设 volumeMode: Block → Pod 用 volumeDevices 指定 devicePath。
Raw Block Volume 就好比给你一块“裸硬盘”:不预装 Windows/Linux 文件系统,你自己决定怎么分区、格式化、存数据。
为什么用它(官方核心):
-
普通 Filesystem 模式:OpenEBS 自动在 LV 上 mkfs.ext4,Pod 挂载成目录(/datadir),方便但有文件系统开销(双缓存、权限管理等)。
-
Raw Block 模式:直接把 LV 暴露成块设备(如 /dev/xvda),应用直接读写块(零 overhead),适合数据库等高性能场景(它们自己有数据管理机制)。
配置关键(官方准确):
-
StorageClass:和普通 LVM 差不多,但不能写 fsType(因为无文件系统)。
-
PVC:必须加 volumeMode: Block(这是触发 Raw Block 的开关)。
-
Pod/Deployment:用 volumeDevices(而非 volumeMounts),指定 devicePath(如 /dev/xvda,应用就在这个路径操作裸设备)。
-
其他参数(如 volgroup/vgpattern、thinProvision)仍可用。
4.5 .2 流程
-
创建(或复用)LVM SC。
-
创建 PVC,设 volumeMode: Block。
-
部署 Pod,用 volumeDevices 挂载到指定 devicePath。
-
Pod 启动后,应用在 devicePath 上自己 mkfs、mount 或直接用(取决于应用)。
4.5.3 官方案例分析
# 官方案例 1:StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: openebs-lvm-block-sc # 自定义名字,带 block 标识
provisioner: local.csi.openebs.io
parameters:
storage: "lvm" # 必填(你之前 SC 已配)
vgpattern: "openebs-lvm-vg" # 用你的 VG,推荐(精确匹配)
thinProvision: "yes" # 保持开启,省空间
# fsType: 不写!Raw Block 不支持
allowVolumeExpansion: true
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer # 强烈推荐保持
# 官方案例 2:PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-lvm-block-pvc
spec:
volumeMode: Block # 必须加这个,才是 Raw Block
accessModes:
- ReadWriteOnce
storageClassName: openebs-lvm-block-sc # 用上面新 SC
resources:
requests:
storage: 5Gi
# 官方案例 3:Deployment
apiVersion: v1
kind: Pod
metadata:
name: test-lvm-block-pod
spec:
restartPolicy: Never
nodeSelector: # 推荐加,打破 WaitForFirstConsumer 循环
kubernetes.io/hostname: k8s-node1
containers:
- name: tester
image: busybox:latest # 方便 exec 进去测试
command: ["/bin/sh"]
args: ["-c", "while true; do sleep 30; done"]
volumeDevices: # 必须用这个
- devicePath: /dev/sdb # 自定义路径(如 /dev/myblock),应用在这里操作
name: block-vol
volumes:
- name: block-vol
persistentVolumeClaim:
claimName: test-lvm-block-pvc
核心在于:
volumeDevices: # 必须用这个
- devicePath: /dev/sdb # 自定义路径(如 /dev/myblock),应用在这里操作
name: block-vol
4.6 LVM卷扩容
卷扩容就好比“给硬盘加容量”:原来 4Gi 用满了,直接改申请为 5Gi,系统自动把底层 LV 拉大,并在线 resize 文件系统(应用继续跑,不中断)。
为什么能在线扩容(官方机制):
-
Kubernetes 标准特性 + OpenEBS CSI 驱动支持。
-
编辑 PVC → 控制器 resize LV(lvextend)。
-
节点插件(有 Pod 挂载时)执行 lvextend -r(-r 自动 resize 文件系统)。
-
必须有运行 Pod:否则文件系统不扩(PVC 卡 FileSystemResizePending),Pod 重启或新建后自动完成。
支持的文件系统(官方):
-
ext4(默认,在线扩容完美)。
-
xfs、btrfs(也支持,btrfs 在线需 K8s 1.23+)。
-
Raw Block 模式(在线需 K8s 1.23+,应用自己 resize)。
注意事项/警告(官方强调):
-
StorageClass 必须allowVolumeExpansion: true,否则扩容失败。
-
扩容后 PVC 会短暂显示 FileSystemResizePending(正常,等 Pod 触发)。
-
无 Pod 使用时,LV 已扩但文件系统未扩(df -h 仍显示旧大小),启动 Pod 后自动完成。
-
不支持缩容(Kubernetes 标准限制)。
4.6.1 官方案例 1:StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: openebs-lvmpv
allowVolumeExpansion: true # <--- 关键开关
parameters:
fstype: "ext4"
volgroup: "lvmpv-vg"
provisioner: local.csi.openebs.io
4.6.2 官方案例 2 & 3:PVC(初始 + 扩容后)
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: csi-lvmpv
spec:
storageClassName: openebs-lvmpv
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 4Gi
扩容后 PVC(只改 storage):
# ... 相同字段
resources:
requests:
storage: 5Gi # 增大这里
开启后假设你有一个 Bound 的 PVC(如之前的 test-lvm-pvc 2Gi):
编辑扩容(推荐方式,最简单):
kubectl edit pvc test-lvm-pvc # 直接改 requests.storage 为 5Gi(或更大,但 ≤ VG 剩余)
或者打补丁的方式
kubectl patch pvc test-lvm-pvc -p '{"spec":{"resources":{"requests":{"storage":"5Gi"}}}}'
4.7 快照与快照恢复
OpenEBS Local PV LVM 支持 快照(Snapshot) 和 从快照恢复(Restore) 高级操作,基于 Kubernetes VolumeSnapshot API 和 LVM 原生 snapshot 机制。
快照(Snapshot):
-
创建原卷的点-in-time 只读副本(默认 ReadOnly)。
-
前提:节点加载 dm-snapshot 内核模块;支持薄/厚卷(薄卷创建 thin snapshot)。
-
默认快照大小 = 原卷大小;可选 snapSize 参数自定义(百分比或绝对值)。
-
通过 VolumeSnapshotClass(driver: local.csi.openebs.io)和 VolumeSnapshot 创建。
-
限制:快照后原卷不能扩容(LVM 不自动调整 snapshot);快照必须与 PVC 同 namespace;deletionPolicy 默认 Delete。
恢复(Restore):
-
从快照创建新卷(clone),数据同快照时刻,新卷在原卷相同节点上,属于相同 VG。
-
通过 PVC 的 dataSource 引用 VolumeSnapshot 创建新 PVC。
-
前提:仅支持 thin snapshot(OpenEBS v4.0+,推荐 v4.4.0+);恢复卷大小必须精确匹配快照 LV 大小。
-
差异于动态 provision:固定节点/VG/大小;不支持恢复后扩容。
-
警告:thin pool 满可能导致写失败(建议配置 autoextend)。
整体:快照适合备份/克隆,恢复适合灾备或测试新卷。你的环境(thinProvision: "yes" + dm_thin_pool 已加载)完美支持 thin snapshot/restore,但需额外确认 dm-snapshot 模块。
警告合集:
-
快照存在时,原卷禁止扩容(官方故意阻止,避免 LVM 复杂性)。
-
恢复卷不能扩容;大小必须 = 快照 status.lvSize。
-
只支持 thin snapshot 恢复(你的 SC 已配)。
-
thin pool 满风险:配置 lvm.conf autoextend(VG 有余量时自动扩 pool)。
4.7.1 快照
4.7.1.1 官方案例:VolumeSnapshotClass(默认 + snapSize)
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: openebs-lvm-snapclass # 自定义
annotations:
snapshot.storage.kubernetes.io/is-default-class: "true" # 可设默认
driver: local.csi.openebs.io
deletionPolicy: Delete # 或 Retain(保留 snapshot 内容)
带 snapSize(自定义大小,创建厚 snapshot):
# ... 同上
parameters:
snapSize: 50% # 或 "5Gi" 绝对值
4.7.1.2 官方案例:VolumeSnapshot
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: test-lvm-snap # 同 PVC namespace
spec:
volumeSnapshotClassName: openebs-lvm-snapclass
source:
persistentVolumeClaimName: test-lvm-pvc # 你的现有 PVC
4.7.1.3 验证
[root@k8s-master ~/openEBS]# kubectl apply -f snapshotclass.yaml
volumesnapshotclass.snapshot.storage.k8s.io/openebs-lvm-snapclass created
[root@k8s-master ~/openEBS]# kubectl apply -f snapshot.yaml
volumesnapshot.snapshot.storage.k8s.io/test-lvm-snap created
[root@k8s-master ~/openEBS]# kubectl get volumesnapshot
NAME READYTOUSE SOURCEPVC SOURCESNAPSHOTCONTENT RESTORESIZE SNAPSHOTCLASS SNAPSHOTCONTENT CREATIONTIME AGE
test-lvm-snap true test-lvm-pvc 0 openebs-lvm-snapclass snapcontent-fd4f61e0-0a8b-4a83-9e9e-c36f7b963c64 4s 4s
[root@k8s-master ~/openEBS]# kubectl get lvmsnapshot -n openebs
NAME AGE
snapshot-fd4f61e0-0a8b-4a83-9e9e-c36f7b963c64 8s
[root@k8s-node1 ~]# lvs openebs-lvm-vg
LV VG Attr LSize Pool Origin Data% Meta% Move Log Cpy%Sync Convert
fd4f61e0-0a8b-4a83-9e9e-c36f7b963c64 openebs-lvm-vg Vri---tz-k 2.00g openebs-lvm-vg_thinpool pvc-746225fe-1a80-4151-ab6f-9aeb92557375
openebs-lvm-vg_thinpool openebs-lvm-vg twi-aotz-- 2.00g 4.79 12.11
pvc-746225fe-1a80-4151-ab6f-9aeb92557375 openebs-lvm-vg Vwi-aotz-- 2.00g openebs-lvm-vg_thinpool 4.79
4.7.2 恢复
4.7.2.1 官方案例:恢复 PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-lvm-restore-pvc # 同 namespace
spec:
storageClassName: openebs-lvm-advanced # 用你的现有 SC(需匹配 VG)
dataSource:
name: test-lvm-snap # 上面快照名
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi # 必须精确匹配快照大小(查 volumesnapshot status.restoreSize)
先查快照大小:kubectl get volumesnapshot test-lvm-snap -o yaml | grep restoreSize
用你的 SC(已配 thin + vgpattern: "openebs-lvm-vg",自动匹配 VG)。
恢复卷会在原 PVC 节点上创建(同 VG)。
# 验证:
kubectl get pvc test-lvm-restore-pvc
kubectl get lvmvolume -n openebs # 看到新 LVMVolume,spec.source 指向 snapshot
4.8 Thin Provisioning
4.8.1. 核心内容总结(官方定义)
精简置备(Thin Provisioning) 允许你“超额分配”存储资源。
-
传统方式 (Thick Provisioning): 你申请 2Gi,系统立刻从硬盘划走 2Gi 锁死,别人不能用,即便你只存了 1KB 的文件。
-
精简方式 (Thin Provisioning): 你申请 2Gi,系统先答应给你 2Gi,但实际写多少才占多少。就像信用额度,你有 100 万额度,但不代表你银行里真的存了 100 万。
4.8.2. 通俗易懂的解释
想象你在开一家健身房(存储池):
-
厚置备 (Thick): 每个会员来办卡,你都必须给他在更衣室永久锁死一个柜子。如果你只有 100 个柜子,办完 100 张卡你就不能再招人了,哪怕有些会员一年才来一次,柜子一直空着。
-
精简置备 (Thin): 你照样发会员卡,甚至发 200 张(超分配)。柜子是共享的,谁来健身(写数据)谁才占用柜子。只要不是 200 人同时来,你的健身房就能正常运转。
-
风险: 如果 200 人同时来了,柜子就不够用了(空间撑爆),会导致应用崩溃。所以需要监控。
-
4.8.3. 官方案例配置:
步骤 A:开启内核支持
操作:modprobe dm_thin_pool 分析:LVM 的精简配置依赖 Linux 内核的 dm_thin_pool 模块。这是“地基”,如果不加载这个模块,后续所有操作都会失败。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: openebs-lvm-advanced
allowVolumeExpansion: true
provisioner: local.csi.openebs.io
parameters:
volgroup: "lvmvg"
thinProvision: "yes" # 核心参数:开启精简置备
步骤 B:监控与自动扩容(进阶案例)
官方强调了自动扩容(Auto-extend),这是防止“挤兑”的关键。
配置案例(在节点的 /etc/lvm/lvm.conf 中):
Code snippet
thin_pool_autoextend_threshold = 75
thin_pool_autoextend_percent = 20
-
分析:
-
threshold = 75:意味着当精简池用了 75% 的时候,触发自动扩容。 -
percent = 20:触发后,池子的物理大小自动增加 20%。 -
注意:官方提醒,即便改了配置文件,你还需要执行
lvchange --monitor y <vg-name>/<pool-name>。如果不开启monitor,配置文件改了也没用。
五、Replicated PV Mayastor 核心概览
OpenEBS Replicated PV Mayastor 是一个高性能、分布式的块存储引擎,用于在 Kubernetes 中提供 同步数据冗余(多副本)与高可用性(HA)。它适合 关键任务工作负载,如数据库或其他对数据持久性要求非常高的应用。
5.1 什么是 Replicated PV Mayastor
Replicated PV Mayastor(简称 Mayastor) 是 OpenEBS 的一个存储引擎,其核心特点是:
-
块存储(Block Storage):不像目录级别的数据卷,而是底层块设备级别的存储。
-
多副本同步(Replicated):同一个数据卷的多个副本同时存在于不同节点,提高容错能力。
-
高可用(High Availability):节点故障时数据仍然可用,不会直接导致存储不可访问。
-
Kubernetes 原生集成:通过 CSI(Container Storage Interface)与 Kubernetes 调度协同工作,实现 PV/PVC 动态分配。
-
性能友好:采用 Rust 编写,面向 NVMe 等高速设备,具有低延迟、高吞吐的存储性能。
5.2 适用于哪些场景
Replicated PV Mayastor 主要用于解决 Kubernetes 本地存储在容错与持久性方面的局限:
-
容错问题:单机 Local PV(如 hostPath)一旦节点故障,数据不可用。
-
高可用:通过同步复制多个副本,在节点故障时仍然保证数据可以在线访问。
-
数据一致性:复制层保证数据的一致性,即使发生写入故障也不会丢失。
5.3 Mayastor 的主要优势
5.3.1 高可用存储(Highly Available Storage)
-
数据跨多个节点进行同步复制
-
节点故障不影响卷可用性
-
服务在多副本之间自动容错恢复
📌 结果:应用无需自己实现副本逻辑即可享受 HA 能力。
5.3.2 性能优化(Performance‑Optimized)
-
内置 Rust 语言实现(安全 + 性能)
-
面向 NVMe / block 设备优化
-
更低 IO 延迟、更高吞吐量
📌 适用于性能敏感场景,例如数据库、缓存等需要高 IO 的应用。
5.3.3 节点下降恢复能力(Node Failure Resiliency)
当某个节点不可用:
-
Mayastor 会自动将该节点的副本从集群中剔除
-
并确保剩余副本继续对外提供数据服务
📌 这大大提升了生产级容错能力。
5.4 Mayastor 与 OpenEBS LocalPV 的对比
| 特性 | LocalPV | Mayastor Replicated |
|---|---|---|
| 数据副本 | ❌ 单一 | ✅ 多副本 |
| HA 容错 | ❌ 无 | ✅ 有 |
| 节点扩展 | ❌ 数据锁定节点 | ✅ 可重复调度 |
| 性能开销 | ⭐⭐⭐ | ⭐⭐(因 replication) |
| Kubernetes 原生 | ✔ | ✔ |
| 适合场景 | 本地临时数据 | 关键任务持久存储 |
六、安装运行Replicated PV Mayastor
6.1 安装前置准备
这个是 OpenEBS 用于高可靠性和高性能的引擎,对环境要求更严格:
-
通用要求
-
x86-64 CPU,支持 SSE4.2 指令集;
-
推荐 Linux kernel ≥ 5.15;
-
节点必须满足 SPI-NVMe、ext4/xfs 等模块加载;
-
Helm 版本 ≥ v3.7(安装 Mayastor 组件用)。
-
-
资源要求
-
Io-engine pod 每个节点至少:
-
2 个 CPU 核心
-
1GiB RAM
-
支持 HugePages(大页)(至少 2GiB 2MiB 页面)。
-
-
-
网络要求
-
确保某些端口不被占用,如 gRPC 服务端口 10124;
-
如果用 NVMe-oF TCP 协议,必须启用并保证连接无防火墙阻断。
-
-
集群规模
-
至少 3 个 worker 节点(尤其使用复制/镜像卷时)。
-
-
协议限制
-
Replicated PV Mayastor 只支持 NVMe-oF over TCP 方式挂载卷。
-
NVMe-oF TCP检查(所有节点)
lsmod | grep nvme_tcp
如果没有:
modprobe nvme_tcp
# 永久生效
[root@k8s-master ~]# echo "nvme_tcp" > /etc/modules-load.d/nvme_tcp.conf
[root@k8s-master ~]# cat /etc/modules-load.d/nvme_tcp.conf
nvme_tcp
[root@k8s-master ~]# systemctl daemon-reload && systemctl restart systemd-modules-load.service
[root@k8s-master ~]# lsmod | grep nvme_tcp
nvme_tcp 90112 0
nvme_fabrics 36864 1 nvme_tcp
nvme_core 241664 2 nvme_tcp,nvme_fabrics
nvme_keyring 20480 3 nvme_tcp,nvme_core,nvme_fabrics
为Mayastor再次添加一块20G大小的磁盘,每一个参与副本调度的节点都必须具备至少一块独立的裸块设备。
[root@k8s-master ~]# lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 100G 0 disk
├─sda1 8:1 0 1M 0 part
├─sda2 8:2 0 2G 0 part /boot
└─sda3 8:3 0 98G 0 part
└─ubuntu--vg-ubuntu--lv 252:0 0 49G 0 lvm /var/lib/kubelet/pods/aa8670d2-3e92-418a-bebc-50e13b484160/volume-subpaths/tigera-ca-bundle/calico-node/6
/
sdc 8:16 0 20G 0 disk
[root@k8s-node2 ~]# mkfs.ext4 /dev/sdb
mke2fs 1.47.2 (1-Jan-2025)
Creating filesystem with 5242880 4k blocks and 1310720 inodes
Filesystem UUID: 242e50b2-91c5-46cc-8387-c14510cd2c4b
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
4096000
Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done
[root@k8s-node2 ~]# ls -l /dev/disk/by-uuid/ |grep sdb
lrwxrwxrwx 1 root root 9 Feb 3 09:07 242e50b2-91c5-46cc-8387-c14510cd2c4b -> ../../sdb
HugePages配置,所有节点
echo 1024 | sudo tee /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
# 验证
[root@k8s-master ~]# grep Huge /proc/meminfo
AnonHugePages: 0 kB
ShmemHugePages: 0 kB
FileHugePages: 20480 kB
HugePages_Total: 1024
HugePages_Free: 1024
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
Hugetlb: 2097152 kB
# 设置为永久
vi /etc/sysctl.conf
加入:
vm.nr_hugepages = 1024
应用:
[root@k8s-master ~]# sysctl -p
vm.nr_hugepages = 1024
验证
[root@k8s-master ~]# kubectl describe node k8s-node1 | grep -i hugepages-2Mi
hugepages-2Mi: 2Gi
hugepages-2Mi: 2Gi
hugepages-2Mi 256Mi (12%) 256Mi (12%)
如果是0则需要重启所有节点的kubelet
[root@k8s-master ~]# systemctl restart kubelet
[root@k8s-master ~]# systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; preset: enabled)
Drop-In: /usr/lib/systemd/system/kubelet.service.d
└─10-kubeadm.conf
Active: active (running) since Tue 2026-02-03 10:56:40 CST; 10s ago
Invocation: 84fbb8e199474a39b8920f033a575434
Docs: https://kubernetes.io/docs/
Main PID: 9263 (kubelet)
Tasks: 12 (limit: 5114)
Memory: 70.5M (peak: 108.3M)
CPU: 3.466s
CGroup: /system.slice/kubelet.service
└─9263 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml ->
Feb 03 10:56:47 k8s-master kubelet[9263]: I0203 10:56:47.510172 9263 reconciler_common.go:251] "operationExecutor.VerifyControllerAttachedVolume started for volume \"ca-certs\" >
Feb 03 10:56:47 k8s-master kubelet[9263]: I0203 10:56:47.510192 9263 reconciler_common.go:251] "operationExecutor.VerifyControllerAttachedVolume started for volume \"kubeconfig\>
Feb 03 10:56:47 k8s-master kubelet[9263]: I0203 10:56:47.510201 9263 reconciler_common.go:251] "operationExecutor.VerifyControllerAttachedVolume started for volume \"usr-local-s>
Feb 03 10:56:47 k8s-master kubelet[9263]: I0203 10:56:47.510217 9263 reconciler_common.go:251] "operationExecutor.VerifyControllerAttachedVolume started for volume \"usr-share-c>
Feb 03 10:56:47 k8s-master kubelet[9263]: I0203 10:56:47.510231 9263 reconciler_common.go:251] "operationExecutor.VerifyControllerAttachedVolume started for volume \"etcd-data\">
Feb 03 10:56:47 k8s-master kubelet[9263]: I0203 10:56:47.510240 9263 reconciler_common.go:251] "operationExecutor.VerifyControllerAttachedVolume started for volume \"k8s-certs\">
Feb 03 10:56:47 k8s-master kubelet[9263]: I0203 10:56:47.941825 9263 kubelet_node_status.go:124] "Node was previously registered" node="k8s-master"
Feb 03 10:56:50 k8s-master kubelet[9263]: I0203 10:56:50.158212 9263 operation_generator.go:557] "MountVolume.MountDevice succeeded for volume \"pvc-4dbad85b-bf40-43b2-85f6-cc1e>
Feb 03 10:56:50 k8s-master kubelet[9263]: E0203 10:56:50.159466 9263 kubelet.go:3196] "Failed creating a mirror pod" err="pods \"kube-controller-manager-k8s-master\" already exi>
Feb 03 10:56:50 k8s-master kubelet[9263]: I0203 10:56:50.159492 9263 kubelet.go:3194] "Creating a mirror pod for static pod" pod="kube-system/kube-scheduler-k8s-master"
为所有K8S集群的节点打上存储标签
[root@k8s-master ~/openEBS]# kubectl label node k8s-master k8s-node1 k8s-node2 openebs.io/engine=mayastor
6.2 使用Helm安装应用
# 只安装 Replicated Storage(Mayastor)
helm install openebs openebs/openebs \
--namespace openebs \
--create-namespace \
--set engines.local.hostpath.enabled=false \
--set engines.local.lvm.enabled=false \
--set engines.local.zfs.enabled=false \
--wait \
--timeout 15m
6.2 典型工作流程
1) 创建 DiskPool(块设备池)
-
在每个支持 Mayastor 的节点上声明一个或多个 DiskPool
-
实际由单个块设备(如 NVMe / SSD / raw disk)提供容量
📌 DiskPool 是 Mayastor 卷的存储基础,用于分配副本存储空间。
2) 创建 Replicated StorageClass
你可以定义 StorageClass 来指定:
-
protocol(如 nvmf)
-
repl(副本数,例如 2 或 3)
-
thin / thick provisioning
-
fsType(文件系统类型)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: mayastor-3
parameters:
protocol: nvmf
repl: "3"
provisioner: io.openebs.csi-mayastor
3) PVC → Pod 流程
-
创建 PVC,指定上面的 StorageClass
-
CSI driver 调用 Mayastor 服务创建逻辑卷
-
Mayastor 会在多个节点上创建副本并同步
-
Pod 挂载卷并正常读写
-
如果节点故障,副本仍然在线提供数据服务
📌 整个过程由 Kubernetes 与 CSI 协议统一管理,无需手工操作。
6.3 创建DiskPool
创建 DiskPool(存储池)DiskPool 是 Mayastor 卷的物理基础,用于管理节点上的块设备。
# 节点 k8s-master 的存储池
apiVersion: "openebs.io/v1beta3"
kind: DiskPool
metadata:
name: pool-k8s-master
namespace: openebs
spec:
node: k8s-master
disks: ["aio:///dev/disk/by-uuid/746b2ce7-c116-4b5a-b860-ef0b17340558"]
---
# 节点 k8s-node1 的存储池
apiVersion: "openebs.io/v1beta3"
kind: DiskPool
metadata:
name: pool-k8s-node1
namespace: openebs
spec:
node: k8s-node1
disks: ["aio:///dev/disk/by-uuid/38821f23-8e48-4d1a-87ab-99320cd4306a"]
---
# 节点 k8s-node2 的存储池
apiVersion: "openebs.io/v1beta3"
kind: DiskPool
metadata:
name: pool-k8s-node2
namespace: openebs
spec:
node: k8s-node2
disks: ["aio:///dev/disk/by-uuid/242e50b2-91c5-46cc-8387-c14510cd2c4b"]
[root@k8s-master ~/openEBS]# kubectl apply -f DiskPool.yaml
diskpool.openebs.io/pool-k8s-master created
diskpool.openebs.io/pool-k8s-node1 created
diskpool.openebs.io/pool-k8s-node2 created
kubectl get dsp -n openebs
必须确保三个池的 STATE 都是 Online。
-
nodeSelector:指定磁盘所属节点 -
disks:裸块设备路径 -
每个节点可以有多个 DiskPool
📌 解释:DiskPool 就是存储池,每个节点挂一块设备,Mayastor 卷可以跨池创建副本。
6.4 创建SC
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: mayastor-3-repl
provisioner: openebs.io/logging
parameters:
repl: "3" # 副本数量
protocol: nvmf # 数据传输协议
# 如果你以后想给池加标签进行精细化管理,可以开启下面的参数
# poolHasTopologyKey: "openebs.io/engine"
allowVolumeExpansion: true
reclaimPolicy: Delete
volumeBindingMode: Immediate
-
provisioner:
openebs.io/logging是 Mayastor 4.x 默认的存储驱动名称。repl: "3": 这意味着当你创建一个 PVC 时,Mayastor 会自动在你的三个
DiskPool上各创建一个副本。protocol: nvmf: 使用 NVMe-over-Fabrics 协议。因为你之前已经加载了
nvme_tcp模块,所以这里用这个协议性能最好。volumeBindingMode: Immediate: 立即绑定。对于分布式存储,这能确保 PVC 创建后立刻完成调度。
高级调度参数 (Placement & Topology)
这些参数决定了副本“落在哪里”:
-
poolHasTopologyKey: 如果你给DiskPool打了自定义标签(例如topology.kubernetes.io/zone=zone1),设置此参数可以让 Mayastor 优先选择带有该标签的池。 -
affinityGroup: 用于确保属于同一组的卷副本在物理上尽可能分散或聚合。 -
nodeSelector/poolSelector: 通过标签过滤特定的节点或存储池。例如:只想让数据库运行在带 SSD 的节点上。
6.5 创建PVC
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mayastor-test-pvc
spec:
storageClassName: mayastor-3-repl
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
EOF
6.6 创建测试Pod
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: storage-test-app
spec:
containers:
- name: writer
image: alpine
command: ["/bin/sh", "-c"]
args:
- while true; do
date >> /mnt/data/test-log.txt;
echo "Data written to Mayastor volume";
sleep 5;
done
volumeMounts:
- name: my-volume
mountPath: /mnt/data
volumes:
- name: my-volume
persistentVolumeClaim:
claimName: mayastor-test-pvc
EOF
📌 解释:Pod 挂载 PVC,Mayastor 会在多个节点上创建副本,实现高可用存储。
核心注意事项
-
副本数 ≤ 节点数
-
如果集群只有 2 节点,repl=3 会失败
-
-
裸块设备
-
DiskPool 必须使用未挂载的块设备(不能是 /var/lib/kubelet/pods/ 已挂载卷)
-
-
文件系统
-
Mayastor 可以直接提供块设备给 Pod,PVC 可以选择
xfs或ext4
-
-
扩容
-
StorageClass 开启
allowVolumeExpansion: true,PVC 可在线扩容
-
-
高可用
-
Pod 调度节点失败或节点故障,不影响卷读写.
-
| 官方术语 | 通俗理解 |
|---|---|
| DiskPool | 节点物理块设备池 |
| Mayastor Volume | 多副本存储卷(PV) |
| StorageClass | 卷模板,指定副本、协议、文件系统等 |
| repl | 副本数 |
| nvmf / iscsi | 块设备访问协议 |
| WaitForFirstConsumer | 卷绑定在 Pod 调度节点时创建 |
七、Replicated PV Mayastor高级特性
7.1 高可用性
7.1.1 Mayastor HA 的核心组件
Mayastor 实现“故障无感”切换主要靠两个机制:
-
Nexus(控制节点):卷的“指挥中心”,负责接收 I/O 并分发给各个副本。
-
High Availability Storage Agent (ha-node):在每个节点上运行,负责监控路径健康状况并在主节点失效时快速切换。
7.1.2 HA 的关键工作流程
当一个存储节点挂掉时,Mayastor 会按以下步骤操作:
-
检测(Detection):控制平面发现某个副本无法连接。
-
标记(Marking):该副本被标记为
Faulted。 -
IO 重新路由:如果挂掉的是主节点(Nexus 所在节点),CSI 会迅速在另一个健康的节点上重建 Nexus,Pod 的 I/O 自动切到新路径。
-
自动修复(Self-healing):一旦坏节点恢复或新节点加入,Mayastor 会自动从健康的副本开始 Rebuild(数据重构),直到副本数再次达到
repl设定的值。
7.1.3 HA 相关的重要 SC 参数
在 StorageClass 中,有两个高级参数可以微调 HA 行为:
| 参数 | 建议值 | 说明 |
|---|---|---|
local |
true |
本地优先。Nexus 会优先创建在 Pod 所在的节点上,减少网络开销。 |
rebuildOrder |
0 |
重构优先级。数值越小,当发生故障时,该卷重构的优先级越高。 |
常用维护操作
A. 查看卷的健康详细状态
不仅仅是 kubectl get pvc,你需要看 Mayastor 专属的资源状态:
# 查看所有 Mayastor 卷及其健康状态 kubectl get mayastorvolumes -n openebs # 查看更详细的副本分布和 Nexus 状态 kubectl describe mnv -n openebs <volume-name>
B. 模拟故障测试 (Chaos Testing)
你可以手动删掉一个 io-engine Pod 来模拟节点宕机:
-
观察
kubectl get mnv,你会发现卷的状态变为Degraded(降级)。 -
观察你的测试应用,由于有 3 副本,读写应该 不会中断。
-
恢复 Pod 后,观察状态变为
Online,说明重构完成。
最佳实践建议
副本数 >=3:这是 HA 的基础,保证在“一节点维护+一节点突发故障”时数据依然安全。
网络延迟:HA 依赖节点间的实时同步,确保你的三台虚拟机之间是万兆网或极低延迟的内网。
不要在 Master 节点运行 I/O 密集型应用:虽然你现在是三节点全开,但在大规模环境下,建议将存储节点和计算节点适当分离。
7.2 Kubectl 插件
OpenEBS 官方提供了一个名为 mayastor 的 kubectl 插件,它是运维 Mayastor 的“瑞士军刀”。
以下是对该插件核心功能和常用命令的深度总结:
虽然 Mayastor 的对象(如 DiskPool, Volume)是 K8s 资源,但很多底层细节(如 Nexus 内部状态、副本同步进度、SPDK 指标)在标准命令下是不可见的。该插件直接与控制平面通信,提供 存储视角 的实时数据。
7.2.1 核心查询命令总结
| 维度 | 命令 | 用途 |
|---|---|---|
| 存储节点 | kubectl mayastor get nodes |
查看所有存储节点的状态、gRPC 端点。 |
| 存储池 | kubectl mayastor get pools |
查看池的物理容量、已用空间、节点归属。 |
| 存储卷 | kubectl mayastor get volumes |
最常用。查看卷的健康状态(Online, Degraded)、副本数。 |
| 副本细节 | kubectl mayastor get replicas |
查看具体每个副本在哪个节点、哪个盘上。 |
进阶诊断命令
当你遇到 PVC 挂载不上或者 IO 报错时,以下命令能救命:
A. 查看卷的拓扑结构
kubectl mayastor get volume-spec <volume-uuid>
这能展示这个卷的 Nexus 运行在哪个节点,以及它的 Replica 分布在哪些节点。
B. 动态调整副本数
如果发现磁盘空间不足或想提高冗余,可以动态修改:
# 将副本数从 3 改为 2 kubectl mayastor scale volume <volume-uuid> --replica-count 2
故障处理实战场景
场景 1:磁盘坏了怎么办? 使用插件将坏盘上的副本标记为故障,并触发重新同步: kubectl mayastor fault replica <uuid>
场景 2:副本同步卡住了? 查看卷的详细状态,可以看到 Rebuild 进度百分比,这是原生 kubectl 绝对看不到的信息。
7.2.2 安装方法
# 官方推荐通过二进制下载
curl -L https://github.com/openebs/mayastor-control-plane/releases/download/v2.9.3/kubectl-mayastor-x86_64-linux-musl.tar.gz
tar -xvf kubectl-mayastor-x86_64-linux-musl.tar.gz
mv kubectl-mayastor /usr/local/bin/
kubectl-mayastor 插件是连接“K8s 逻辑层”和“存储物理层”的桥梁。
7.3 快照(Snapshot)
7.3.1. 核心概念与先决条件
在 Mayastor 中,快照的操作遵循 Kubernetes 标准的 CSI Snapshotter 工作流。
-
瞬时性:快照是几乎瞬间完成的,不会由于卷的大小而增加创建时间。
-
依赖项:你必须先配置好
VolumeSnapshotClass。 -
空间占用:初次创建几乎不占空间,只有当原卷(Source Volume)发生数据变化时,快照才会开始占用存储池空间。
7.3.2. 关键资源对象 (CRDs)
快照涉及三个互相协作的 K8s 资源:
-
VolumeSnapshotClass:快照的“模版”,定义由谁(Mayastor)来执行快照。
-
VolumeSnapshot:用户的请求,类似于 PVC。
-
VolumeSnapshotContent:实际存储在磁盘上的快照数据,类似于 PV。
7.3.3. 实战配置步骤
A. 创建 VolumeSnapshotClass
首先,你需要告诉 K8s 如何处理 Mayastor 的快照请求:
cat <<EOF | kubectl apply -f -
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: mayastor-snapshot-class
driver: openebs.io/logging
deletionPolicy: Delete
EOF
B. 为你的测试卷创建快照
假设你之前创建的 PVC 叫 mayastor-test-pvc:
cat <<EOF | kubectl apply -f -
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: test-pvc-snapshot
spec:
volumeSnapshotClassName: mayastor-snapshot-class
source:
persistentVolumeClaimName: mayastor-test-pvc
EOF
7.3.4. 核心功能总结表
| 操作 | 场景 | 命令示例 |
|---|---|---|
| 创建快照 | 数据更新前备份、创建检查点 | kubectl apply -f snapshot.yaml |
| 查看状态 | 确认快照是否准备就绪 | kubectl get volumesnapshot |
| 快照回滚 | 数据损坏,需要恢复 | 不支持原地覆盖,需通过快照创建新 PVC。 |
| 克隆/恢复 | 从快照创建一个一模一样的新卷 | 在新 PVC 的 dataSource 中指定快照。 |
| 删除快照 | 从集群中将快照移除 | kubectl delete volumesnapshot mayastor-pvc-snap |
7.4 Snapshot Restore快照恢复
在 Kubernetes 的 CSI 标准(包括 Mayastor)中,"恢复"的概念通常指的是 "从快照创建一个新卷",而不是直接回滚旧卷。
7.4.1. 核心概念 (Core Concepts)
-
技术定义: 快照恢复(Snapshot Restore)本质上是一个 Provisioning(资源调配) 过程。它创建一个 全新 的 PersistentVolumeClaim (PVC),并预先填充特定 VolumeSnapshot 中的数据。
-
通俗解释: Mayastor 不支持“时光倒流”(即直接把原来的盘回滚到以前的状态)。 它的“恢复”就像是 “基于模版复印”:你拿着一张快照(底片),去申请一块新的硬盘,这块新硬盘里出厂就自带了和快照一模一样的数据。
-
关键机制:
-
非原地覆盖 (No In-place Restore):恢复操作不会影响原始的 PVC。
-
数据源引用 (DataSource Reference):在创建新 PVC 时,通过字段指定数据来源为某个快照。
-
7.4.2. 核心操作步骤 (Procedure)
整个过程只需要一步:创建一个新的 PVC 对象,但在配置中多加一个 dataSource 参数。
关键知识点:
-
Namespace 一致性:恢复出来的 PVC 必须和快照(VolumeSnapshot)在同一个命名空间(Namespace)下。
-
容量限制:新申请的 PVC 容量(
resources.requests.storage)必须 大于或等于 快照原始卷的容量。你不能把 10G 的数据恢复到 5G 的盘里。 -
StorageClass:你可以使用与原卷相同的 StorageClass,也可以使用不同的(只要该 SC 支持 Mayastor)。
7.4.3. 官方标准案例 (Official Example)
这是从快照恢复数据的标准 YAML 配置。
假设你已经有一个名为 test-pvc-snapshot 的快照(由上一步生成),现在我们要从中恢复出一个叫 restored-pvc 的新卷。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: restored-pvc # 1. 新卷的名称
namespace: default # 2. 必须与快照在同一命名空间
spec:
storageClassName: mayastor-3-repl # 3. 使用的存储类(通常与原卷一致)
dataSource: # 4. [核心] 指定数据来源
name: test-pvc-snapshot # 这里写快照的名字
kind: VolumeSnapshot # 类型必须是 VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi # 5. 容量必须 >= 原卷容量
7.4.4. 验证恢复结果 (Verification)
当你应用上述 YAML 后,Kubernetes 会执行以下流程:
-
Provisioning: CSI 驱动读取快照数据。
-
Binding: 创建一个新的 PV 并绑定到
restored-pvc。 -
Ready: 当 PVC 状态变为
Bound时,表示数据已准备好。
kubectl get pvc restored-pvc
如果看到 `STATUS: Bound`,说明恢复成功。
此时,你可以创建一个新的 Pod 挂载这个 `restored-pvc`,里面的数据就是你快照时刻的数据。
7.5 Encryption (数据加密)
这是根据 OpenEBS Mayastor 4.3.x 官方文档关于 Encryption (数据加密) 的深度笔记总结。
OpenEBS Mayastor 的加密方案主要针对 静态数据加密 (Data-at-Rest Encryption),也就是确保存储在物理磁盘上的数据是加密的,即便硬盘被盗也无法直接读取数据。
7.5.1. 核心概念 (Core Concepts)
-
技术定义: Mayastor 使用 AES-XTS 算法 对 DiskPool (存储池) 进行加密。
-
通俗解释: 它不是对每个文件单独加密,而是给整个“存储仓库”(DiskPool)加了一把锁。 只要你把这个仓库(DiskPool)配置成加密模式,那么所有存放进去的“货物”(Volume Replicas / 卷副本)都会自动被加密。
-
关键机制:
-
池级加密 (Pool-Level Encryption):加密是在存储池层面配置的,而不是在每个 PVC 上单独配密钥。
-
AES-XTS Cipher:一种专门用于磁盘加密的高强度算法,需要两个 128 位的密钥。
-
StorageClass 约束:你需要告诉 StorageClass,“我这个卷必须放到加密的池子里去”。
-
7.5.2. 核心操作流程 (Procedure)
要实现加密存储,需要三个步骤:配钥匙 (Secret) -> 建仓库 (DiskPool) -> 存货物 (StorageClass)。
第一步:创建密钥 Secret (Create Encryption Secret)
你需要创建一个 Kubernetes Secret 来存放 AES-XTS 密钥。
-
要求:包含两个 128 位的 Key(十六进制编码)。
-
注意:官方建议将此 Secret 设置为
immutable: true(不可变),防止误改。
第二步:配置加密 DiskPool (Configure Encrypted DiskPool)
在创建 DiskPool 时,引用上面的 Secret。这样这个池子里的所有数据都会被加密。
第三步:定义加密 StorageClass (Define Encrypted StorageClass)
创建一个新的 StorageClass,并强制要求使用加密池。
7.5.3. 官方标准案例 (Official Examples)
1. 创建密钥 Secret
apiVersion: v1
kind: Secret
metadata:
name: pool-encr-secret
namespace: openebs # 假设你的 Mayastor 运行在 openebs 命名空间
type: Opaque
immutable: true
stringData:
encryption_parameters: |
{
"cipher": "AesXts",
"key": "2b7e151628aed2a6abf7158809cf4f3c",
"key_len": 128,
"key2": "2b7e151628aed2a6abf7158809cf4f3d",
"key2_len": 128
}
注:上面的 key 仅为官方示例,生产环境请务必生成随机的高强度密钥。
2. 创建引用该密钥的 DiskPoo
apiVersion: "openebs.io/v1beta3"
kind: DiskPool
metadata:
name: encrypted-pool-1
namespace: openebs
spec:
node: k8s-node-1
disks: ["/dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_drive-scsi1"]
encryptionConfig: # [核心] 引用加密配置
source:
secret:
name: pool-encr-secret
3.创建强制加密的 StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: mayastor-encrypted-sc
parameters:
protocol: nvmf
repl: "2"
encrypted: "true" # [核心] 必须设为 true
provisioner: openebs.io/logging
reclaimPolicy: Delete
7.5.4. 核心笔记与限制 (Key Takeaways & Limitations)
-
密钥轮换 (Key Rotation):不支持。目前一旦池创建,密钥就不能换。
-
迁移 (Migration):
-
如果你已经有一堆没加密的数据,不能自动迁移到加密池。
-
手动迁移方案:必须新建一个加密池,然后把旧卷的副本扩容(Scale Up)到新池,再把旧池的副本缩容(Scale Down)删掉。过程比较繁琐且需要谨慎操作(建议先备份快照)。
-
-
安全性:DiskPool 的加密依赖于那个 K8s Secret。如果 Secret 泄露,加密也就形同虚设。建议结合 K8s 自身的 ETCD 加密功能来保护这个 Secret。
7.6 Velero数据迁移
使用 Velero 进行数据迁移
这项技术主要用于将旧的 OpenEBS 引擎(如 cStor, Jiva)或其他存储的数据,迁移到新的 Replicated Storage (Mayastor) 引擎中。
7.6.1. 核心概念 (Core Concepts)
-
技术定义: 利用 Velero 配合 Restic(或 Kopia)组件,执行 文件系统级别 (File System Level) 的备份与恢复,从而实现跨存储引擎的数据迁移。
-
通俗解释: 因为旧的存储(如 cStor)和新的存储(Mayastor)底层格式不一样,不能直接把硬盘“插”过去。 这个方法相当于:先把旧硬盘里的文件全部打包上传到云端(S3/MinIO),然后在新的硬盘里把文件下载下来。
-
适用场景:
-
从旧版 OpenEBS 迁移到 Mayastor。
-
分布式数据库(如 Cassandra, MongoDB)的迁移。
-
跨集群迁移。
-
7.6.2. 核心组件与机制 (Mechanisms)
-
Velero:Kubernetes 备份和恢复的标准工具,作为“指挥官”。
-
Restic:Velero 的插件,负责具体的“搬运工作”,深入到 Pod 内部去复制文件。
-
Execution Hooks (执行钩子):这是分布式数据库备份的关键。
-
为什么需要? 数据库通常会在内存里缓存数据,直接复制文件会导致数据损坏(不一致)。
-
怎么做? 在备份前“冻结”数据库(Pre-hook),备份完后“解冻”数据库(Post-hook),确保数据静止且一致。
-
-
StorageClass Mapping (存储类映射):
-
恢复时,你需要告诉 Velero:“原本是用
cstor-sc的数据,请恢复到mayastor-3-repl去”。
-
7.6.3. 操作流程笔记 (Step-by-Step Procedure)
整个过程分为三个阶段:准备 -> 备份 (Backup) -> 恢复 (Restore)。
第一阶段:准备工作
-
安装 Velero 客户端和服务端,并配置好后端存储(如 MinIO 或 AWS S3)。
-
重要:必须启用
--use-restic(或--use-node-agent在新版中),因为我们需要备份 PV 里的文件,而不是快照。
第二阶段:备份 (Backup for Distributed DB)
核心在于处理数据一致性。
-
Annotate Pods (添加注解): 你需要给数据库的 Pod 打上标签,告诉 Velero 哪个卷需要备份。
-
格式:
backup.velero.io/backup-volumes: <volume-mount-name>
-
-
配置 Hooks (配置冻结钩子): 定义备份前和备份后的动作。通常涉及
fsfreeze命令。 -
执行备份: 运行 Velero 备份命令。
第三阶段:恢复 (Restore to New Storage)
核心在于“偷梁换柱”,把底层存储换成 Mayastor。
-
配置映射 (Configure Mapping): 创建一个 ConfigMap,定义
old-storage-class: new-storage-class。 -
执行恢复: 运行 Velero 恢复命令,Velero 会自动创建新的 PVC(使用 Mayastor),并将数据从 S3 拉取进去。
7.6.4. 官方标准案例 (Official Example)
以下案例展示了如何迁移一个 Cassandra 数据库(StatefulSet)。
步骤 A: 备份配置 (Backup)
1. 给 Pod 添加注解 假设 Cassandra 的数据卷挂载名为 cassandra-data。
kubectl -n cassandra annotate pod cassandra-0 backup.velero.io/backup-volumes=cassandra-data
2. 定义 Hooks (在备份命令中或通过注解)
为了保证数据一致性,我们需要在备份前调用 nodetool flush 将内存数据刷入磁盘。 (注:官方文档通常建议使用注解方式定义 Hook,以下是标准逻辑)
# 这是一个逻辑示意,实际配置通常写在 Pod 注解中
pre.hook.backup.velero.io/command: '["/bin/bash", "-c", "nodetool flush"]'
pre.hook.backup.velero.io/container: cassandra
执行备份命令
velero backup create cassandra-backup \
--include-namespaces cassandra \
--default-volumes-to-restic=true
步骤 B: 恢复配置 (Restore)
1. 创建 StorageClass 映射配置 (ConfigMap) 我们要把原本的存储(假设是 openebs-cstor)映射到新的 mayastor-3-repl。
apiVersion: v1
kind: ConfigMap
metadata:
name: change-storage-class-config
namespace: velero
labels:
velero.io/plugin-config: ""
velero.io/change-storage-class: RestoreItemAction
data:
openebs-cstor: mayastor-3-repl # [核心] 左边是旧SC,右边是新SC
执行恢复命令
velero restore create --from-backup cassandra-backup \
--namespace-mappings cassandra:cassandra-new # 可选:恢复到新命名空间
7.6.5. 核心笔记总结 (Key Takeaways)
-
本质:这是文件级迁移,不是块级迁移。
-
关键点:分布式数据库必须配置 Hooks (fsfreeze/flush),否则恢复出来的数据可能坏掉。
-
映射:恢复时必须通过 ConfigMap 做 StorageClass 的映射,这是从旧引擎切换到 Mayastor 的关键步骤。
-
限制:由于是文件拷贝,速度取决于数据量大小和网络带宽,比快照恢复要慢。
7.7 可观测性监控
7.7.1. 核心概念 (Core Concepts)
-
技术定义: Mayastor 监控体系是基于 Prometheus 架构的,通过
mayastor-obs-call-home和mayastor-obs-exporter两个核心组件,将分布式存储的内部状态转化为时间序列数据。 -
通俗解释: 如果说“可观测性”是设计图纸,那么“监控”就是具体的安装说明书。它手把手教你如何把 Mayastor 的各项性能数据接入到你的 Prometheus 监控大屏幕上。
7.7.2. 核心监控组件 (Key Components)
-
Exporters (导出器):
-
io-engine-exporter:运行在每个存储节点上,抓取最底层的磁盘 I/O 性能数据。
-
agent-core-exporter:抓取控制平面的逻辑数据(比如卷的副本状态)。
-
-
ServiceMonitors:
-
官方提供的 Kubernetes 自定义资源,用来告诉 Prometheus:“去这里抓取 Mayastor 的数据”。
-
7.7.3. 核心指标笔记 (Official Metrics)
这是运维中最有价值的部分,建议直接记录这些官方术语:
| 监控类别 | 官方指标名 (部分) | 通俗解释 |
|---|---|---|
| 池容量 | mayastor_pool_size_bytes |
池的总空间大小(字节)。 |
| 池占用 | mayastor_pool_used_bytes |
已经被占用的空间。 |
| 卷性能 | mayastor_volume_read_iops |
卷的每秒读取次数。 |
| 卷带宽 | mayastor_volume_write_throughput_bytes |
每秒写入的数据量。 |
| 延迟 | mayastor_volume_latency_us |
读写操作的延迟(微秒,数值越小性能越好)。 |
| 健康度 | mayastor_node_status |
存储节点是否在线(1 为正常)。 |
7.7.4. 官方标准部署案例 (Standard Procedures)
官方推荐使用 Helm 来一键开启监控功能。
案例一:安装时开启监控
如果你还没有安装 Mayastor,可以在安装命令中加入监控参数:
helm install openebs openebs/openebs \
--namespace openebs \
--set mayastor.obs.enabled=true \
--set mayastor.obs.callhome.enabled=true
案例二:手动配置 Prometheus 抓取 (ServiceMonitor)
如果你已经安装了 Prometheus Operator,可以直接应用官方的 ServiceMonitor:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: mayastor-io-engine
namespace: openebs
spec:
selector:
matchLabels:
app: io-engine # 匹配 Mayastor 的存储引擎 Pod
endpoints:
- port: metrics # 默认监听 9502 端口
interval: 15s
八、总结
针对于上述的LocalVolumes的HostPath和LVM-Volumes以及Replicated Volumes存储类型,其实在生产环境使用最多的还是Local Volumes的LVM与ZFS居多,越来越多的架构师倾向于在应用层(如数据库分片、Elasticsearch 副本)解决数据可靠性,而非在存储层进行二次同步。
在上述的所有场景中,其实Local Volumes和Replicated Volumes是两种存储类型,是可以分开部署的。
# 只安装 OpenEBS Local Volumes(HostPath)
helm install openebs openebs/openebs \
--version 4.3.3 \
--namespace openebs \
--create-namespace \
--set engines.replicated.mayastor.enabled=false
# 只安装 Replicated Storage(Mayastor)
helm install openebs openebs/openebs \
--version 4.3.3 \
--namespace openebs \
--create-namespace \
--set engines.local.hostpath.enabled=false \
--set engines.local.lvm.enabled=false \
--set engines.local.zfs.enabled=false \
--wait \
--timeout 15m
当然也可以两套架构同时部署
# 这个是两种存储模式都安装
[root@k8s-master ~/openEBS]# helm install openebs ./openebs-4.3.3.tgz --namespace openebs --create-namespace --timeout 10m --wait
在对 OpenEBS 的核心组件有了深入了解后,我们不难发现,并没有所谓的“最佳”存储,只有“最适合业务场景”的选型。为了方便大家在生产环境中快速决策,我将 HostPath、LVM-Volumes 和 Replicated Volumes (以 Mayastor 为主) 进行了全方位的对比:
| 维度 | Local PV: HostPath | Local PV: LVM | Replicated (Mayastor) |
| 底层原理 | 绑定宿主机特定目录 | 基于 Linux LVM 逻辑卷 | 跨节点同步冗余 (NVMe-oF) |
| 数据安全性 | 低 (随节点宕机不可用) | 低 (随节点宕机不可用) | 高 (允许节点级故障) |
| 读写性能 | 极高 (原生磁盘性能) | 极高 (接近原生裸盘) | 中等/高 (受网络 IO 影响) |
| 运维复杂度 | 极低 (零配置上手) | 中等 (需管理 VG 卷组) | 高 (需配置存储网络/大页内存) |
| 动态扩容 | 不支持 | 支持 (在线扩容) | 支持 |
| 快照/克隆 | 不支持 | 支持 | 支持 |
读写效率深度解析
-
HostPath & LVM (本地王者): 由于数据路径不经过网络,也不存在额外的软件封装层,它们的 I/O 路径最短。在随机读写测试中,延迟几乎等同于物理 SSD。LVM 相比 HostPath 略有一点点逻辑卷管理的开销,但在现代 CPU 下几乎可以忽略不计。
-
Replicated Volumes (高可用代价): 虽然 OpenEBS 的 Mayastor 引擎通过 SPDK 和 NVMe-oF 极大地优化了路径,但写操作必须在多个节点(通常是 3 副本)都完成确认后才返回。这意味着其写入效率受限于你的“最慢网络节点”。通常其读性能很强,但写延迟会比本地卷高出 2-5 倍。
最佳实践场景建议
💡 场景一:分布式数据库 (MySQL MHA, TiDB, Cassandra)
-
首选方案:LVM-Volumes或者Zfs
-
理由: 这些应用本身在应用层就有副本机制。存储层再做副本会导致“1写2变为1写4”,极大地浪费带宽。LVM 提供了本地高性能,同时支持快照备份和动态扩容,是这类负载的生产标准配置。
💡 场景二:开发测试环境或极简集群
-
首选方案:HostPath
-
理由: 快速部署,无需提前规划磁盘阵列或 LVM 卷组。如果你只是想跑个临时的 CI/CD 流水线,HostPath 是最快且最轻量的选择。
💡 场景三:传统单机应用 (单实例 PostgreSQL, 内部 Wiki)
-
首选方案:Replicated Volumes (Mayastor)
-
理由: 当你的应用自身无法处理数据冗余,且你不能接受由于一个节点故障导致服务彻底中断时,必须在存储层实现高可用。虽然牺牲了部分性能,但换来了 Pod 可以在节点间自由漂移的能力。
在最后我们也需要提一下ZFS-LocalPV这一种存储方式,我们直接用它和LVM-LocalPV做对比。
| 维度 | LVM-LocalPV | ZFS-LocalPV |
| I/O 路径 | 短且直接。数据直接通过内核驱动写入物理块。 | 长且复杂。数据需经过写时复制 (CoW)、校验计算等逻辑。 |
| 写入性能 | 极佳。由于是覆盖写,延迟低且稳定。 | 波动/慢。写时复制 (CoW) 导致碎片化,写操作会有放大效应。 |
| 读取性能 | 稳定。依赖 Linux 内核的 Page Cache。 | 极快 (命中缓存时)。依靠强大的 ARC (自适应缓存),常用数据几乎全在内存中。 |
| 内存开销 | 极低。几乎不消耗额外内存。 | 极高。默认会吃掉节点 50% 的物理内存作为缓存(建议 1TB 存储配 1GB RAM)。 |
| CPU 消耗 | 极低。 | 较高 (尤其开启压缩、校验或加密时)。 |
为什么 ZFS 有时比 LVM 快,有时却慢?
ZFS 的“作弊”神器:ARC 缓存
ZFS 在读性能上往往能“秒杀” LVM,因为它拥有一套极其聪明的 ARC (Adaptive Replacement Cache)。
优势: 如果你的数据库有大量重复读操作,ZFS 会直接从内存中返回结果,速度比 SSD 还快。
代价: 这种性能是拿内存换来的。如果内存不足,ZFS 的性能会断崖式下跌。
ZFS 的阿喀琉斯之踵:写时复制 (CoW)
LVM: 像在纸上用铅笔写字,哪里不要改哪里,原地覆盖。
ZFS: 像在纸上用钢笔写字,改错一个字就要在新页面上重新写整段话。这种 CoW (Copy-on-Write) 机制在处理高频率的随机小文件写入(如大负载数据库)时,会导致严重的磁盘碎片和较高的写入延迟。
特色功能的性能加成
透明压缩 (LZ4/ZSTD): ZFS 支持在线压缩。在 CPU 资源充足的情况下,开启 LZ4 压缩反而能提升性能,因为写入磁盘的数据变少了,对于 I/O 密集型任务是个巨大的优化。LVM 本身不支持此功能,需依赖文件系统层。
数据校验 (Checksum): ZFS 每次读写都会计算校验和以防止“静默数据损坏”。这增加了 CPU 负担,但在生产环境中,这种性能损耗换取了极高的数据安全性。
-
选 LVM-LocalPV 的情况:
-
你的资源受限(内存小于 16GB)。
-
你的应用本身是写密集型的(如高频日志收集、重型随机写数据库)。
-
你需要追求极致的低延迟和低 CPU 开销。
-
-
选 ZFS-LocalPV 的情况:
-
你的服务器内存非常充裕。
-
你的数据非常重要,不能忍受任何静默损坏。
-
你需要极致的快照性能(ZFS 的快照几乎不影响性能,而 LVM 的 Thick Snapshot 会拖慢系统)。
-
你的数据具有高压缩率,想通过 LZ4 压缩来节省空间并提升吞吐量。
-
博主总结: 如果你追求极致性能且应用自带高可用,请闭眼选 LVM-Volumes;如果你追求绝对的数据高可用且不差钱(网络和硬件),请尝试 Mayastor;如果你只是想快速上手玩一玩,HostPath 永远是你最好的朋友。
更多推荐



所有评论(0)