k8s(kubernetes)的PV / PVC / StorageClass(理论+实践)

NFS总是不支持PVC扩容

先来个一句话总结 :PV、PVC是K8S用来做存储管理的资源对象,它们让存储资源的使用变得可控 ,从而保障系统的稳定性、可靠性。StorageClass则是为了减少人工的工作量而去自动化创建 PV的组件。所有Pod使用存储只有一个原则:先规划后申请再使用

一、理论

1、PV概念

PV是对K8S存储资源的抽象,PV一般由运维人员创建和配置,供容器申请使用。

没有PV之前,服务器的磁盘没有分区的概念,有了PV之后,相当于通过PV对服务器的磁盘进行分区。

2、PVC概念

PVC 是Pod对存储资源的一个申请,主要包括存储空间申请、访问模式等。创建PV后,Pod就可以通过PVC向PV申请磁盘空间了。类似于某个应用程序向操作系统的D盘申请1G的使用空间。

PVC 创建成功之后,Pod 就可以以存储卷(Volume)的方式使用 PVC 的存储资源了。Pod 在使用 PVC 时必须与PVC在同一个Namespace下。

3、PV / PVC的关系

PV相当于对磁盘的分区,PVC相当于APP(应用程序)向某个分区申请多少空间。比如说安装WPS程序时,一般会告知我们安装它需要多少存储空间,让你选择在某个磁盘下安装。如果将来某个分区磁盘满了,也不会影响别的分区磁盘的使用。

一旦 PV 与PVC绑定,Pod就可以使用这个 PVC 了。如果在系统中没有满足 PVC 要求的 PV,PVC则一直处于 Pending 状态,直到系统里产生了一个合适的 PV。

4、StorageClass概念

K8S有两种存储资源的供应模式:静态模式和动态模式,资源供应的最终目的就是将适合的PV与PVC绑定:

  • 静态模式:管理员预先创建许多各种各样的PV,等待PVC申请使用。

  • 动态模式:管理员无须预先创建PV,而是通过StorageClass自动完成PV的创建以及与PVC的绑定。

StorageClass就是动态模式,根据PVC的需求动态创建合适的PV资源,从而实现存储卷的按需创建。

一般某个商业性的应用程序,会用到大量的Pod,如果每个Pod都需要使用存储资源,那么就需要人工时不时的去创建PV,这也是个麻烦事儿。解决方法就是使用动态模式:当Pod通过PVC申请存储资源时,直接通过StorageClass去动态的创建对应大小的PV,然后与PVC绑定,所以基本上PV → PVC是一对一的关系。

5、Provisioner概念

在创建 PVC 时需要指定 StorageClass,PVC 选择到对应的StorageClass后,与其关联的 Provisioner 组件来动态创建 PV 资源。

那Provisioner是个啥呢?其实就一个存储驱动,类似操作系统里的磁盘驱动。

StorageClass 资源对象的定义主要包括:名称、Provisioner、存储的相关参数配置、回收策略。StorageClass一旦被创建,则无法修改,只能删除重新创建。

PV和PVC的生命周期,包括4个阶段:资源供应(Provisioning)、资源绑定(Binding)、资源使用(Using)、资源回收(Reclaiming)。首先旧的有资源供应,说白了就是得有存储驱动,然后才能创建、绑定和使用、回收。

6、使用PV / PVC前后对比

6.1、通过描述对比

在没有使用PV、PVC之前,各个Pod都可以任意的向存储资源里(比如NFS)写数据,随便一个Pod都可以往磁盘上插一杠子,长期下去磁盘的管理会越来越混乱,然后导致数据使用超限,磁盘爆掉,最后导致磁盘上的所有应用全部挂掉。

为了解决这个问题,引入了PV、PVC的概念,达到限制Pod写入存储数据大小的目的,从而更好地保障了系统的可用性、稳定性。

有了PVC、PV之后,所有Pod使用存储资源,保持一个原则:先规划 → 后申请 → 再使用。

那你肯定有一个疑问,"StorageClass是自动化创建PV,跟原本的无序不可控是一样的效果啊,都可以随便占用存储资源啊"。

其实不然,使用StorageClass只是自动化了创建PV的流程,但依旧执行的是一个存储可控的流程。每个Pod使用多少存储空间是固定的,Pod没有办法超额使用存储空间,更不会影响到别的应用,要出故障也只是某个Pod自己出故障。

6.2、通过图片对比

没有使用PV、PVC之前的情况,如下面2张图:

