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 容器内的目录上。

  1. PV 和 PVC 的绑定
    • 管理员创建了一个 PV(PersistentVolume),定义了 NFS 存储的详细信息。
    • 用户创建了一个 PVC(PersistentVolumeClaim),请求 1Gi 的存储,并指定存储类为 manual
    • Kubernetes 根据 PVC 的请求,将 PVC 绑定到符合条件的 PV(这里是 nfs PV)。
  2. 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 了。

posted @   guixiang  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~
点击右上角即可分享
微信分享提示