AIOps 一场颠覆传统运维的盛筵
956
2022-11-01
Kubernetes系列13 一 持久化存储 PV、PVC、StorageClass
在前面讲解 StatefulSet 的章节中容器的有状态管理有简单介绍到 PV、PVC,而容器的“有状态”,最常见的状态也是持久化存储了。这一节我们将深入了解Kubernetes 容器的持久化存储到底是怎样实现的?除了 PV、PVC,StorageClass 又是什么?
首先,我们明确下什么是持久化存储?
对于 Docker 创建的容器,持久化存储是将容器的目录挂载到宿主机上。宿主机磁盘不会因为容器的删除而被清理掉,从而具备数据的持久性。
对于 Kubernetes 创建的 Pod,由于存在集群调度的原因,所以容器的目录不是挂载在宿主机上的,而是挂载在远程存储上(可以是NFS、各种存储服务)。简单的可以理解为,将 Pod 的目录挂载到一块远程磁盘上,无论 Pod 被调度到哪个节点,都可以访问到远程磁盘的数据,从而具备数据持久性。
我们再回顾下Kubernetes系列10 一 容器编排之StatefulSet 这一节中定义的 PV、PVC:
PV
apiVersion: v1kind: PersistentVolumemetadata: name: pv-claimspec: capacity: storage: 5Gi accessModes: - ReadWriteOnce nfs: path: /data/nfs server: 192.168.56.103
PVC
kind: PersistentVolumeClaimapiVersion: v1metadata: name: pv-claimspec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi
当PVC与PV完成绑定后,就可以在Pod中的 Volume 使用该存储卷做持久化存储。
apiVersion: v1kind: Podmetadata: name: pv-podspec: ... volumeMounts: - mountPath: "/usr/share/nginx/html" name: pv-storage volumes: - name: pv-storage persistentVolumeClaim: claimName: pv-claim
该 Pod 内容器的 user/share/nginx/html 目录将会挂载到 192.168.56.103 这台服务的 data/nfs目录。
在生产实践中 Kubernetes 中会有成千上万的 PVC、PV,那么PVC、PV的绑定需要什么条件呢?
首先,PV 的 spec.capcity.storage 需要大于等于PVC的spec.resources.requests.storage其次 ,PV 和 PVC 的 storageClassName 字段必须一样。
在上述的案例中并没有指定 storageClassName,是因为默认的 storageClassName为空,PV 和 PVC 的storageClassName都为空,也是相等的。
PersistentVolumeController 负责在 Kubernetes 中将 PV 和 PVC绑定起来。与其他的控制器模型一样,也是一个大的 for 循环:不停的从 ETCD 中取出还未绑定的 PVC对象,遍历所有的 PV,将满足条件的PV与PVC绑定。绑定后的PVC 对象的 spec.volumeName 字段将会填上 PV对象的名字。
在生产中,成百上千个PVC 对象就需要对应这么多个 PV 对象去与其绑定,那么业务每使用一个PVC 对象运维人员都得去创建一个对应满足条件的 PV 对象吗?要是这样运维人员只用成天围着开发转就行了,随时响应创建PV对象,其他啥也干不了了。
其实 Kubernetes 提供一种叫做 Dynamic Provisioning 机制动态的创建 PV 对象。(相应的手工创建 PV 对象的过程叫做 Static Provisioning)当开发人员使用某个PVC对象时,Kubernetes 会自动的创建一个满足条件的PV对象,并与PVC对象绑定。
Dynamic Provisioning 是依靠 StorageClass 对象实现的。PV 与 PVC 绑定的第二个条件中的 storageClassName 就是 StorageClass 对象的名称。
动态创建的PV是根据 StorageClass 对象的定义生成的。可以理解为 StorageClass 对象定义好了存储空间大小,存储插件是什么,在需要生成 PV 对象时,只需要在模板中填充 PVC对象的名称(metadata.name)就行。
我们来看下 StorageClass 对象的 yaml 定义文件:
apiVersion: storage.k8s.io/v1kind: StorageClassmetadata: name: store-pdprovisioner: kubernetes.io/gce-pdparameters: type: pd-standard fstype: ext4 replication-type: none
该 StorageClass 对象的名称为 store-pd每个 StorageClass 对象都需要指定 provisioner,官网翻译为制备器,可以就按照字面意思翻译为供应商,表示由什么存储插件来完成存储。目前支持的存储插件可见官网:https://kubernetes.io/zh/docs/concepts/storage/storage-classes/parameters为定义PV时需要的参数type:pd-standerd或者是pd-ssd 分别表示标准格式的 GCE 远程磁盘、ssd 格式的 GCE 远程磁盘fstype:文件系统类型replication-type:none是全区域的持久化磁盘,regional-pd是区域性的持久化磁盘
在我们上面讲到的PVC定义中加入一行(第8行)指定storageClassName 则可利用 Dynamic Provisioning 方式由上述 StorageClass 的定义自动生成PVC并与其绑定:
kind: PersistentVolumeClaimapiVersion: v1metadata: name: claim-testspec: accessModes: - ReadWriteOnce storageClassName: store-pd resources: requests: storage: 1Gi
在了解了 PV、PVC、StorageClass 对象后,我们可以来整理下 Kubernetes 是如何做持久化存储的:
首先是为宿主机准备一块存储空间,这个过程叫做 Attach。
当某个申明了PVC 对象的 Pod 被调度到某个Node上,无论是哪种远程存储,我们可以理解为是需要将一块存储空间挂载至宿主机上的(具体挂载在哪个目录还得通过第二步实现)。只是不同的远程存储,例如 Google Cloud、AWS 其认证方式、API 不同。
其次是将准备好的存储区域挂载至宿主机的某个目录,这个过程叫做Mount。
/var/lib/kubelet/pods/
这一步将准备好的磁盘空间挂载至宿主机的该路径。
但是在我们的生产中经常是使用的本地持久化存储。虽然很不建议这样做,如果宿主机磁盘出现问题,持久化的数据也将一并会受到影响。但是本地存储确实省事,我们来看一个定义本地持久化存储的yaml定义:
kind: StorageClassapiVersion: storage.k8s.io/v1metadata: name: local-storageprovisioner: kubernetes.io/no-provisionervolumeBindingMode: WaitForFirstConsumer
provisoner指定的 no-provisioner表示不使用任何的存储插件;指定的 volumeBindingMode=WaitForFirstConsumer 属性表示延迟绑定。
在讲解延迟绑定之前,我们可以想一下,为什么需要延迟绑定?
假设某个Pod需要被调度到 Node2 上,但是该 Pod 使用的PVC是可以与 Node1或者其他的Node上的本地持久化PV完成绑定的。如果不去延迟绑定PV C和PV,那么该Pod的调度一定会失败。
这里的延迟绑定表示在Pod调度完成之后再来绑定PVC和PV。
Kubernetes 里有一个类似 Volume Controller 的循环专门来完成延迟绑定的工作。
本系列回顾:
Kubernetes系列1 一 容器是什么?
Kubernetes系列3 一 docker隔离与限制的原理
Kubernetes系列4 一 docker镜像
Kubernetes系列5 一 实践课
Kubernetes系列6 一 Kubernetes登场
Kubernetes系列7 一 最小编排单位Pod
Kubernetes系列8 一 Pod的生命周期
Kubernetes系列9 一 终于聊到编排了
Kubernetes系列10 一 容器编排之StatefulSet
Kubernetes系列11 一 容器编排之DaemonSet
Kubernetes系列12 一 Job 与 CronJob
发表评论
暂时没有评论,来抢沙发吧~