一、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 用于高可靠性和高性能的引擎,对环境要求更严格:

  1. 通用要求

    • x86-64 CPU,支持 SSE4.2 指令集;

    • 推荐 Linux kernel ≥ 5.15;

    • 节点必须满足 SPI-NVMe、ext4/xfs 等模块加载;

    • Helm 版本 ≥ v3.7(安装 Mayastor 组件用)。

  2. 资源要求

    • Io-engine pod 每个节点至少:

      • 2 个 CPU 核心

      • 1GiB RAM

      • 支持 HugePages(大页)(至少 2GiB 2MiB 页面)。

  3. 网络要求

    • 确保某些端口不被占用,如 gRPC 服务端口 10124;

    • 如果用 NVMe-oF TCP 协议,必须启用并保证连接无防火墙阻断。

  4. 集群规模

    • 至少 3 个 worker 节点(尤其使用复制/镜像卷时)(如果没有3worker测试环境可以用master作为第3个存储节点)。

  5. 协议限制

    • 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 官方推荐的工作流程

  1. 安装 OpenEBS 并启用 Local PV Hostpath Provisioner

    • 安装 Helm chart 或 manifest

  2. 确认 BasePath 存在

    • 默认是节点上的 /var/openebs/local

  3. 创建 StorageClass(如果需要自定义)

    • 修改 BasePath 或 NodeAffinity 配置

  4. 创建 PVC 引用该 StorageClass

  5. 创建 Pod 使用 PVC 进行挂载

  6. 验证数据持久性 / 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 拥有卷。

配置位置

  1. CSI Driver

    (全局设置,控制驱动行为):

       fsGroupPolicy
    • 有三个值:

    • None:啥都不改(非 root 必失败)。

    • ReadWriteOnceWithFSType:只有 RWO + 指定 fsType 时调整(LVM 下等同 File)。

    • File:总是调整权限(推荐,无条件)。

    • 其他固定:attachRequired: false、podInfoOnMount: true。

  2. 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 流程

  1. 创建(或复用)LVM SC。

  2. 创建 PVC,设 volumeMode: Block。

  3. 部署 Pod,用 volumeDevices 挂载到指定 devicePath。

  4. 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 用于高可靠性和高性能的引擎,对环境要求更严格:

  1. 通用要求

    • x86-64 CPU,支持 SSE4.2 指令集;

    • 推荐 Linux kernel ≥ 5.15;

    • 节点必须满足 SPI-NVMe、ext4/xfs 等模块加载;

    • Helm 版本 ≥ v3.7(安装 Mayastor 组件用)。

  2. 资源要求

    • Io-engine pod 每个节点至少:

      • 2 个 CPU 核心

      • 1GiB RAM

      • 支持 HugePages(大页)(至少 2GiB 2MiB 页面)。

  3. 网络要求

    • 确保某些端口不被占用,如 gRPC 服务端口 10124;

    • 如果用 NVMe-oF TCP 协议,必须启用并保证连接无防火墙阻断。

  4. 集群规模

    • 至少 3 个 worker 节点(尤其使用复制/镜像卷时)。

  5. 协议限制

    • 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 流程

  1. 创建 PVC,指定上面的 StorageClass

  2. CSI driver 调用 Mayastor 服务创建逻辑卷

  3. Mayastor 会在多个节点上创建副本并同步

  4. Pod 挂载卷并正常读写

  5. 如果节点故障,副本仍然在线提供数据服务

📌 整个过程由 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 会在多个节点上创建副本,实现高可用存储。

