PinkHello
做一个快乐的程序猿
回望K8S 持久化存储

PVPVCStorageClass 说的啥?

PV: 持久化存储数据卷,这个 API 主要定义的是一个持久化存储在宿主机上的一个目录。一般由运维人员进行定义,比如定义一个 NFS 类型的 PV

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs
spec:
  storageClassName: manual
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  nfs:
    server: 10.244.1.5
    path: "/"

PVC: POD 所希望使用的持久化存储的属性. 比如 Volume 的存储大小、可读写权限等. PVC 一般由开发人员创建、或者由 PVC模板的方式成为StatefulSet的一部分,由StatefulSet控制器负责创建带编号的PVC.

# 创建一个 1 GB 大小的PVC
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: manual
  resources:
    requests:
      storage: 1Gi
...

用户创建的PVC要真正的被容器使用的化,需要于心和某个符合条件的PV进行绑定:

  • 第一个条件,PVPVCspec 字段。例如: PV 的存储(storage)大小就必须满足 PVC 的要求
  • 第二个条件,PVPVCstorageClassName 字段名称必须一样。

下面是去使用这个PVC

apiVersion: v1
kind: Pod
metadata:
  labels:
    role: web-frontend
spec:
  containers:
  - name: web
    image: nginx
    ports:
      - name: web
        containerPort: 80
    volumeMounts:
        - name: nfs
          mountPath: "/usr/share/nginx/html"
  volumes:
  - name: nfs
    persistentVolumeClaim:
      claimName: nfs

PVC 理解为持久化存储的 接口,它提供了对某种持久化存储的描述,但不提供具体的实现,而这个持久化的实现部分由 PV 完成。

假设,我们在创建 POD 的时候,系统内并没有合适的 PV 跟它定义的 PVC 绑定,这个时候容器想要使用的 Volume 不存在,怎么办呢?

Kubernetes 内,时机存在一个专门处理持久化存储的控制器,叫做 VolumeController。这个VolumeController维护着多个控制循环, 其中有个循环就是用来撮合 PVPVC 进行绑定的角色,名字叫 PersistentVolumeController. 它会不断的查看当前的每一个 PVC, 是否处于 Bound 状态 如果不是,它会遍历所以的、可用的 PV,并尝试将其与这个声明的PVC进行绑定。这样,Kubernetes 就可以保证用户提交的每一个 PVC,只要有合适的 PV 出现,它能快速的进入绑定状态。 而所谓的绑定,其实就是将这个 PV 对象的名字填充在了 PVC 对象的 spec.volumeName 字段上。接下来 Kubernetes 只要获取到这个 PVC 对象,就一定能够找到它所绑定的 PV.

PV 对象如何变成容器里的一个持久化存储的呢?

所谓容器的 Volume ,其实就是将一个宿主机上的目录,跟容器里的目录进行绑定挂载在一起的, 所谓的 持久化Volume ,指的就是这个宿主机上的目录,具备持久化,即当目录里面的内容,既不会因为容器的删除而被清理、也不会跟当前的宿主机绑定,当容器进行重启或者在其他节点上重建之后,依然能够挂载到这个 Volume, 访问这些内容, 所以,大多数情况下,持久化的 Volume 的实现,往往依赖一个远程存储服务,比如远程文件存储(NFSGlusterFS),远程块存储(公有云的远程磁盘)。

持久化宿主机目录的过程,这个形象的成为 两阶段处理

当一个 POD 调度到一个节点上后, Kubelet 就要为这个 POD 创建一个 Volume 目录, 默认情况下 KubeletVolume 创建的目录如下所示(在宿主机上)

/var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>

接下来, Kubelet 要做的操作取决于 Volume 类型. 如果 Volume 类型是远程块存储,比如 Google CLoudPersistent Disk, 那么 Kubelet 就需要先调用 Google CloudAPI, 将它所提供的 Persistent Disk 挂载到 Pod 所在的宿主机上。 相当于执行了

gcloud compute instances attach-disk <虚拟机名字> --disk <远程磁盘名字>

