Kubernetes云原生开源分布式存储介绍
1 Kubernetes存储介绍
1.1 为何引入PV、PVC以及StorageClass?
熟悉Kubernetes的都对PV、PVC以及StorageClass不陌生,我们经常用到,因此这里不再详细介绍PV、PVC以及StorageClass的用法,仅简单聊聊为什么需要引入这三个概念。
我们看下最早期Pod使用Volume的写法:
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- image: ...
name: test-pod
volumeMounts:
- mountPath: /data
name: data
volumes:
- name: data
capacity:
storage: 10Gi
cephfs:
monitors:
- 172.16.0.1:6789
- 172.16.0.2:6789
- 172.16.0.3:6789
path: /opt/eshop_dir/eshop
user: admin
secretRef:
name: ceph-secret
这种方式至少存在两个问题:
-
Pod声明与底层存储耦合在一起,每次声明volume都需要配置存储类型以及该存储插件的一堆配置,如果是第三方存储,配置会非常复杂。
-
开发人员的需求可能只是需要一个20GB的卷,这种方式却不得不强制要求开发人员了解底层存储类型和配置。
比如前面的例子中每次声明Pod都需要配置Ceph集群的mon地址以及secret,特别麻烦。
于是引入了PV(Persistent Volume),PV其实就是把Volume的配置声明部分从Pod中分离出来:
apiVersion: v1
kind: PersistentVolume
metadata:
name: cephfs
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteMany
cephfs:
monitors:
- 172.16.0.1:6789
- 172.16.0.2:6789
- 172.16.0.3:6789
path: /opt/eshop_dir/eshop
user: admin
secretRef:
name: ceph-secret
我们发现PV的spec部分几乎和前面Pod的volume定义部分是一样的。
有了PV,在Pod中就可以不用再定义volume的配置了,直接引用即可,volume定义和Pod松耦合了。
但是这没有解决volume定义的第二个问题,存储系统通常由运维人员管理,开发人员并不知道底层存储配置,也就很难去定义好PV。
为了解决这个问题,引入了PVC(Persistent Volume Claim),声明与消费分离,开发与运维责任分离。
运维人员负责存储管理,可以事先根据存储配置定义好PV,而开发人员无需了解底层存储配置,只需要通过PVC声明需要的存储类型、大小、访问模式等需求即可,然后就可以在Pod中引用PVC,完全不用关心底层存储细节。
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: cephfs
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 8Gi
PVC会根据声明的大小、存储类型(如storageClassName)、accessModes等关键字查找PV,如果找到了匹配的PV,则会与之关联。
通过PV以及PVC,开发人员的问题是解决了,但没有解决运维人员的问题。运维人员需要维护一堆PV列表和配置,如果PV不够用需要手动创建新的PV,PV空闲了还需要手动去回收,管理效率太低了。
于是又引入了StorageClass,StorageClass类似声明了一个非常大的存储池,其中一个最重要的参数是provisioner,这个provisioner声明了谁来提供存储源,我们熟悉的OpenStack Cinder、Ceph、AWS EBS等都是provisioner。
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: aws-gp2
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp2
fsType: ext4
有了StorageClass后,Kubernetes会根据开发人员定义的PVC中声明的StorageClassName以及大小等需求自动创建PV,即Dynamic Provisioning。
而运维人员只需要声明好StorageClass以及Quota配额,无需维护PV。
通过PV、PVC以及StorageClass,开发和运维的工作彻底解放了。
1.2 Kubernetes存储方案发展过程概述
我们知道Kubernetes存储最开始是通过Volume Plugin实现集成外部存储系统,即不同的存储系统对应不同的volume plugin。
Volume Plugin实现代码全都放在了Kubernetes主干代码中(in-tree),也就是说这些插件与核心Kubernetes二进制文件一起链接、编译、构建和发布。
这种方案至少存在如下几个问题:
-
在Kubernetes中添加新存储系统支持需要在核心Kubernetes增加插件代码,随着存储插件越来越多,Kubernetes代码也会变得越来越庞大。
-
Kubernetes与具体的存储plugin耦合在一起,一旦存储接口发生任何变化都需要重新修改plugin代码,也就是说不得不修改Kubernetes代码,这会导致Kubernetes代码维护越来越困难。
-
如果plugin有bug或者存储系统故障导致crash,可能导致整个Kubernetes集群整体crash。
-
这些插件运行时无法做权限管控,具有Kubernetes所有组件的所有权限,存在一定的安全风险。
-
插件的实现必须通过Golang语言编写并与Kubernetes一起开源,可能对一些厂商不利。
因此从1.8开始,Kubernetes停止往Kubernetes代码中增加新的存储支持, 并推出了一种新的插件形式支持外部存储系统,即FlexVolume,不过FlexVolume其实在1.2就提出了。
FlexVolume类似于CNI插件,通过外部脚本集成外部存储接口,这些脚本默认放在/usr/libexec/kubernetes/kubelet-plugins/volume/exec/
,需要安装到所有Node节点上。
这样每个存储插件只需要通过外部脚本(out-of-tree)实现attach
、detach
、mount
、umount
等接口即可集成第三方存储,不需要动Kubernetes源码,可以参考官方的一个LVM FlexVolume Demo[1]。
但是这种方法也有问题:
-
脚本文件放在host主机上,因此驱动不得不通过访问宿主机的根文件系统去运行脚本。
-
这些插件如果还有第三方程序依赖或者OS兼容性要求,还需要在所有的Node节点安装这些依赖并解决兼容问题。
因此这种方式虽然解决了in-tree的问题,但显然这种方式用起来不太优雅,不太原生。
因此Kubernetes从1.9开始又引入了Container Storage Interface (CSI)容器存储接口,并于1.13版本正式GA。
CSI的实现方案和CRI类似通过gRPC与volume driver进行通信,存储厂商需要实现三个服务接口Identity Service、Controller Service、Node Service,
-
Identity Service用于返回一些插件信息;
-
Controller Service实现Volume的CURD操作,
-
Node Service运行在所有的Node节点,用于实现把volume挂载在当前Node节点的指定目录,该服务会监听一个Socket,controller通过这个Socket进行通信,可以参考官方提供的样例CSI Hostpath driver Sample[2]。
更多有关CSI介绍可以参考官方的设计文档CSI Volume Plugins in Kubernetes Design Doc[3]。
通过CSI基本解决了如上in-tree以及FlexVolume的大多数问题,未来Kubernetes会把in-tree的存储插件都迁移到CSI。
当然Flex Volume Plugin也会与新的CSI Volume Plugin并存以便兼容现有的第三方FlexVolume存储插件。
1.3 为什么需要云原生分布式存储
通过CSI接口或者Flex Volume Plugin解决了Kubernetes集成外部存储的问题,目前Kubernetes已经能够支持非常多的外部存储系统了,如NFS、GlusterFS、Ceph、OpenStack Cinder等,这些存储系统目前主流的部署方式还是运行在Kubernetes集群之外单独部署和维护,这不符合All In Kubernetes的原则。
如果已经有分布式存储系统还好,可以直接对接。但如果没有现成分布式存储,则不得不单独部署一套分布式存储。
很多分布式存储部署相对还是比较复杂的,比如Ceph。而Kubernetes天生就具有快速部署和编排应用的能力,如果能把分布式存储的部署也通过Kubernetes编排管理起来,则显然能够大大降低分布式存储的部署和维护成本,甚至可以使用一条apply命令就可以轻松部署一个Ceph集群。
这主要有两种实现思路:
-
第一种思路就是重新针对云原生平台设计一个分布式存储,这个分布式存储系统组件是微服务化的,能够复用Kubernetes的调度、故障恢复和编排等能力,如后面要介绍的Longhorn、OpenEBS。
-
另一种思路就是设计微服务组件把已有的分布式存储系统包装管理起来,使原来的分布式存储可以适配运行在Kubernetes平台上,实现通过Kubernetes管理原有的分布式存储系统,如后面要介绍的Rook。
1.4 Container Attached Storage,容器存储的未来?
我们知道组成云计算的三大基石为计算、存储和网络,Kubernetes计算(Runtime)、存储(PV/PVC)和网络(Subnet/DNS/Service/Ingress)的设计都是开放的,可以集成不同的方案,比如网络通过CNI接口支持集成Flannel、Calico等网络方案,运行时(Runtime)通过CRI支持Docker、Rkt、Kata等运行时方案,存储通过volume plugin支持集成如AWS EBS、Ceph、OpenStack Cinder等存储系统。
但是我们发现目前主流的方案中存储与计算、网络稍有不同,计算和网络都是以微服务的形式通过Kubernetes统一编排管理的,即Kubernetes既是计算和网络的消费者,同时也是计算和网络的编排者和管理者。
而存储则不一样,虽然Kubernetes已经设计了PV/PVC机制来管理外部存储,但只是弄了一个标准接口集成,存储本身还是通过独立的存储系统来管理,Kubernetes根本不知道底层存储是如何编排和调度的。
社区认为既然计算和网络都由我Kubernetes统一编排了,是不是存储也考虑下?
于是社区提出了Container Attached Storage(CAS)理念,这个理念的目标就是利用Kubernetes来编排存储,从而实现我Kubernetes编排一切,这里的一切包括计算、存储、网络,当然更高一层的还包括应用、服务、软件等。
这个方案如何实现呢?CAS提出如下方案:
-
每个volume都由一个轻量级的Controller来管理,这个Controller可以是一个单独的Pod。
-
这个Controller与使用该volume的应用Pod在同一个Node(sidecar模式)。
-
不同的Volume的数据使用多个独立的Controller Pod进行管理。
CAS
由于Pod是通过Kubernetes编排与调度的,因此毫无疑问通过这种形式其实就实现了Kubernetes编排和调度存储: )
Kubernetes毕竟是目前主流趋势,通过Kubernetes编排和管理存储也必然是一种发展趋势,目前OpenEBS就是CAS的一种开源实现,商业存储如PortWorx、StorageOS也是基于CAS模式的。
更多关于CAS的可以参考CNCF官宣文章Container Attached Storage: A Primer[4]。
2 简单好用的Longhorn
2.1 Longhorn简介
Longhorn[5]在我之前的文章轻量级Kubernetes k3s初探已经简单介绍过,最初由Rancher公司开发并贡献给社区,专门针对Kubernetes设计开发的云原生分布式块存储系统,因此和Kubernetes契合度很高,主要体现在两个方面,一是它本身就直接运行在Kubernetes平台上,通过容器和微服务的形式运行;其二是能很好的与PV/PVC结合。
与其他分布式存储系统最大的不同点是,Longhorn并没有设计一个非常复杂的控制器来管理海量的volume数据卷,而是将控制器拆分成一个个非常轻量级的微控制器,这些微控制器能够通过Kubernetes、Mesos等平台进行编排与调度。
每个微控制器只管理一个volume,换句话说,一个volume一个控制器,每个volume都有自己的控制器,这种基于微服务的设计使每个volume相对独立,控制器升级时可以先选择一部分卷进行操作,如果升级出现问题,可以快速选择回滚到旧版本,升级过程中只可能会影响正在升级的volume,而不会导致其他volume IO中断。
Longhorn的实现和CAS的设计理念基本是一致的,相比Ceph来说会简单很多,而又具备分布式块存储系统的一些基本功能:
-
支持多副本,不存在单点故障;
-
支持增量快照;
-
支持备份到其他外部存储系统中,比如S3;
-
精简配置(thin provisioning);
-
...
我觉得Longhorn还有一个特别好的功能是内置了一个Web UI,通过UI能够很方便的管理Node、Volume以及Backup,不得不说Longhorn真是麻雀虽小五脏俱全。
根据官方的说法,Longhorn并不是为了取代其他分布式块存储系统,而是为了设计一个更简单的适合容器环境的块存储系统,其他分布式存储有的一些高级功能Longhorn并没有实现,比如去重(deduplication)、压缩、分块、多路径等。
Longhorn存储管理机制比较简单,当在Longhorn中Node节点增加物理存储时,其本质就是把Node对应的路径通过HostPath挂载到Pod中,我们可以查看该路径的目录结构,在replicas
目录中一个volume一个子目录,文件内容如下:
# find replicas/int32bit-volume-3-ab6717d6/
replicas/int32bit-volume-3-ab6717d6/
replicas/int32bit-volume-3-ab6717d6/volume.meta
replicas/int32bit-volume-3-ab6717d6/volume-head-000.img
replicas/int32bit-volume-3-ab6717d6/revision.counter
replicas/int32bit-volume-3-ab6717d6/volume-head-000.img.meta
其中int32bibt-volume-3
是volume名称,ab6717d6
对应副本名称,子目录中包含一些volume的metadata以及img文件,而img文件其实就是一个raw格式文件:
# qemu-img info volume-head-000.img
image: volume-head-000.img
file format: raw
virtual size: 20G (21474836480 bytes)
disk size: 383M
raw格式其实就是Linux Sparse稀疏文件,由于单个文件大小受文件系统和分区限制,因此Longhorn volume会受单个磁盘的大小和性能的限制,不过我觉得Kubernetes Pod其实也很少需要用到特别大的volume。
更多关于Longhorn的技术实现原理可以参考官宣文章Announcing Longhorn: an open source project for microservices-based distributed block storage[6]。
2.2 Longhorn部署
Longhorn部署也非常简单,只需要一个kubectl apply
命令:
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml
创建完后就可以通过Service或者Ingress访问它的UI了:
longhorn webui
在Node页面可以管理节点以及物理存储,Volume页面可以管理所有的volumes,Backup可以查看备份等。
volume详情页面可以查看volume的挂载情况、副本位置、对应的PV/PVC以及快照链等:
longhorn volume
除此之外,Longhorn还支持创建备份计划,可以通过cron指定时间点或者定时对volume进行快照或者备份到S3中。
longhorn backup
2.3 Kubernetes集成Longhorn存储
Longhorn既支持FlexVolume也支持CSI接口,安装时会自动根据Kubernetes版本选择FlexVolume或者CSI。
Kubernetes集成Longhorn,根据前面对StorageClass的介绍,我们需要先安装Longhorn StorageClass:
kubectl create -f \
https://raw.githubusercontent.com/longhorn/longhorn/master/examples/storageclass.yaml
声明一个20Gi的PVC:
# kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: longhorn-volv-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 2Gi
创建Pod并使用新创建的PVC:
# kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: test-volume-longhorn
spec:
containers:
- name: test-volume-longhorn
image: jocatalin/kubernetes-bootcamp:v1
volumeMounts:
- name: volv
mountPath: /data
volumes:
- name: volv
persistentVolumeClaim:
claimName: longhorn-volv-pvc
通过Longhorn Dashboard查看volume状态已经挂载到Pod中:
longhorn pvc
Longhorn volume不仅可以通过PV形式挂载到Kubernetes容器,还可以直接通过ISCSI接口挂载到Node节点上:
longhorn_attach_to_host
此时可以通过lsblk
在OS查看到块存储设备,通过iscsiadm
命令可以查看Node节点连接的设备会话:
# lsblk -S
NAME HCTL TYPE VENDOR MODEL REV TRAN
sda 0:0:0:1 disk IET VIRTUAL-DISK 0001 iscsi
# iscsiadm -m session
tcp: [3] 10.244.0.50:3260,1 iqn.2019-10.io.longhorn:int32bit-volume-1 (non-flash)
3 CAS开源实现OpenEBS
3.1 OpenEBS简介
OpenEBS[7]是MayaData[8](之前叫CloudByte)公司开源的云原生容器存储项目,命名上可能参考了AWS EBS(Elastic Block Storage)。
OpenEBS也是目前Container Attached Storage的一种开源实现方案,因此它直接运行在Kubernetes平台上,通过Kubernetes平台进行编排与调度。
OpenEBS支持如下三种存储类型,分别为cStore、Jiva以及LocalPV。
Jiva
后端实现其实就是前面介绍的Longhorn,也就是使用了raw格式sparse稀疏文件作为虚拟磁盘实现容器volume,这个和虚拟机的本地虚拟磁盘实现类似,可以通过qemu-img info
查看volume分配的虚拟大小以及实际使用的空间,稀疏文件默认路径为/var/openebs
,所以volume的容量总大小取决于这个路径挂载的文件