Kubernetes 本地持久化存储方案 OpenEBS LocalPV 落地实践上——使用篇

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: Kubernetes 本地持久化存储方案 OpenEBS LocalPV 落地实践上——使用篇

K8s 支持多达 20+ 种类型的持久化存储,如常见的 CephFS 、Glusterfs 等,不过这些大都是分布式存储,随着社区的发展,越来越多的用户期望将 K8s 集群中工作节点上挂载的数据盘利用起来,于是就有了 local 类型持久卷的支持。

我将通过上、下两篇文章介绍 K8s 本地持久化存储方案 OpenEBS LocalPV 落地实践完整过程。本篇为使用篇,着重介绍实践过程,下一篇文章为原理篇,将对 OpenEBS LocalPV 原理进行讲解。

我们可以把 local 类型持久卷称作:Local Persistent Volume,简称 LocalPV。LocalPV 所代表的是某个被挂载的本地(工作节点)存储设备,例如磁盘、分区或者目录,所以 LocalPV 并不能像分布式存储一样可靠,但速度极快,这也决定了 LocalPV 使用场景:I/O 敏感度高且能够容忍小概率数据丢失现象。

K8s 本地存储

K8s 官方文档里有一个使用 LocalPV 的简单示例,由于篇幅所限,我这里就不对其进行演示了,感兴趣的读者可以自行尝试。

特点

这里简单总结下 K8s LocalPV 的特点:

  • 只能用作静态创建的持久卷,不支持动态供应,也就是说必须通过手动的方式创建 PV。
  • hostPath 卷相比,LocalPV 能够以持久和可移植的方式使用,而无需手动将 Pod 调度到节点。系统通过查看 PV 的节点亲和性(nodeAffinity)配置,就能了解卷的节点约束。
  • 如果想使用存储类来自动绑定 PVC 和 PV,则必须将 StorageClass 配置成延迟绑定,示例如下:
1
2
3
4
5
6
apiVersion:storage.K8s.io/v1
kind:StorageClass
metadata:
name:local-storage
provisioner:kubernetes.io/no-provisioner
volumeBindingMode:WaitForFirstConsumer

其中 volumeBindingMode: WaitForFirstConsumer 属性即为延迟卷绑定,它使得调度器在为 PVC 选择一个合适的 PV 时能考虑到所属 Pod 的调度限制。

举个例子,假设我们创建好了两个 PV 分别为 PV1PV2,然后创建一个 Pod 并申明一个 PVC 叫 PVC1PV1PV2 同时满足 PVC1 的要求,但此时存储类并不能马上将 PVC1PV1PV2 中任何一个 PV 进行绑定,而是要考虑 Pod 的调度策略。如果 Pod 指定了节点亲和性必须要部署到 PV1 所在节点,则 PVC1 就需要跟 PV1 进行绑定,而不能与 PV2 进行绑定。

可以发现,当 Pod 需要使用 LocalPV 时,PVC 与 PV 的绑定就需要考虑 Pod 的调度情况了,所以 LocalPV 的存储类无法支持立即绑定,只能将绑定时机延迟到 Pod 调度时进行(WaitForFirstConsumer)。

OpenBES 本地存储

由于 K8s LocalPV 的使用限制无法满足生产需求,所以就需要寻找替代方案,好在社区已经有人实现了更强大的 LocalPV 存储方案——OpenEBS LocalPV。

简介

OpenEBS 官网地址为:https://openebs.io/。

OpenEBS 可以将 K8s 工作节点上的任何可用存储转换为本地或分布式(也称为复制)持久卷。

OpenEBS 最初由 MayaData 构建并捐赠给 CNCF,现在是 CNCF 沙盒项目

卷类型

OpenEBS 支持两种卷类型:本地卷、复制卷,架构如下:

openebs

本地卷能够直接将工作节点上插入的数据盘抽象为持久卷,供当前节点上的 Pod 挂载使用。

而复制卷则相对复杂一些,OpenEBS 使用其内部的引擎为每个复制卷创建一个微服务,在使用时,有状态服务将数据写入 OpenEBS 引擎,引擎将数据同步复制到集群中的多个节点,从而实现了高可用。

本地卷

由于本次 OpenEBS 的落地实践针对于本地持久化存储,故本文将主要介绍 OpenEBS 本地卷的使用,不会对复制卷进行过多讲解,感兴趣的读者可以进入官网查看学习。

