38. Kubernetes 存储原理
本章讲解知识点
- 持久卷(Persistent Volume)详解
- PV 详解
- PVC 详解
- StorageClass 详解
- 总结
<br>
1. 持久卷(Persistent Volume)详解
1.1 基本概念
Kubernetes 中,对于存储资源的管理方式和计算资源 (CPU / 内存) 是完全不同的。为了使存储实现的细节对用户和管理员不可见,Kubernetes 引入了 Persistent Volume (PV) 和 Persistent Volume Claim (PVC) 两个资源对象来管理存储。自 1.0 版本以来,PV 被定义为容器应用程序可以使用的存储资源的抽象。管理员可以创建和配置 PV,并通过插件式的机制对存储提供商的具体实现进行管理,例如:GlusterFS、iSCSI、RBD、GCE 和 AWS 公共云提供的共享存储。除了 EmptyDir 类型的存储卷,PV 的生命周期独立于使用它的 Pod。
PVC 则是用户对存储资源的申请。类似于 Pod 消耗 Node 的资源,PVC 消耗 PV 的资源。PVC 可以申请存储空间的大小 (Size) 和访问模式 (例如 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany)。通常情况下,PV 对象是由运维人员事先创建在 Kubernetes 集群里待用的。比如,运维人员可以定义这样一个 NFS 类型的 PV。PVC 和 PV 的设计,其实跟“面向对象”的思想完全一致。PVC 可以理解为持久化存储的“接口”,它提供了对某种持久化存储的描述,但不提供具体的实现;而这个持久化存储的实现部分则由 PV 负责完成。这样做的好处是,作为应用开发者,我们只需要跟 PVC 这个“接口”打交道,而不必关心具体的实现是 NFS 还是 Ceph。毕竟这些存储相关的知识太专业了,应该交给专业的人去做。
为了满足应用程序对存储设备的不同要求,如读写速度、并发性能和数据冗余等,使用 PVC 申请的存储空间通常是不足的。为此,Kubernetes 引入了一个名为 StorageClass 的资源对象,自版本 1.4 起用于标记存储资源和性能,并根据 PVC 的需求动态分配合适的 PV 资源。自版本 1.6 开始,StorageClass 和存储资源的动态供应机制得到完善,可以按需创建存储卷,实现了共享存储的自动化管理。
StorageClass 的定义允许管理员将存储资源定义为特定类型或配置描述,例如快速存储、慢速存储、有数据冗余或无数据冗余。用户可以根据 StorageClass 的描述快速了解各种存储资源的特性,从而根据应用程序的需求申请相应的存储资源。
从 Kubernetes 1.9 版本开始,引入了容器存储接口 (CSI) 机制,用于建立一套标准的存储管理接口,以连接 Kubernetes 和外部存储系统。通过此机制,存储提供商可以在 Kubernetes 之外提供具体的存储驱动,并通过标准接口为容器提供存储服务,这将有助于解耦 Kubernetes 代码和存储相关代码,类似于 CRI 和 CNI。
1.2 工作原理
PV(持久卷)可以被视为可用的存储资源,PVC(持久卷声明)则是对存储资源的需求。它们的生命周期可以分为以下四个阶段:资源供应(Provisioning)、资源绑定(Binding)、资源使用(Using)和资源回收(Reclaiming),如下图所示:
1. 资源供应
Kubernetes 支持两种资源供应模式:静态模式 (Static) 和动态模式 (Dynamic),资源供应的结果就是将合适的 PV 与 PVC 成功绑定。
- 静态模式:集群管理员预先创建许多 PV,在 PV 的定义中能够体现存储资源的特性
- 动态模式:集群管理员无需预先创建 PV,而是通过 StorageClass 的设置对后端存储资源进行描述,标记存储的类型和特性。用户通过创建 PVC 对存储类型进行申请,系统将自动完成 PV 的创建和 PVC 的绑定。如果 PVC 声明的 Class 为空“”,则说明 PVC 不用动态模式
静态资源供应模式下,通过 PV 和 PVC 完成绑定并供 Pod 使用的原理如下图:
在动态资源供应模式下,通过 StorageClass 和 PVC 完成资源动态绑定(系统自动生成 PV),并供 Pod 使用的原理如下图:
2. 资源绑定
用户定义 PVC 后,系统会根据其对存储资源的请求(包括存储空间和访问模式),从现有的 PV 中选择一个满足要求的 PV 并将其绑定到 PVC 上,从而使用户的应用程序可以使用该 PVC。如果系统中没有满足 PVC 要求的 PV,则 PVC 将保持 Pending 状态,直到管理员创建一个符合要求的 PV。
PV 与 PVC 的绑定关系是一对一的,绑定后 PV 将被独占,无法再与其他 PVC 绑定。如果 PVC 申请的存储空间小于 PV 的空间,则整个 PV 的空间都将为 PVC 所用,可能会造成资源浪费。
如果资源供应采用动态模式,则系统会为 PVC 找到合适的 StorageClass,并自动创建一个 PV 并将其绑定到 PVC 上。
3. 资源使用
当 Pod 需要使用存储资源时,需要在 Volume 的定义中引用类型为 “persistentVolumeClaim” 的 PVC 卷,并将其挂载到容器内的特定路径中供使用。
一旦 PVC 被挂载,Pod 就可以使用存储资源了。同一 PVC 可以被多个 Pod 同时挂载使用。在这种情况下,应用程序需要处理多个进程访问同一存储的问题。
相对于容器应用 (Pod),存储资源 (PV、PVC) 是独立的管理资源,可以单独删除。删除存储资源时,系统会检查其当前是否正在使用。如果正在使用,则相关资源的删除将被推迟,直到没有被使用才会执行删除操作,以确保数据不会因资源被直接删除而丢失。这种机制被称为“使用中的存储对象的保护机制” (Storage Object in Use Protection)。
该保护机制适用于 PV 和 PVC 两种资源:
(1) 对于 PVC 的删除操作将等到使用它的 Pod 被删除之后在执行
举例来说,当用户删除一个正在被 Pod 使用的 PVC 时,PVC 对象不会被立刻删除,查看 PVC 对象的状态,可以看到其状态为 “Terminating”,以及系统为其设置的 Finalizer 为 “kubernetes.io/pvc-protection”。
(2) 对于 PV 的删除操作将等到绑定它的 PVC 被删除之后再执行
同理。
4. 资源回收(Reclaiming)
用户在使用存储资源完毕后,可以删除 PVC。与该 PVC 绑定的 PV 将被标记为“已释放”,但还不能立即与其它 PVC 绑定。通过之前 PVC 写入的数据可能还被留在存储设备上,只有在清除这些数据之后,该 PV 才能再次使用。
管理员可以对 PV 设置资源回收策略 (Recalim Policy),可以设置3种回收策略:Retain、Delete 和 Recycle。
1)Retain(保留数据)
Retain 策略表示在删除 PVC 之后,与之绑定的 PV 不会被删除,仅被标记为已释放 (released)。PV 中的数据仍然存在,在清空之前不能被新的 PVC 使用,需要管理员手工清理之后才能继续使用,清理步骤:
- 删除 PV 资源对象,此时与该 PV 关联的某些外部存储提供商 (例如 AWSElasticBlockStore、GCEPersistentDisk、Cinder 等) 的后端存储资产 (asset) 中的数据仍然存在。
- 手工清理 PV 后端存储资产 (asset) 中的数据
- 手工删除后端存储 PV。如果希望重用该存储,则可以创建一个新的 PV 与之关联
2) Delete (删除数据)
Delete 策略表示自动删除 PV 资源对象和相关的后端存储资产,并不是所有类型提供商都支持 Delete 策略,目前支持 Delete 策略的存储提供商包括 AWSElasticBlockStore、GCEPersistentDisk、Azure Disk、Cinder 等。
通过动态供应机制创建的 PV 将集成 StorageClass 的回收策略,默认为 Delete 策略。管理员应该基于用户的需求设置 StorageClass 的回收策略,或者在创建出 PV 后手动更新其回收策略。
3) Recycle(弃用)
目前只有 HostPort 和 NFS 类型的 Volume 支持 Recycle 策略,其实机制为运行 rm -rf /thevolume/*
命令,删除 Volume 目录下的全部文件,使得 PV 可以继续被新的 PVC 使用。
需要注意的是 Recycle 策略已被弃用,推荐以动态供应机制管理容器所需的存储资源。
1.3 PVC 绑定 Pod 示例
PV(Persistent Volume)是一种描述持久化存储数据卷的 API 对象。它主要定义了一个持久化存储在宿主机上的目录,比如一个 NFS 的挂载目录。通常情况下,PV 对象是事先由运维人员在 Kubernetes 集群中创建好的。例如,运维人员可以创建一个类似以下所示的 NFS 类型的 PV:
apiVersion: v1 kind: PersistentVolume metadata: name: nfs spec: storageClassName: manual capacity: storage: 1Gi accessModes: - ReadWriteMany nfs: server: 10.244.1.4 path: "/"
PVC 描述的是 Pod 所需求的持久化存储的属性,包括 Volume 存储的大小、可读写权限等。通常情况下,PVC 对象由开发人员创建,或者通过 StatefulSet 的 PVC 模板成为 StatefulSet 的一部分并由 StatefulSet 控制器创建。例如,开发人员可以声明一个 1 GiB 大小的 PVC:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs spec: accessModes: - ReadWriteMany storageClassName: manual resources: requests: storage: 1Gi
要让用户创建的 PVC 生效,需要将它绑定到符合条件的 PV 上。绑定前需要满足两个条件:PV 和 PVC 的 spec 字段需要匹配,比如 PV 的存储大小要满足 PVC 的要求;同时,PV 和 PVC 的 storageClassName 字段也必须一致。
在成功地将 PVC 和 PV 进行绑定之后,Pod 就能够像使用 hostPath 等常规类型的 Volume 一样,在自己的 YAML 文件里声明使用这个 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
可以看出,在 Pod 的配置文件中,需要在 volumes 字段中声明要使用的 PVC 名称。当该 Pod 被创建之后,kubelet 会将该 PVC 所对应的 PV 挂载到该 Pod 容器内指定的目录上,即实现了容器和持久化存储之间的连接。
1.4 持久化 Volume 原理
这个 PV 对象,如何变成容器里的一个持久化存储。
在前面讲解容器基础的时候,已经详细剖析了容器 Volume 的挂载机制。用一句话总结,所谓容器的 Volume,其实就是将一个宿主机上的目录,跟一个容器里的目录绑定挂载在了一起。
持久化 Volume 意味着在宿主机上创建一个目录,该目录具有持久性。即使容器被删除,该目录中的内容也不会被清除,且该目录不会与当前宿主机绑定。这意味着当容器在其他节点上重新创建时,仍可以通过挂载该 Volume 访问该目录中的内容。但是,类型为 hostPath 和 emptyDir 的 Volume 不具备此功能,因为它们可能会被 kubelet 清除并且不能迁移到其他节点。因此,大多数情况下,持久化 Volume 的实现依赖于远程存储服务,例如远程文件存储(如 NFS、GlusterFS)和远程块存储(如公有云提供的远程磁盘)。 Kubernetes 通过使用这些存储服务为容器准备一个持久化的宿主机目录,并在将来进行绑定挂载时使用,从而实现持久化。
当一个 Pod 被调度到某个节点上时,Kubelet 会执行“两阶段处理”操作,即为该 Pod 创建“持久化”目录。默认情况下,Kubelet 会在节点上创建一个宿主机路径,用于存放该 Pod 的 Volume 目录。
/var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>
kubelet 需要根据不同的 Volume 类型来执行相应的操作。如果 Volume 类型是远程块存储,例如 Google Cloud 的 Persistent Disk(GCE 提供的远程磁盘服务),那么 kubelet 就需要调用 Goolge Cloud 的 API 将其提供的 Persistent Disk 挂载到 Pod 所在的宿主机上。块存储通常被理解为一块磁盘。这相当于执行:
$ gcloud compute instances attach-disk <虚拟机名字> --disk <远程磁盘名字>
这一步为虚拟机挂载远程磁盘的操作,对应的是“两阶段处理”的第一阶段,也称为“Attach”阶段。在 Kubernetes 中,kubelet 会先调用相应的 API,将提供的远程块存储挂载到 Pod 所在的宿主机上。对于远程磁盘,比如 Google Cloud 的 Persistent Disk,我们可以简单地理解为一块磁盘。
完成 Attach 阶段后,为了能够使用这个远程磁盘,kubelet 还需要进行第二个操作,即将这个磁盘设备进行格式化,然后将其挂载到指定的挂载点上。这个挂载点,正是我们之前提到的 Volume 的宿主机目录。因此,这一步相当于执行以下操作:
# 通过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。在 Mount 阶段完成后,该 Volume 的宿主机目录将成为一个“持久化”目录,容器在其中写入的内容将保存在 Google Cloud 的远程磁盘中。如果 Volume 类型是远程文件存储(例如 NFS),则 kubelet 的处理过程将更加简单。因为在这种情况下,kubelet 可以跳过“第一阶段”(Attach)的操作,这是因为通常情况下远程文件存储没有需要挂载在宿主机上的“存储设备”。因此,kubelet 会直接从“第二阶段”(Mount)开始准备宿主机上的 Volume 目录。在这一步骤中,kubelet 需要充当客户端,将远程 NFS 服务器的目录(例如“/”目录)挂载到 Volume 的宿主机目录上,相当于执行以下命令:
$ mount -t nfs <NFS服务器地址>:/ /var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>
通过这个挂载操作,Volume 的宿主机目录就成为了一个远程 NFS 目录的挂载点,后面你在这个目录里写入的所有文件,都会被保存在远程 NFS 服务器上。所以,我们也就完成了对这个 Volume 宿主机目录的“持久化”。
以上,就是 Kubernetes 处理 PV 的具体原理了。对应地,在删除一个 PV 的时候,Kubernetes 也需要 Unmount 和 Dettach 两个阶段来处理。这个过程我就不再详细介绍了,执行“反向操作”即可。
<br>
2. PV 详解
下面是一个标准的 PV 定义:
apiVersion: v1 kind: PersistentVolume metadata: name: pv0001 spec: capacity: # 存储能力,设置为 5GiB 的存储空间 storage: 5Gi volumeMode: Filesystem # 存储卷模式,设置为文件系统 accessModes: # 访问模式,设置为读写权限,并只能被单个 Node 挂载 - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle # 回收策略,设置为回收空间 storageClassName: slow # 存储类型,设置为 slow(系统中必须已经存在名为 slow 的 StorageClass) mountOptions: # 挂载参数,设置了 hard 和 nfsvers=4.1 - hard - nfsvers=4.1 nfs: # 后端存储类型为 nfs,路径为 /tmp,地址为 172.17.0.2 path: /tmp server: 172.17.0.2
通过上面的例子可以看出,在定义 PV 时有很多的字段选项都需要设置,下面我们一一讲解这些字段选项。
1.capacity(存储能力)
主要设置的是存储空间的大小 storage,以后可能还支持 IOPS、吞吐率等设置。
2.volumeMode(存储卷模式)
存储卷的模式有两种:Filesystem (文件系统) 和 Block (块设备)。如果省略这个设置的话,默认为 Filesystem。
支持块类型的存储卷有:AWSElasticBlockStore、AzureDisk、FC、GCEPersistentDisk、iSCSI、Local volume、RBD (Ceph Block Device)、VsphereVolume
3.accessModes(访问模式)
设置 PV 的访问模式,描述应用对存储资源的访问权限,现在的访问模式有如下 3 类:
- ReadWriteOnce (RWO):读写权限,只能被单个 Node 挂载。
- ReadOnlyMany (ROX):只读权限,可以被多个 Node 挂载。
- ReadWriteMany (RWX):读写权限,可以被多个 Node 挂载。用于多个节点的不同 Pod 可以同时读写一个 PVC。
某些 PV 可以支持多种访问模式(比如:NFS 可以支持多个应用的读写操作),但是在挂载的时候只能支持一种访问模式,多种访问模式并不能同时生效。
下面的表格展示了不同卷类型所支持的访问模式:
4.persistentVolumeReclaimPolicy(PV 回收策略)
当前的回收策略有 3 种:
- Retain(保留):后续需要手工处理。
- Recycle(回收空间):简单清除文件的操作(执行
rm -rf /thevolume/*
命令)。 - Delete(删除):与 PV 相连的后端存储完成 Volume 的删除操作(比如:AWS EBS、GCE PD、Azure Disk 和 OpenStack Cinder 等设备内部的 Volume 清理)。
支持对应策略的卷分别有:
- Recycle 策略:NFS、HostPath
- Delete 策略:AWS EBS、GCE PD、Azure Disk 和 OpenStack
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
本专刊适合于立志转行云计算的小白,有一定的编程、操作系统、计算机网络、数据结构、算法基础。 本专刊同时也适合于面向云计算(Docker + Kubernetes)求职的从业者。 本专刊囊括了云计算、VMWare、Docker、Kubernetes、Containerd等一系列知识点的讲解,并且最后总