为虚拟机挂载远程磁盘的操作,对应的正是 两阶段处理 的第一段。即在 Kubernetes 中的 Attach 阶段。

Attach 阶段后,要能够使用这个远程磁盘, Kubelet 需要进行第二个操作: 即使会这个磁盘设备, 然后将它挂载到宿主机知道的挂载点上。 这一步相当于执行:

# 通过lsblk命令获取磁盘设备ID
sudo lsblk
# 格式化成ext4格式
sudo mkfs.ext4 -m 0 -F -E lazy_itable_init=0,lazy_journal_init=0,discard /dev/<磁盘设备ID>
# 挂载到挂载点
sudo mkdir -p /var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>

这个将磁盘设备格式化并挂载到 Volume 宿主机目录的操作,对应的正是 “两阶段处理” 的第二段: Mount

Kubernetes 如何定义和区分这两个阶段的? 在具体的 Volume 插件的实现接口上, Kubernetes 分别给这两个阶段提供了两种不同的参数列表:

  • 第一阶段 Attach, Kubernetes 提供的可用参数是 nodeName, 即宿主机的名字
  • 第二阶段 Mount, Kubernetes 提供的可用参数是 dir, 即 Volume 的宿主机目录

在经过 “两阶段处理”, 我们得到了一个 持久化Volume 宿主机目录, 然后, Kubelet 只要把这个 Volume 目录通过 CRI 里的 Mounts 参数,传递给 Docker, 然后就可以为 POD 里的容器挂载这个 “持久化” 的 Volume

另外还有一个核心的概念 StorageClass, Kubernetes 为我们提供了一套可以自动创建 PV 的机制, 即 Dynamic Provisioning, 前面人工管理的 PV 方式叫做 Static Provisioning

Dynamic Provisioning 机制工作的核心, 在于一个名叫 StorageClass 的API对象。而这个对象的作用,就是创建 PV 的模板。StorageClass对象会定义如下两个部分内容:

  • 第一, PV 的属性。比如 存储类型、Volume 大小等
  • 第二, 创建这种 PV 需要用到的存储插件。比如 Ceph 等 有这两个信息后, Kubernetes 就可以根据用户提交的 PVC,找到一个对应的 StorageClass,然后 Kubernetes 就好调用该 StorageClass 声明的存储插件, 创建出需要的 PV
#在这个 YAML 文件里,我们定义了一个名叫 block-service 的 StorageClass。
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: block-service
provisioner: kubernetes.io/gce-pd # GCE PD 存储插件的名字
parameters:
  type: pd-ssd # SSD格式的GCE远程磁盘

Kubernetes PV 和 PVC 体系是不是多此一样

Kubernetes 支持多种的持久化 Kubernetes内置的持久化数据卷实现

在通过 PVPVC, 以及 StorageClass 这套存储体系, 为后来添加持久化存储方案,对已有的 Kubernetes 的影响,几乎可以忽略不及。作位用户, PODYAMLPVCYAML 并没有任何特殊的改变.

总结

PVC-PV设计

  • PVC 描述 POD 想要使用的持久化存储的属性,比如存储的大小、读写权限。
  • PV 描述的 一个具体的 Volume 的属性、比如 Volume 的类型、挂载目录、远程存储服务器地址
  • StorageClass 作用,充当 PV 的模板,只要同属于一个 StorageClassPVPVC,才可以绑定在一起。

Kubernetes持久化存储的原理 Kubernetes持久化存储的通过CSI插件操作原理

  • Driver Registrar 组件负责将插件注册到 kubelet 阶段,请求CSI插件的 Identity服务 获取插件信息
  • External Provisioner 组件 Provision 阶段, External Provisioner 监听(Watch)了 APIServer 里的 PVC对象。当一个 PVC 被创建时,它就会调用 CSI ControllerCreateVolume 方法,为你创建对应 PV
  • External Attacher 组件负责正是 “Attach 阶段”, 它监听了 APIServer 里的 VolumeAttachment 对象的变化, VolumeAttachment 对象Kubernetes 确认一个 Volume 可以进入 Attach 阶段 的重要标志

最后修改于 2021-05-18