OpenEBS 本地卷支持多种类型:Hostpath、Device、LVM、ZFS、Rawfile

每种类型各有特点,都有自己的适用场景,比如相较于 K8s 原生 Hostpath,OpenEBS 的 Hostpath 能够支持将外挂的数据盘目录作为 Hostpath 目录,从而避免 Pod 可能将宿主机目录写满的问题。Device 能够将块设备用于 LocalPV 的使用,速度极快。而借助 LVM 的能力,则可以更灵活的使用 LocalPV,它可以支持 PV 的动态扩缩容操作。

OpenEBS 为其支持的每种类型都实现了一个单独的项目,这里以使用块设备为例,介绍 OpenEBS LocalPV 使用实践,项目地址为:device-localpv,下文中我将以 Device-LocalPV 来指代它。

实践

这里我将通过 5 个步骤来演示 OpenEBS LocalPV 的实践过程,你可以跟着我一步步操作。

第一步,环境准备

这里我们使用 minikube 来搭建 K8s 集群环境,OpenEBS 官方要求 K8s 版本为 1.20+,我实测下来 1.19 版本也是没有问题的,不过建议大家还是尽量优先选用推荐版本。

第二步,安装 Device-LocalPV

由于 OpenEBS Device-LocalPV 本身即为云原生而开发的应用,所以安装起来非常简单,只需要一条 kubectl apply 命令即可。

执行以上命令后,会得到如下几个相关 Pod:

1
2
3
4
5
➜ kubectl get pod -A
NAMESPACE     NAME                               READY   STATUS    RESTARTS   AGE
kube-system   openebs-device-controller-0        2/2     Running   0          2m23s
kube-system   openebs-device-node-4wld7          2/2     Running   0          2m23s
kube-system   openebs-device-node-p2r6m          2/2     Running   0          2m23s

确保这几个 Pod 全部处于 Running 状态,就表示 Device-LocalPV 安装成功了。如果安装失败,则需要根据 kubectl describe 命令的描述信息进行排查。

第三步,准备磁盘