核心注意事项

  1. 副本数 ≤ 节点数

    • 如果集群只有 2 节点,repl=3 会失败

  2. 裸块设备

    • DiskPool 必须使用未挂载的块设备(不能是 /var/lib/kubelet/pods/ 已挂载卷)

  3. 文件系统

    • Mayastor 可以直接提供块设备给 Pod,PVC 可以选择 xfsext4

  4. 扩容

    • StorageClass 开启 allowVolumeExpansion: true,PVC 可在线扩容

  5. 高可用

    • 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 会按以下步骤操作:

  1. 检测(Detection):控制平面发现某个副本无法连接。

  2. 标记(Marking):该副本被标记为 Faulted

  3. IO 重新路由:如果挂掉的是主节点(Nexus 所在节点),CSI 会迅速在另一个健康的节点上重建 Nexus,Pod 的 I/O 自动切到新路径。

  4. 自动修复(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 来模拟节点宕机:

  1. 观察 kubectl get mnv,你会发现卷的状态变为 Degraded(降级)。

  2. 观察你的测试应用,由于有 3 副本,读写应该 不会中断

  3. 恢复 Pod 后,观察状态变为 Online,说明重构完成。

最佳实践建议

副本数 >=3:这是 HA 的基础,保证在“一节点维护+一节点突发故障”时数据依然安全。

网络延迟:HA 依赖节点间的实时同步,确保你的三台虚拟机之间是万兆网或极低延迟的内网。

不要在 Master 节点运行 I/O 密集型应用:虽然你现在是三节点全开,但在大规模环境下,建议将存储节点和计算节点适当分离。

7.2 Kubectl 插件

OpenEBS 官方提供了一个名为 mayastorkubectl 插件,它是运维 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 资源:

  1. VolumeSnapshotClass:快照的“模版”,定义由谁(Mayastor)来执行快照。

  2. VolumeSnapshot:用户的请求,类似于 PVC。

  3. 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 参数。

关键知识点:
  1. Namespace 一致性:恢复出来的 PVC 必须和快照(VolumeSnapshot)在同一个命名空间(Namespace)下。

  2. 容量限制:新申请的 PVC 容量(resources.requests.storage)必须 大于或等于 快照原始卷的容量。你不能把 10G 的数据恢复到 5G 的盘里。

  3. 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 会执行以下流程:

  1. Provisioning: CSI 驱动读取快照数据。

  2. Binding: 创建一个新的 PV 并绑定到 restored-pvc

  3. 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)

  1. 密钥轮换 (Key Rotation)不支持。目前一旦池创建,密钥就不能换。

  2. 迁移 (Migration)

    • 如果你已经有一堆没加密的数据,不能自动迁移到加密池。

    • 手动迁移方案:必须新建一个加密池,然后把旧卷的副本扩容(Scale Up)到新池,再把旧池的副本缩容(Scale Down)删掉。过程比较繁琐且需要谨慎操作(建议先备份快照)。

  3. 安全性: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)

  1. Velero:Kubernetes 备份和恢复的标准工具,作为“指挥官”。

  2. Restic:Velero 的插件,负责具体的“搬运工作”,深入到 Pod 内部去复制文件。

  3. Execution Hooks (执行钩子)这是分布式数据库备份的关键

    • 为什么需要? 数据库通常会在内存里缓存数据,直接复制文件会导致数据损坏(不一致)。

    • 怎么做? 在备份前“冻结”数据库(Pre-hook),备份完后“解冻”数据库(Post-hook),确保数据静止且一致。

  4. 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)

核心在于处理数据一致性。

  1. Annotate Pods (添加注解): 你需要给数据库的 Pod 打上标签,告诉 Velero 哪个卷需要备份。

    • 格式backup.velero.io/backup-volumes: <volume-mount-name>

  2. 配置 Hooks (配置冻结钩子): 定义备份前和备份后的动作。通常涉及 fsfreeze 命令。

  3. 执行备份: 运行 Velero 备份命令。

第三阶段:恢复 (Restore to New Storage)

核心在于“偷梁换柱”,把底层存储换成 Mayastor。

  1. 配置映射 (Configure Mapping): 创建一个 ConfigMap,定义 old-storage-class: new-storage-class

  2. 执行恢复: 运行 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)

  1. 本质:这是文件级迁移,不是块级迁移。

  2. 关键点:分布式数据库必须配置 Hooks (fsfreeze/flush),否则恢复出来的数据可能坏掉。

  3. 映射:恢复时必须通过 ConfigMap 做 StorageClass 的映射,这是从旧引擎切换到 Mayastor 的关键步骤。

  4. 限制:由于是文件拷贝,速度取决于数据量大小和网络带宽,比快照恢复要慢。

7.7 可观测性监控

7.7.1. 核心概念 (Core Concepts)

  • 技术定义: Mayastor 监控体系是基于 Prometheus 架构的,通过 mayastor-obs-call-homemayastor-obs-exporter 两个核心组件,将分布式存储的内部状态转化为时间序列数据。

  • 通俗解释: 如果说“可观测性”是设计图纸,那么“监控”就是具体的安装说明书。它手把手教你如何把 Mayastor 的各项性能数据接入到你的 Prometheus 监控大屏幕上。

7.7.2. 核心监控组件 (Key Components)

  1. Exporters (导出器)

    • io-engine-exporter:运行在每个存储节点上,抓取最底层的磁盘 I/O 性能数据。

    • agent-core-exporter:抓取控制平面的逻辑数据(比如卷的副本状态)。

  2. 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 的核心组件有了深入了解后,我们不难发现,并没有所谓的“最佳”存储,只有“最适合业务场景”的选型。为了方便大家在生产环境中快速决策,我将 HostPathLVM-VolumesReplicated 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 永远是你最好的朋友。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