有了PV、PVC之后的情况,如下图

二、实践

在实践PV、PVC、StorageClass之前,需要读者朋友自行安装NFS服务器。文中演示的内容是通过yaml编排自动到NFS服务器起上创建PV。

1、Pod使用PV、PVC挂载存储卷

1.1、编排PV、PVC、Pod挂载PVC

文中演示的是:Pod的某个目录挂载到NFS的某个目录下。使用了nginx镜像,将html文件写在PV所在的NFS服务器上,最终可以看到利用PV / PVC 成功挂载上去了。

yaml文件如下:

复制代码
# PV编排
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv1
  namespace: dev1
  labels:
    pv: nfs-pv1
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  # Recycle 删除PVC会同步删除PV | Retain 删除PVC不会同步删除PV
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    path: /data/nfstest/share/pv1
    server: 10.20.1.20
    readOnly: false
---
# PVC 编排,通过selector查找PV,K8S里的资源查找都是通过selector查找label标签
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc1
  namespace: dev1
  labels:
    pv: nfs-pvc1
spec:
  resources:
    requests:
      storage: 100Mi
  accessModes:
    - ReadWriteOnce
  selector:
    matchLabels:
      pv: nfs-pv1
---
# Pod挂载PVC,这里为了测试,直接通过node节点的hostPort暴露服务
apiVersion: v1
kind: Pod
metadata:
  name: webapp
  namespace: dev1
  labels:
    app: webapp
spec:
  containers:
    - name: webapp
      image: nginx
      imagePullPolicy: IfNotPresent
      ports:
        - containerPort: 80
          hostPort: 8081
      volumeMounts:
        - name: workdir
          mountPath: /usr/share/nginx/html
  volumes:
    - name: workdir
      persistentVolumeClaim:
        claimName: nfs-pvc1

执行kubectl命令,查看实践效果如下:

2、Pod使用StorageClass自动挂载存储卷

2.1、安装 Provisioner

文中选择通过helm的方式安装nfs-subdir-external-provisioner,这种方式相对简单。安装文档、安装过程见下文:

  • 安装文档

https://kubernetes.io/zh-cn/docs/concepts/storage/storage-classes/#nfs

https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner

  • 安装过程

通过以下3个步骤完成nfs-subdir-external-provisioner的安装。

  1. 安装helm,本文以mac为例

    brew install heml

2.安装nfs-subdir-external-provisioner,执行以下2个命令:

复制代码
$ helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
$ helm install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner -n kube-system \
    --set image.repository=dyrnq/nfs-subdir-external-provisioner \
    --set nfs.server=10.20.1.20 \
    --set nfs.path=/data/nfstest/nfs-storage

这里注意几个参数:

image.repository:修改了镜像的地址,默认用的国外镜像很有可能拉不下来

nfs.server:你的NFS服务器地址

nfs.path:存储目录

  1. 查看helm安装的结果:

执行命令:helm list -A,查看helm安装结果:

查看是否创建了对应的pod,如果没有修改镜像地址会一直拉取失败,如下图:

修改镜像地址后成功启动Pod,如下图:

2.2、使用StorageClass

文中演示的是:Pod利用StorageClass自动创建PV,同时在对应的存储目录上创建了文件,写入了数据。

yaml文件如下:

复制代码
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-storage-1
provisioner: cluster.local/nfs-subdir-external-provisioner
parameters:
  # 设置为"false"时删除PVC不会保留数据,"true"则保留数据
  archiveOnDelete: "false"
mountOptions:
  # 指定NFS版本,这个需要根据NFS Server版本号设置
  - nfsvers=4
---
# 创建PVC
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: nfs-storage-pvc-1
  namespace: dev1
spec:
  storageClassName: nfs-storage-1    #需要与上面创建的storageclass的名称一致
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Mi
---
kind: Pod
apiVersion: v1
metadata:
  name: nfs-storage-pod-1
  namespace: dev1
spec:
  containers:
    - name: nfs-storage-pod-1
      image: busybox
      command:
        - "/bin/sh"
      args:
        - "-c"
        - "touch /mnt/teststorage && echo 111 > /mnt/teststorage && exit 0 || exit 1"  ## 创建一个名称为"SUCCESS"的文件
      volumeMounts:
        - name: nfs-pvc
          mountPath: "/mnt"
  restartPolicy: "Never"
  volumes:
    - name: nfs-pvc
      persistentVolumeClaim:
        claimName: nfs-storage-pvc-1

执行kubectl命令后,可以看到如下效果:

