背景

Kubernetes中最小的管理单元是一个PodPod中产生的数据都是临时的,当Pod重启时里边的数据会丢失。理想情况下服务应该是无状态的,但实际应用中我们很难做到服务完全无状态化,有些服务一定是有状态的,要想让这些服务能运行在集群中就不得不先解决数据持久化的问题,所以在Kubernetes集群下我们需要一种机制来解决这个问题,这就是Kubrnetes存储的作用。

云计算的普级加上Cloud native微服务概念的大火,让Kubernetes成为近似OS级的标准。反过来它本身也必须易扩展足够开放、包容去支持各种云计算厂商,从它的release路线中就可以可见一斑,当然主要开发者也是来自己这些大厂,成就彼此。存储体系的演进也一样,朝着开放可扩展的方向发展。

主要概念及用法

Kubernetes存储系统有几个重要概念:VolumePV(PersistentVolume )PVC(PersistentVolumeClaim)StorageClass

Volume

Volume是一切底层存储的抽象,也是最基础的使用方式,其底层可以是不同类型的存储方案,kubernetes默认支持多种类型:

  • awsElasticBlockStore
  • azureDisk
  • azureFile
  • cephfs
  • cinder
  • configMap
  • ……

支持的全部类型请参见这里

用法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
apiVersion: v1
kind: Pod
metadata:
  name: test-ebs
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test-ebs
      name: test-volume
  volumes:
  - name: test-volume
    # This AWS EBS volume must already exist.
    awsElasticBlockStore:
      volumeID: <volume-id>
      fsType: ext4

Persistent Volumes

通过上边例子我们不难看到Volume虽然已经满足了基本的数据持久化需求,但是因为处于最底层,所以需要使用人员掌握不同存储方案的技术细节,如:使用NFS你需要知道NFS Server地址;使用rbd你需要知道Ceph Mon的地址及Key等。通常在一个公司运维一般是资源的提供方,开发是资源的需求方。开发通常不太关注底层存储方案和细节,他们更多只关注自己服务需要多大空间,IO要求等,至于底层存储细节那是运维人员应该掌握的。所以在这种背景之下就需要一种对Volume更高的抽象,这就是PV,PVC要来解决的问题。

PV: 一个实体存储单元。通常由集群管理人员,一般是运维人员提供,可以理解为了一块硬盘。

PVC: 一个逻辑上的存储单元,它会消费PV。

用法:

 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
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv001
  labels:
    type: amazonEBS
spec:
  capacity:
    storage: 20Gi
  accessModes:
    - ReadWriteOnce
  awsElasticBlockStore:
    volumeID: vol-xxxxxxxxxxxxxxxx
    fsType: ext4
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nginx-pvc
  labels:
    type: amazonEBS
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  selector:
    matchLabels:
      type: "amazonEBS"

Storage Class

虽然已经有了PV、PVC这一层抽象,开发人员已经不需要了解底层存储细节,但是PV得需要集群管理人员提前手动创建好,不是很不方便,所以该轮到Storage Class出山了。

Storage Class为集群管理员提供了一种动态提供PV的能力。开发人员按需要申请PVCStorage Class动态创建出需要的PV

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
reclaimPolicy: Retain
allowVolumeExpansion: true
mountOptions:
  - debug
volumeBindingMode: Immediate

插件系统

in-tree

在FlexVolume插件前,Kubernetes支持丰富的存储类型都是以in-tree的方式提供的,这意味着它的代码是Kubernetes代码的一部分,并必须随Kubernetes二进制一起发布,这就会带来几个问题:

  • 修复Bug和增加对新存储类型的支持都比较困难,必须等Kubernetes的发版过程。

  • 第三方的存储代码存在于核心代码中使得整个服务的安全性和稳定性无法保障,如果这些代码有Bug也会影响Kubernetes自身。

  总的来说就是这种in-tree的插件机制可扩展性和维护性都很差,我称它为0.x版本

FlexVolume

Kubernetes 自1.2版本开始推出了out-of-tree的插件1.0方案,来弥补in-tree插件的不足。用户可以自己开发FlexVoume驱动来支持自己的存储类型。插件实现请参考这里

虽然FlexVolume插件方案解决了in-tree插件的各种问题,但它本身也有不足。

  • 功能比较简单,只实现了Attach/DetachMount/Unmount功能,没有实现动态提供、快照等功能

  • Kubernetes和驱动的交互通过二进制调用的方式,而且每一次对插件的调用都是一个独立操作,当我们想在两次调用过程中传递一些信息时就比较困难,只能先存在本地的一个临时文件中,然后再一次调用时再去读取。

总结来说这一插件方案很粗糙,终究不能成为正统,所以在CSI方案出来后只能让贤,虽然官方为了保持兼容性还会一直支持,但已成为历史。

CSI

全新的插件方案,插件和驱动调用通过grpc协议,功能也比较丰富,支持存储卷动态提供、快速、动态扩容等,我称他为插件2.0,带表着未来,连in-tree的插件也在做平滑迁移,当系统中运行有对应类型的CSI驱动时,你的使用方式虽然还是in-tree的方式,但Kubernetes已在后台默默为你替换成了CSI插件方案,Volume的Attach/Detah、Mount/Unmount等操作都会调用相应的CSI驱动完成。后续会详细介绍一下CSI的实现原理和编写自己的你CSI驱动。

总结

Kubernetes 存储系统概念虽然比较多,但核心要点只有一个,那就是

如何将目标存储Moun到Kubernetes Node节点上。

剩下不过就是使用类似 docker run -d --name=nginx -v nginx-vol:/usr/share/nginx/html nginx:latest命令将宿主机上的一个目录映射到Pod中,这样Pod中的容器就可以使用这个目录存储数据。

认识这一点对理解整个存储系统很有帮助,虽然容器也有办法绕开宿主机直接mount后端存储,但通常不会这么做,所以一切问题的核心就是要解决如何后端存储挂载到Node节点上来。

参考

https://github.com/kubernetes/community/blob/master/contributors/devel/sig-storage/flexvolume.md