k8sPV、PVC、StorageClass存储状态(20250216)
k8s:PV、PVC、StorageClass存储状态(2025/02/16)
所谓容器的 Volume,其实就是将一个宿主机上的目录,跟一个容器里的目录绑定挂载在了一起
PV与PVC长啥样,有什么用
PV 描述的,是持久化存储数据卷,由运维人员事先创建在 Kubernetes 集群里待用的
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs
spec:
storageClassName: manual
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
nfs:
server: 10.244.1.4
path: "/"
manual
表示这个 PV 是手动管理的,而不是由动态存储供应器自动创建的。
server: 10.244.1.4
是 NFS 服务器的 IP 地址
ReadWriteMany
:表示这个 PV 可以被多个节点以读写模式挂载。
PVC 描述的,则是 Pod 所希望使用的持久化存储的属性。比如,Volume 存储的大小、可读写权限等等。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs
spec:
accessModes:
- ReadWriteMany
storageClassName: manual
resources:
requests:
storage: 1Gi
声明一个 1 GiB 大小的 PVC
而用户创建的 PVC 要真正被容器使用起来,就必须先和某个符合条件的 PV 进行绑定
PV和PVC绑定的两个条件
- 第一个条件,当然是 PV 和 PVC 的 spec 字段。比如,PV 的存储(storage)大小,就必须满足 PVC 的要求。
- 第二个条件,则是 PV 和 PVC 的 storageClassName 字段必须一样。
在成功地将 PVC 和 PV 进行绑定之后,Pod 就能够像使用 hostPath 等常规类型的 Volume 一样,在自己的 YAML 文件里声明使用这个 PVC 了
pod挂载
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,也就是一个 NFS 类型的 Volume,挂载在这个 Pod 容器内的目录上。
- PV 和 PVC 的绑定:
- 管理员创建了一个 PV(
PersistentVolume
),定义了 NFS 存储的详细信息。- 用户创建了一个 PVC(
PersistentVolumeClaim
),请求 1Gi 的存储,并指定存储类为manual
。- Kubernetes 根据 PVC 的请求,将 PVC 绑定到符合条件的 PV(这里是
nfs
PV)。- Pod 使用 PVC:
- Pod 通过
volumes
字段引用 PVC(claimName: nfs
)。- PVC 将 NFS 存储挂载到 Pod 中,路径为
/usr/share/nginx/html
。- Nginx 容器可以使用这个目录来提供静态文件服务。
验证 Pod 是否成功挂载 NFS 存储
kubectl get pods
kubectl exec -it <pod-name> -- /bin/sh #<pod-name> 不要光顾着复制啊
ls /usr/share/nginx/html
如果挂载成功,你应该能够看到 NFS 服务器上 /
路径下的文件。
“持久化”宿主机目录的过程————“两阶段处理”
当一个 Pod 调度到一个节点上之后,kubelet 就要负责为这个 Pod 创建它的 Volume 目录。默认情况下,kubelet 为 Volume 创建的目录是如下所示的一个宿主机上的路径:
/var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>
接下来,kubelet 要做的操作就取决于你的 Volume 类型了。
GCE 提供的远程磁盘服务
如果你的 Volume 类型是远程块存储,比如 Google Cloud 的 Persistent Disk(GCE 提供的远程磁盘服务),那么 kubelet 就需要先调用 Goolge Cloud 的 API,将它所提供的 Persistent Disk 挂载到 Pod 所在的宿主机上。
备注:你如果不太了解块存储的话,可以直接把它理解为:一块磁盘。
为虚拟机挂载远程磁盘的操作,对应的正是“两阶段处理”的第一阶段。在 Kubernetes 中,我们把这个阶段称为 Attach。
Attach 阶段完成后,为了能够使用这个远程磁盘,kubelet 还要进行第二个操作,即:格式化这个磁盘设备,然后将它挂载到宿主机指定的挂载点上。不难理解,这个挂载点,正是我在前面反复提到的 Volume 的宿主机目录。
将磁盘设备格式化并挂载到 Volume 宿主机目录的操作,对应的正是“两阶段处理”的第二个阶段,我们一般称为:Mount。
Mount 阶段完成后,这个 Volume 的宿主机目录就是一个“持久化”的目录了,容器在它里面写入的内容,会保存在 Google Cloud 的远程磁盘中。
Volume 类型是远程文件存储(比如 NFS)
kubelet 可以跳过“第一阶段”(Attach)的操作,这是因为一般来说,远程文件存储并没有一个“存储设备”需要挂载在宿主机上。
从“第二阶段”(Mount)开始准备宿主机上的 Volume 目录
kubelet 需要作为 client,将远端 NFS 服务器的目录(比如:“/”目录),挂载到 Volume 的宿主机目录上,即相当于执行如下所示的命令:
$ mount -t nfs <NFS服务器地址>:/ /var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>
Volume 的宿主机目录就成为了一个远程 NFS 目录的挂载点,后面你在这个目录里写入的所有文件,都会被保存在远程 NFS 服务器上。所以,我们也就完成了对这个 Volume 宿主机目录的“持久化”。
在 Kubernetes 中,容器运行时和 kubelet 之间的通信是通过 CRI(Container Runtime Interface)实现的。CRI 定义了容器运行时和 kubelet 之间的标准接口,使得不同的容器运行时可以与 kubelet 进行交互,并且支持 Pod 中的卷附加、挂载、卸载等操作。 因此,kubelet 会将“持久化”卷的目录通过 CRI 里的 Mounts 参数传递给 Docker,使得 Docker 可以为 Pod 中的容器挂载这个卷,并且支持容器对卷中数据的读写操作。
对应地,在删除一个 PV 的时候,Kubernetes 也需要 Unmount 和 Dettach 两个阶段来处理。
关于 PV 的“两阶段处理”流程,是靠独立于 kubelet 主控制循环(Kubelet Sync Loop)之外的两个控制循环来实现的。
kubelet 的一个主要设计原则,就是它的主控制循环绝对不可以被 block
StorageClass 对象的作用————创建 PV 的模板
StorageClass 对象会定义如下两个部分内容:
- 第一,PV 的属性。比如,存储类型、Volume 的大小等等。
- 第二,创建这种 PV 需要用到的存储插件。比如,Ceph 等等。
有了这样两个信息之后,Kubernetes 就能够根据用户提交的 PVC,找到一个对应的 StorageClass 了。然后,Kubernetes 就会调用该 StorageClass 声明的存储插件,创建出需要的 PV。
假如我们的 Volume 的类型是 GCE 的 Persistent Disk 的话,运维人员就需要定义一个如下所示的 StorageClass:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: block-service
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-ssd
这个 StorageClass 的 provisioner 字段的值是:kubernetes.io/gce-pd,这正是 Kubernetes 内置的 GCE PD 存储插件的名字。
而这个 StorageClass 的 parameters 字段,就是 PV 的参数。比如:上面例子里的 type=pd-ssd,指的是这个 PV 的类型是“SSD 格式的 GCE 远程磁盘”。
由于需要使用 GCE Persistent Disk,上面这个例子只有在 GCE 提供的 Kubernetes 服务里才能实践。如果你想使用我们之前部署在本地的 Kubernetes 集群以及 Rook 存储服务的话,你的 StorageClass 需要使用如下所示的 YAML 文件来定义:
apiVersion: ceph.rook.io/v1beta1
kind: Pool
metadata:
name: replicapool
namespace: rook-ceph
spec:
replicated:
size: 3
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: block-service
provisioner: ceph.rook.io/block
parameters:
pool: replicapool
#The value of "clusterNamespace" MUST be the same as the one in which your rook cluster exist
clusterNamespace: rook-ceph
它声明使的存储插件是由 Rook 项目。
在 Kubernetes 里创建这个 StorageClass :
$ kubectl create -f sc.yaml
作为应用开发者,我们只需要在 PVC 里指定要使用的 StorageClass 名字即可,如下所示:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: claim1
spec:
accessModes:
- ReadWriteOnce
storageClassName: block-service
resources:
requests:
storage: 30Gi
可以看到,我们在这个 PVC 里添加了一个叫作 storageClassName 的字段,用于指定该 PVC 所要使用的 StorageClass 的名字是:block-service。
以 Google Cloud 为例。当我们通过 kubectl create 创建上述 PVC 对象之后,Kubernetes 就会调用 Google Cloud 的 API,创建出一块 SSD 格式的 Persistent Disk。然后,再使用这个 Persistent Disk 的信息,自动创建出一个对应的 PV 对象。
实践一下这个过程(如果使用 Rook 的话下面的流程也是一样的,只不过 Rook 创建出的是 Ceph 类型的 PV):
$ kubectl create -f pvc.yaml
可以看到,我们创建的 PVC 会绑定一个 Kubernetes 自动创建的 PV,如下所示
$ kubectl describe pvc claim1
Name: claim1
Namespace: default
StorageClass: block-service
Status: Bound
Volume: pvc-e5578707-c626-11e6-baf6-08002729a32b
Labels: <none>
Capacity: 30Gi
Access Modes: RWO
No Events.
通过查看这个自动创建的 PV 的属性,你就可以看到它跟我们在 PVC 里声明的存储的属性是一致的,如下所示:
$ kubectl describe pv pvc-e5578707-c626-11e6-baf6-08002729a32b
Name: pvc-e5578707-c626-11e6-baf6-08002729a32b
Labels: <none>
StorageClass: block-service
Status: Bound
Claim: default/claim1
Reclaim Policy: Delete
Access Modes: RWO
Capacity: 30Gi
...
No events.
这个自动创建出来的 PV 的 StorageClass 字段的值,也是 block-service。这是因为,Kubernetes 只会将 StorageClass 相同的 PVC 和 PV 绑定起来。
- PVC 描述的,是 Pod 想要使用的持久化存储的属性,比如存储的大小、读写权限等。
- PV 描述的,则是一个具体的 Volume 的属性,比如 Volume 的类型、挂载目录、远程存储服务器地址等。
- 而 StorageClass 的作用,则是充当 PV 的模板。并且,只有同属于一个 StorageClass 的 PV 和 PVC,才可以绑定在一起。
StorageClass 的另一个重要作用,是指定 PV 的 Provisioner(存储插件)。这时候,如果你的存储插件支持 Dynamic Provisioning 的话,Kubernetes 就可以自动为你创建 PV 了。