可以看到如我们预料的那样,通过storageClass自动创建了PV,同时在NFS对应的存储目录上创建了文件,写入了数据。

至此,我们实践过程全部结束。

三、总结

本文主要讲解了PV、PVC、StorageClass的理论和实战。

一句话总结 :PV、PVC是K8S用来做存储管理的资源对象,它们让存储资源的使用变得可控 ,从而保障系统的稳定性、可靠性。StorageClass则是为了减少人工的工作量而去自动化创建 PV的组件。所有Pod使用存储只有一个原则:先规划后申请再使用

四、Volume之Persistent Volume持久卷

基本概念

「1. PV 持久卷」 :PersistentVolume,其是K8s中对实际物理存储系统的抽象。作为K8s中集群层面的资源,与节点资源类似。PV不属于任何命名空间**「2. PVC 持久卷申领」**:PersistentVolumeClaim,其是K8s中用户对存储请求的抽象。类似地,Pod会消耗节点资源,而PVC则会消耗PV资源

具体地,通过PV实现了对底层真实存储系统的抽象,使得开发者只需通过创建PVC资源即可。而开发者可以直接将PVC的名称作为卷在工作负载资源中进行引用。当集群中存在满足PVC要求的PV时,则会将该PV与PVC进行双向绑定

静态制备

此场景下K8s集群的管理员需要先创建相应的PV资源,然后才能供开发者使用该资源。下面即是通过hostPath卷创建PV资源的示例。此时不难理解,PV实际上是对物理存储资源的抽象

复制代码
# 1. 创建持久卷PV

apiVersion: v1
kind: PersistentVolume
metadata:
  # PV卷名称
  name: mongodb-pv
spec:
  # 容量
  capacity: 
    # 存储大小: 100MB
    storage: 100Mi
  # 该卷支持的访问模式
  accessModes:
    - ReadWriteOnce # RWO, 该卷可以被一个节点以读写方式挂载
    - ReadOnlyMany  # ROX, 该卷可以被多个节点以只读方式挂载
  # 回收策略: 保留
  persistentVolumeReclaimPolicy: Retain
  # 该持久卷的实际存储类型: 此处使用HostPath类型卷
  hostPath:
    path: /tmp/MongodbData

这里对卷的访问模式进行说明,其支持下述选项。值得一提的是,卷虽然可以能够支持多种访问模式,但其在同一时刻只能以一种访问模式进行挂载。例如,某卷可以被节点A以RWO模式挂载,也可以被B节点以ROX模式挂载。但不能同时使用两种模式挂载

**1. ReadWriteOnce (RWO)**:卷可以被一个节点以读写方式挂载。ReadWriteOnce访问模式允许运行在同一节点上的多个Pod访问卷 **2. ReadOnlyMany (ROX)**:卷可以被多个节点以只读方式挂载 **3. ReadWriteMany (RWX)**:卷可以被多个节点以读写方式挂载 **4. ReadWriteOncePod (RWOP)**:在Kubernetes 1.22以上版本时,卷可以被单个Pod以读写方式挂载

这里对卷的回收策略进行补充说明

「1. Retain 保留」

当PVC资源被删除时,其所绑定的PV不会被删除,但该PV的状态会变为**「Released已释放」**。由于此时PV所关联的物理存储系统上仍然存在历史数据,故该PV此时不能直接被申领。需要集群管理员手动进行处置。一方面对于PV资源而言,其需要删除并重新创建;另一方面,对于PV所关联的物理存储系统上的数据,需要根据实际情况判断是直接删除还是保留以便能够复用数据

「2. Delete 删除」

其不需要管理员人工进行介入操作,系统不仅会将PV资源删除,同时还会将该PV资源所关联的物理存储系统上的数据一并删除掉

K8s集群的管理员创建PV资源,效果如下所示

当K8s集群中的PV资源准备好后,开发者就可以通过创建PVC资源来获取PV了。这里我们将storageClassName字段设置为空字符串,意为不使用存储类进行动态制备,而是使用集群管理员提前创建好的PV。即所谓的静态制备;然后在部署应用的配置文件中,直接引用PVC的名称进行卷的定义即可。需要注意 PVC所属的命名空间 与 使用该PVC的Pod 需在同一个命名空间下

复制代码
# 1. 创建持久卷申领PVC

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mongodb-pvc
spec:
  resources:
    # 申请30MB的存储空间
    requests:
      storage: 30Mi
  # 访问模式
  accessModes:
  - ReadWriteOnce
  # 禁止使用动态制备的卷, 即使用静态制备
  storageClassName: ""

