简介
PV 和 PVC
与CPU 和 Mem 这些资源相比,“存储”对k8s 来说更像是“外设”,k8s 提供统一的“总线”接入。Kata Containers 创始人带你入门安全容器技术OCI规范规定了容器之中应用被放到什么样的环境下、如何运行,比如说容器的根文件系统上哪个可执行文件会被执行,是用什么用户执行,需要什么样的 CPU,有什么样的内存资源、外置存储,还有什么样的共享需求等等。
为何引入PV、PVC以及StorageClass?
Kubernetes云原生开源分布式存储介绍早期Pod使用Volume的写法
apiVersion: v1
kind: Pod
metadata:
labels:
role: web-frontend
spec:
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
volumeMounts:
- name: ceph
mountPath: "/usr/share/nginx/html"
volumes:
- name: ceph
capacity:
storage: 10Gi
cephfs:
monitors:
- 172.16.0.1:6789
- 172.16.0.2:6789
- 172.16.0.3:6789
path: /ceph
user: admin
secretRef:
name: ceph-secret
这种方式至少存在两个问题:
- Pod声明与底层存储耦合在一起,每次声明Volume都需要配置存储类型以及该存储插件的一堆配置,如果是第三方存储,配置会非常复杂。
- 开发人员的需求可能只是需要一个20GB的卷,这种方式却不得不强制要求开发人员了解底层存储类型和配置。
于是引入了PV(Persistent Volume),PV其实就是把Volume的配置声明部分从Pod中分离出来,PV的spec部分几乎和前面Pod的Volume定义部分是一样的由运维人员事先创建在 Kubernetes 集群里待用
apiVersion: v1
kind: PersistentVolume
metadata:
name: cephfs-pv
spec:
capacity:
storage: 10Gi
cephfs:
monitors:
- 172.16.0.1:6789
- 172.16.0.2:6789
- 172.16.0.3:6789
path: /ceph_storage
user: admin
secretRef:
name: ceph-secret
有了PV,在Pod中就可以不用再定义Volume的配置了,直接引用即可。但是这没有解决Volume定义的第二个问题,存储系统通常由运维人员管理,开发人员并不知道底层存储配置,也就很难去定义好PV。为了解决这个问题,引入了PVC(Persistent Volume Claim),声明与消费分离,开发与运维责任分离。
kind:PersistentVolumeClaim
apiVersion:v1
metadata:
name: cephfs-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 8Gi
通过 kubectl get pv
命令可看到 PV 和 PVC 的绑定情况
apiVersion: v1
kind: Pod
metadata:
labels:
role: web-frontend
spec:
containers:
- name: web
image: nginx
ports:
- name: web
containerPort: 80
volumeMounts:
- name: cephfs-volume
mountPath: "/usr/share/nginx/html"
volumes:
- name: cephfs-volume
persistentVolumeClaim:
claimName: cephfs-pvc
运维人员负责存储管理,可以事先根据存储配置定义好PV,而开发人员无需了解底层存储配置,只需要通过PVC声明需要的存储类型、大小、访问模式等需求即可,然后就可以在Pod中引用PVC,完全不用关心底层存储细节。
PS:感觉上,在Pod的早期,以Pod 为核心,Pod 运行所需的资源都定义在Pod yaml 中,导致Pod 越来越臃肿。后来,Kubernetes 集群中出现了一些 与Pod 生命周期不一致的资源,并单独管理。 Pod 与他们 更多是引用关系, 而不是共生 关系了。
Persistent Volume(PV)和 Persistent Volume Claim(PVC)
PVC 和 PV 的设计,其实跟“面向对象”的思想完全一致。PVC 可以理解为持久化存储的“接口”,它提供了对某种持久化存储的描述,但不提供具体的实现;而这个持久化存储的实现部分则由 PV 负责完成。这样做的好处是,作为应用开发者,我们只需要跟 PVC 这个“接口”打交道,而不必关心具体的实现是 NFS 还是 Ceph
PVC | Pod | |
---|---|---|
资源 | 消耗 PV 资源,PV资源是集群的 | 消耗 Node 资源 |
可以请求特定存储卷的大小及访问模式 | Pod 可以请求特定级别的资源(CPU 和内存) | |
确定Node后,为Node挂载存储设备 ==> Pod 为Node 带了一份“嫁妆” |
能调度到Node上,说明Node本身的CPU和内存够用 | |
完全是 Kubernetes 项目自己负责管理的 runtime 只知道mount 本地的一个目录 |
容器操作基本委托给runtime |
PVC、PV 的一些属性:
- PVC 和 PV 总是成对出现的,PVC 必须与 PV 绑定后才能被应用(Pod)消费;
- PVC 和 PV 是一一绑定关系,不存在一个 PV 被多个 PVC 绑定,或者一个 PVC 绑定多个 PV 的情况;
- 消费关系上:Pod 消费 PVC,PVC 消费 PV,而 PV 定义了具体的存储介质。
K8s 持久化存储流程
详解 Kubernetes Volume 的实现原理集群中的每一个卷在被 Pod 使用时都会经历四个操作,也就是附着(Attach)、挂载(Mount)、卸载(Unmount)和分离(Detach)。如果 Pod 中使用的是 EmptyDir、HostPath 这种类型的卷,那么这些卷并不会经历附着和分离的操作,它们只会被挂载和卸载到某一个的 Pod 中。
Volume 的创建和管理在 Kubernetes 中主要由卷管理器 VolumeManager 和 AttachDetachController 和 PVController 三个组件负责。
- VolumeManager 在 Kubernetes 集群中的每一个节点(Node)上的 kubelet 启动时都会运行一个 VolumeManager Goroutine,它会负责在当前节点上的 Pod 和 Volume 发生变动时对 Volume 进行挂载和卸载等操作。
- AttachDetachController 主要负责对集群中的卷进行 Attach 和 Detach
- 让卷的挂载和卸载能够与节点的可用性脱离;一旦节点或者 kubelet 宕机,附着(Attach)在当前节点上的卷应该能够被分离(Detach),分离之后的卷就能够再次附着到其他节点上;
- 保证云服务商秘钥的安全;如果每一个 kubelet 都需要触发卷的附着和分离逻辑,那么每一个节点都应该有操作卷的权限,但是这些权限应该只由主节点掌握,这样能够降低秘钥泄露的风险;
- 提高卷附着和分离部分代码的稳定性;
- PVController 负责处理持久卷的变更
流程如下:
- 用户创建了一个包含 PVC 的 Pod,该 PVC 要求使用动态存储卷;
- Scheduler 根据 Pod 配置、节点状态、PV 配置等信息,把 Pod 调度到一个合适的 Worker 节点上;
- PV 控制器 watch 到该 Pod 使用的 PVC 处于 Pending 状态,于是调用 Volume Plugin(in-tree)创建存储卷,并创建 PV 对象(out-of-tree 由 External Provisioner 来处理);
- AD 控制器发现 Pod 和 PVC 处于待挂接状态,于是调用 Volume Plugin 挂接存储设备到目标 Worker 节点上
- 在 Worker 节点上,Kubelet 中的 Volume Manager 等待存储设备挂接完成,并通过 Volume Plugin 将设备挂载到全局目录:
/var/lib/kubelet/pods/[pod uid]/volumes/kubernetes.io~iscsi/[PV name]
(以 iscsi 为例); - Kubelet 通过 Docker 启动 Pod 的 Containers,用 bind mount 方式将已挂载到本地全局目录的卷映射到容器中。
在 Kubernetes 中,实际上存在着一个专门处理持久化存储的控制器,叫作 Volume Controller。这个Volume Controller 维护着多个控制循环,其中有一个循环,扮演的就是撮合 PV 和 PVC 的“红娘”的角色。它的名字叫作 PersistentVolumeController
Kubernetes 中 PV 和 PVC 的状态变化 |操作| PV 状态| PVC 状态| |—|—|—| |创建 PV| Available| -| |创建 PVC| Available| Pending| || Bound| Bound| |删除 PV| -/Terminating| Lost/Bound| |重新创建 PV| Bound| Bound| |删除 PVC| Released| -| |后端存储不可用| Failed| -| |删除 PV 的 claimRef| Available| -|