Device-LocalPV 能够直接接管节点上的块设备,有时候节点上可能同时插入多块数据盘,而这些数据盘中,也许某些数据盘我们不想当作 LocalPV 来使用,为了能够区分哪些块设备可以供 Device-LocalPV 来使用,我们需要在对应的块设备上创建一个(~10MiB)Meta 分区,用于存储磁盘标识信息,Meta 分区有如下要求:

  • 是块设备的第一个分区(ID_PART_ENTRY_NUMBER=1
  • 不能被格式化成任何文件系统
  • 不能设置 flags 分区标记

操作命令如下:

1
2
3
4
5
6
7
8
9
## 在 minikube 节点上执行如下命令
$ sudo parted /dev/sdb mklabel gpt
$ sudo parted /dev/sdb mkpart test-device 1MiB 10MiB
## 在 minikube-m02 节点执行如下命令
$ sudo parted /dev/sdb mklabel gpt
$ sudo parted /dev/sdb mkpart test-device 1MiB 10MiB
$ sudo parted /dev/sdc mklabel gpt
$ sudo parted /dev/sdc mkpart test-device 1MiB 10MiB

以上分别对 minikubeminikube-m02 两个节点上的块设备进行了 Meta 分区的操作,其中 /dev/sdb/dev/sdc 就是节点上挂载的块设备名,你需要根据自己实际情况来指定。

其中三块盘的 Meta 分区名都叫 test-device 是有意而为之,接下来创建存储类时会用到。

第四步,创建存储类

既然 Device-LocalPV 支持动态供应,那么必然少不了创建存储类的步骤,将以下 yaml 文件保存为 sc.yaml 然后通过 kubectl apply -f sc.yaml 创建存储类。

1
2
3
4
5
6
7
8
9
apiVersion:storage.K8s.io/v1
kind:StorageClass
metadata:
name:openebs-device-sc
allowVolumeExpansion:false
parameters:
devname:"test-device"
provisioner:device.csi.openebs.io
volumeBindingMode:WaitForFirstConsumer

注意:存储类 parameters 字段需要指定 devname,其值为上面在为块设备分区时指定的分区名称 test-device,存储类在创建 PV 的时候正是根据这个分区名的匹配,来找到哪些块设备是供 Device-LocalPV 来使用的。

第五步,创建 StatefulSet 来申请使用 LocalPV

StatefulSet 以及相关资源定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
apiVersion:v1
kind:Service
metadata:
name:nginx
labels:
app:nginx
spec:
ports:
-port:80
name:web
clusterIP:None
selector:
app:nginx
---
apiVersion:apps/v1
kind:StatefulSet
metadata:
name:hello
spec:
selector:
matchLabels:
app:hello
serviceName:"nginx"
replicas:2
template:
metadata:
labels:
app:hello
spec:
terminationGracePeriodSeconds:1
containers:
-name:html
image:busybox
imagePullPolicy:IfNotPresent
command:
-sh
--c
-'while true; do echo "`date` [`hostname`] Hello from OpenEBS Local PV." >> /mnt/store/index.html; sleep $(($RANDOM % 5 + 300)); done'
volumeMounts:
-mountPath:/mnt/store
name:csi-devicepv
-name:web
image:K8s.gcr.io/nginx-slim:0.8
imagePullPolicy:IfNotPresent
ports:
-containerPort:80
name:web
volumeMounts:
-name:csi-devicepv
mountPath:/usr/share/nginx/html
volumeClaimTemplates:
-metadata:
name:csi-devicepv
spec:
accessModes:["ReadWriteOnce"]
storageClassName:"openebs-device-sc"
resources:
requests:
storage:1Gi

这一步我们创建了一个 Service 和一个名叫 hello 的 StatefulSet。我们重点关注 StatefulSet,它启动两个副本,并通过 volumeClaimTemplates 来申请 1Gi 大小的 PVC。

通过 kubectl apply 命令安装以上文件资源后,可以看到,两个 Pod 分别被调度到了不同节点上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查看 Pod 调度情况
➜ kubectl get pod -o wide
NAME      READY   STATUS    RESTARTS   AGE     IP           NODE           NOMINATED NODE   READINESS GATES
hello-0   2/2     Running   0          4m13s   10.244.1.3   minikube-m02   <none>           <none>
hello-1   2/2     Running   0          2m42s   10.244.0.3   minikube       <none>           <none>
# 查看 PVC 资源
➜ kubectl get pvc -o wide
NAME                   STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS        AGE     VOLUMEMODE
csi-devicepv-hello-0   Bound    pvc-042661c8-c000-4dde-9950-2b6859d5f273   1Gi        RWO            openebs-device-sc   4m46s   Filesystem
csi-devicepv-hello-1   Bound    pvc-26f92829-e0d4-4520-86da-2d7741cd68c2   1Gi        RWO            openebs-device-sc   3m15s   Filesystem
# 查看 PV 资源
➜ kubectl get pv -o wide
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                          STORAGECLASS        REASON   AGE   VOLUMEMODE
pvc-042661c8-c000-4dde-9950-2b6859d5f273   1Gi        RWO            Delete           Bound    default/csi-devicepv-hello-0   openebs-device-sc            18m   Filesystem
pvc-26f92829-e0d4-4520-86da-2d7741cd68c2   1Gi        RWO            Delete           Bound    default/csi-devicepv-hello-1   openebs-device-sc            17m   Filesystem

现在分别登录两台 minikube 主机节点,使用 fdisk 命令查看两个节点磁盘使用情况:

fdisk

上图中,左侧为 minikube 节点,右侧为 minikube-m02 节点,可以看到 PV 创建成功后会在对应节点的块设备上创建与 PV 相同大小的分区来提供 LocalPV 的支持,而这个分区的生命周期管理工作都是由 Device-LocalPV 来帮我们完成的,它会随着 PV 的创建而创建,随着 PV 的删除而销毁。

以上几步,我们就完成了 OpenEBS Device-LocalPV 实践演练。

避坑指南

正常来说,通过上面的实践步骤,你应该能顺利的搭建并使用 OpenEBS Device-LocalPV。但如果你不够幸运,也许会遇到一些奇怪的问题,接下来我就介绍几个我在 OpenEBS Device-LocalPV 落地实践过程所踩到的坑,供你参考避坑。

parted 命令版本问题

根据我的经验,parted 命令不同版本表现可能不一致。如下所示,使用两个不同版本的 parted 工具执行相同命令,来对块设备进行 Meta 分区操作,得到的结果却不一样。

parted

可以发现,截图中 3.3 版本 parted 命令分区会产生 flag(msftdata),下面 3.1 版本的 parted 命令分区则没有产生 flag

OpenEBS Device-LocalPV 规定 Meta 分区不能有 flag,如果产生 flag 则 Device-LocalPV 会将这个块设备忽略,不进行使用。

为了保证执行 parted 命令对磁盘进行分区时,得到预期结果,我们可以总是进入 OpenEBS Daemonset 的容器内部来使用 OpenEBS 提供的 parted 命令进行分区。这样就保证了与 Device-LocalPV 工作时内部使用的 parted 命令版本一致,不会出现一些意料之外的问题。Daemonset 所对应的 Pod 即为上面的 openebs-device-node-4wld7openebs-device-node-p2r6m 两个 Pod,OpenEBS 启动时会在每个节点上启动一个 Daemonset 工作负载,用来操作节点块设备提供 LocalPV 支持(下一篇文章原理篇中将会对其有更详细的介绍)。

PV 动态扩容问题

OpenEBS Device-LocalPV 目前还不支持扩容操作,所以在创建存储类时需要指定 allowVolumeExpansion 属性值为 false,以此来标记这个存储类所创建出来的 PV 不支持动态扩容操作。

PV 可能无法创建成功问题

你可能会遇到 PVC 所申请的容量刚好等于块设备剩余容量时,PV 无法创建成功,PVC 一直处于 Pending 状态的问题。

假设我们现在只有一个节点,节点上也仅仅只有一个块设备供 Device-LocalPV 使用,其容量为 10Gi。如果我们连续申请 4 个 PVC,其容量依次为 1G、3G、3G、1G,分别对应如下截图(fdisk 命令显示结果)中 sdb2sdb3sdb4sdb5 4 个分区。

partion

现在如果删除第一个容量为 3G 的 PVC,Device-LocalPV 则会自动删除 /dev/sdb3 这个分区。可是,此时如果我们尝试再次创建一个 3G 的 PVC,这个 PVC 将永远无法创建成功,一直处于 Pending 状态。

当我们每创建一个 PVC 时,存储类都会通过 Device-LocalPV 在节点的块设备上帮我们新创建一个和 PVC 中申请的容量相等的一个磁盘分区出来,从截图中可以看出,这个分区的 StartEnd 是从小到大且连续的。新创建的 3G 的 PVC 容量等于 /dev/sdb3 分区容量,理论上应该是可以创建成功的。

分析 Device-LocalPV 源码可以发现,在计算节点上块设备剩余可用容量是否满足 PVC 申请的容量大小时,Device-LocalPV 通过 if tmp.SizeMiB > partSize 这条语句来进行判断,其中 tmp.SizeMiB 为块设备剩余可用容量,partSize 为 PVC 申请容量,只有块设备剩余容量大于 PVC 申请容量时,才会进行分区操作,如果没有满足 PVC 申请容量的可用分区,PVC 就会一直处于 Pending 状态。

https://github.com/openebs/device-localpv/blob/develop/pkg/device/device-util.go#L243

part-size

由此可见,我们将 if tmp.SizeMiB > partSize 改成 if tmp.SizeMiB >= partSize 即可解决这个问题。

另外,在阅读源码的过程中,我还发现 Device-LocalPV 在计算块设备可用分区时,对于分区大小的计算会涉及从 BytesMib 的单位转换操作:

https://github.com/openebs/device-localpv/blob/develop/pkg/device/device-util.go#L294

parse-part-free

这里的 beginBytesendBytes 对应的就是上面 fdisk 命令截图中的 StartEnd,此时如果分区没有对齐,则会出现浮点数计算精度丢失问题,其中 beginMib 会被 math.Ceil 向上取整,endMib 会被 math.Floor 向下取整,最终得到的 sizeMib 有可能小于实际剩余可用空间,这样就导致可能会出现磁盘剩余容量满足 PVC 申请容量,而 PVC 却无法创建成功现象,所以在创建 PVC 时应尽量申请 1024 整数倍大小的容量。

总结

本篇文章首先简单介绍 K8s LocalPV 使用场景及其特点,接着又介绍了 OpenEBS Device-LocalPV 的使用方法以及实践过程中可能遇到的一些坑,如果你也刚好需要落地 LocalPV,希望本文能对你有所帮助。

下一篇文章我将带你一起剖析 OpenEBS Device-LocalPV 底层原理,记得不要错过。

参考

https://kubernetes.io/zh-cn/docs/concepts/storage/volumes/#local

https://openebs.io/

https://github.com/openebs/device-localpv

https://minikube.sigs.k8s.io/docs/drivers/virtualbox/

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
5天前
|
Kubernetes Cloud Native Docker
云原生时代的容器化实践:Docker和Kubernetes入门
【10月更文挑战第37天】在数字化转型的浪潮中,云原生技术成为企业提升敏捷性和效率的关键。本篇文章将引导读者了解如何利用Docker进行容器化打包及部署,以及Kubernetes集群管理的基础操作,帮助初学者快速入门云原生的世界。通过实际案例分析,我们将深入探讨这些技术在现代IT架构中的应用与影响。
23 2
|
5天前
|
Kubernetes 监控 负载均衡
深入云原生:Kubernetes 集群部署与管理实践
【10月更文挑战第37天】在数字化转型的浪潮中,云原生技术以其弹性、可扩展性成为企业IT架构的首选。本文将引导你了解如何部署和管理一个Kubernetes集群,包括环境准备、安装步骤和日常维护技巧。我们将通过实际代码示例,探索云原生世界的秘密,并分享如何高效运用这一技术以适应快速变化的业务需求。
23 1
|
15天前
|
Kubernetes 负载均衡 Cloud Native
云原生应用:Kubernetes在容器编排中的实践与挑战
【10月更文挑战第27天】Kubernetes(简称K8s)是云原生应用的核心容器编排平台,提供自动化、扩展和管理容器化应用的能力。本文介绍Kubernetes的基本概念、安装配置、核心组件(如Pod和Deployment)、服务发现与负载均衡、网络配置及安全性挑战,帮助读者理解和实践Kubernetes在容器编排中的应用。
47 4
|
16天前
|
Kubernetes 监控 Cloud Native
云原生应用:Kubernetes在容器编排中的实践与挑战
【10月更文挑战第26天】随着云计算技术的发展,容器化成为现代应用部署的核心趋势。Kubernetes(K8s)作为容器编排领域的佼佼者,以其强大的可扩展性和自动化能力,为开发者提供了高效管理和部署容器化应用的平台。本文将详细介绍Kubernetes的基本概念、核心组件、实践过程及面临的挑战,帮助读者更好地理解和应用这一技术。
48 3
|
23天前
|
Kubernetes 监控 开发者
专家级实践:利用Cloud Toolkit进行微服务治理与容器化部署
【10月更文挑战第19天】在当今的软件开发领域,微服务架构因其高可伸缩性、易于维护和快速迭代的特点而备受青睐。然而,随着微服务数量的增加,管理和服务治理变得越来越复杂。作为阿里巴巴云推出的一款免费且开源的开发者工具,Cloud Toolkit 提供了一系列实用的功能,帮助开发者在微服务治理和容器化部署方面更加高效。本文将从个人的角度出发,探讨如何利用 Cloud Toolkit 来应对这些挑战。
34 2
|
24天前
|
Kubernetes 持续交付 Docker
探索DevOps实践:利用Docker与Kubernetes实现微服务架构的自动化部署
【10月更文挑战第18天】探索DevOps实践:利用Docker与Kubernetes实现微服务架构的自动化部署
72 2
|
7天前
|
Kubernetes 负载均衡 调度
Kubernetes集群管理与编排实践
Kubernetes集群管理与编排实践
|
7天前
|
Kubernetes Cloud Native 前端开发
Kubernetes入门指南:从基础到实践
Kubernetes入门指南:从基础到实践
18 0
|
1月前
|
Kubernetes Cloud Native 云计算
云原生之旅:Kubernetes 集群的搭建与实践
【8月更文挑战第67天】在云原生技术日益成为IT行业焦点的今天,掌握Kubernetes已成为每个软件工程师必备的技能。本文将通过浅显易懂的语言和实际代码示例,引导你从零开始搭建一个Kubernetes集群,并探索其核心概念。无论你是初学者还是希望巩固知识的开发者,这篇文章都将为你打开一扇通往云原生世界的大门。
120 17
|
1月前
|
Kubernetes Docker 微服务
微服务实践k8s&dapr开发部署实验(1)服务调用(一)
微服务实践k8s&dapr开发部署实验(1)服务调用(一)
49 2