---

# 2. 创建数据库应用

apiVersion: apps/v1
# 资源类型
kind: ReplicaSet
metadata:
  # RS名称
  name: my-mongodb-app
spec:
  # 副本数量
  replicas: 1
  # 标签选择器
  selector:
    matchLabels:
      app: db
  # Pod 模板
  template:
    metadata:
      # 标签信息
      labels:
        app: db
    spec:
      # 容器信息
      containers:
      - name: my-mongodb-app
        image: mongo
        volumeMounts:
        # 将名为mongodb-data的卷挂载到容器内的指定路径
        - name: mongodb-data
          # MongoDB默认的数据存储路径
          mountPath: /data/db
      # 卷信息
      volumes:       
      - name: mongodb-data
        # 引用PVC的名称即可
        persistentVolumeClaim:
          claimName: mongodb-pvc

当PVC创建完成后,一旦集群中存在满足PVC指定的容量要求、访问模式的PV资源后。该PVC即会与相应的PV进行双向绑定。如下所示,同时会发现应用所在Pod也部署成功

动态制备

对于静态制备而言,无论采用何种回收策略。事实上都需要系统管理员再次创建PV资源。为此动态制备应运而生,K8s集群在收到用户创建的PVC后,会通过PVC中所指定的StorageClass存储类自动地制备出相应的PV资源以供使用。在动态制备场景下,系统管理员只需提前创建相应的若干个StorageClass存储类资源即可。具体地,在StorageClass存储类中会关联真实的物理存储系统和相应的provisioner制备器。其中动态制备的PV会继承相应的StorageClass中定义的回收策略。由于这里我们是通过Kind搭建K8s集群环境的,其已经内置了一个StorageClass存储类。如下所示

此时,开发者只需在创建PVC的过程通过storageClassName字段指定相应的存储类名称即可。类似地在部署应用时直接引用PVC名称定义卷

复制代码
# 1. 创建持久卷申领PVC

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mongodb-pvc-2
spec:
  resources:
    # 申请20MB的存储空间
    requests:
      storage: 30Mi
  # 访问模式
  accessModes:
  - ReadWriteOnce
  # 使用名为standard的存储类进行动态制备
  storageClassName: "standard"

---

# 2. 创建数据库应用

apiVersion: apps/v1
# 资源类型
kind: ReplicaSet
metadata:
  # RS名称
  name: my-mongodb-app-2
spec:
  # 副本数量
  replicas: 1
  # 标签选择器
  selector:
    matchLabels:
      app: db2
  # Pod 模板
  template:
    metadata:
      # 标签信息
      labels:
        app: db2
    spec:
      # 容器信息
      containers:
      - name: my-mongodb-app-2
        image: mongo
        volumeMounts:
        # 将名为mongodb-data-2的卷挂载到容器内的指定路径
        - name: mongodb-data-2
          # MongoDB默认的数据存储路径
          mountPath: /data/db
      # 卷信息
      volumes:       
      - name: mongodb-data-2
        # 引用PVC的名称即可
        persistentVolumeClaim:
          claimName: mongodb-pvc-2

效果如下所示,动态制备的PV成功与PVC进行了绑定,应用也处于运行状态

相关推荐
平行云1 天前
虚拟直播混合式2D/3D应用程序实时云渲染推流解决方案
linux·unity·云原生·ue5·图形渲染·实时云渲染·像素流送
longerxin20201 天前
kubeasz 快速指南:一键部署 Kubernetes-k8s 测试环境
云原生·容器·kubernetes
cyber_两只龙宝1 天前
【Oracle】 Oracle之SQL的子查询
linux·运维·数据库·sql·云原生·oracle
米高梅狮子1 天前
03.Kubernetes自动化部署和namespace、pod
容器·kubernetes·自动化
特长腿特长1 天前
LVS_DR 模式的原理
linux·运维·网络·云原生·centos·lvs
Sirius Wu1 天前
Docker 镜像的构建、打包、变更、再次打包全流程
运维·docker·容器
Zhu7581 天前
【软件部署】docker环境部署domino
运维·docker·容器
努力的搬砖人.1 天前
配置 Docker 镜像加速器
运维·docker·容器
江湖有缘1 天前
实时监控所有端口,Docker 部署 WatchYourPorts 保姆级教程
运维·docker·容器
pupudawang1 天前
docker desktop安装redis
redis·docker·容器