介绍

容器中的文件在磁盘上是临时存放的,当容器崩溃或停止时容器上面的数据未保存, 因此在容器生命周期内创建或修改的所有文件都将丢失。 在崩溃期间,kubelet 会以干净的状态重新启动容器。 当多个容器在一个 Pod 中运行并且需要共享文件时,会出现另一个问题,跨所有容器设置和访问共享文件系统具有一定的挑战性。K8S 卷(Volume) 这一抽象概念能够解决这两个问题。

存储卷的分类

hostPath 存储

警告:
HostPath 卷存在许多安全风险,最佳做法是尽可能避免使用 HostPath。 当必须使用 HostPath 卷时,它的范围应仅限于所需的文件或目录,并以只读方式挂载。HostPath 仅适用于单个节点上的存储,不支持跨节点访问(如果Pod偏移到其他宿主机节点上面可能会出问题)。

支持的 type 值如下:

type值说明
空字符串(默认)用于向后兼容,这意味着在安装 hostPath 卷之前不会执行任何检查
DirectoryOrCreate如果在给定路径上什么都不存在,那么将根据需要创建空目录,权限设置为 0755,具有与 kubelet相同的组和属主信息
Directory在给定路径上必须存在的目录
FileOrCreate如果在给定路径上什么都不存在,那么将在那里根据需要创建空文件,权限设置为 0644,具有与 kubelet 相同的组和所有权
File在给定路径上必须存在的文件
Socket在给定路径上必须存在的 UNIX 套接字
CharDevice在给定路径上必须存在的字符设备
BlockDevice在给定路径上必须存在的块设备

hostPath 配置示例

如有多个节点,在使用 hostPath 存储需要指定pod部署在那个节点上面,例如在Pod上面添加nodeSelector字段指定到某一个节点(需要提前给节点打标签

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
#  nodeSelector:      # 指定部署到特定节点上面
#    disktype: ssd     # 标签
  - image: nginx
    name: test-container
    volumeMounts:
    - mountPath: /test-pd
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      path: /data          # 宿主机上目录位置,注意权限
      type: Directory      # 此字段为可选,详细解释在上面

emptyDir

定义了 emptyDir 卷的 Pod,在 Pod 被指派到某节点时此卷会被创建。emptyDir 卷最初是空的。Pod 中的容器挂载 emptyDir 卷的路径可能相同也可能不同,但这些容器都可以读写 emptyDir 卷中相同的文件。 当 Pod 因为某些原因被从节点上删除时,emptyDir 卷中的数据也会被永久删除。主要作用是为了共享数据用。

emptyDir 卷存储可以使用任何磁盘、SSD 或网络存储,这取决于你的环境。 你可以将 emptyDir.medium 字段设置为 “Memory”, 以告诉 Kubernetes 为你挂载 tmpfs(基于 RAM 的文件系统)。虽然 tmpfs 速度非常快,但是要注意它与磁盘不同, 并且你所写入的所有文件都会计入容器的内存消耗,受容器内存限制约束

说明:
容器崩溃并不会导致 Pod 被从节点上移除,因此容器崩溃期间 emptyDir 卷中的数据是安全的。

emptyDir 配置示例

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: nginx
    name: test-container
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir:
      sizeLimit: 500Mi  # 写入磁盘的大小限制,如果没限制直接删除这行 在emptyDir: []即可

nfs

nfs 卷能将 NFS (网络文件系统) 挂载到你的 Pod 中。 不像 emptyDir 那样会在删除 Pod 的同时也会被删除,nfs 卷的内容在删除 Pod 时会被保存,卷只是被卸载。 这意味着 nfs 卷可以被预先填充数据,并且这些数据可以在 Pod 之间共享。

配置示例

说明:
在使用 NFS 卷之前,你必须部署自己的 NFS 服务器才可以使用,这里不再叙述。

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: registry.k8s.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /my-nfs-data
      name: test-volume
  volumes:
  - name: test-volume
    nfs:
      server: my-nfs-server.example.com   # nfs服务的地址
      path: /my-nfs-volume                # nfs 服务共享的路径
      readOnly: true                    

PV和PVC的使用

在 Kubernetes 中,PV(PersistentVolume)和 PVC(PersistentVolumeClaim)是用于持久化存储的核心概念,其本身并没有存储的相关功能所以需要准备后端存储的环境。

  • PersistentVolume(PV):PV 是集群中的一块持久化存储,它是集群管理员预先配置好的存储资源。PV 可以是网络存储(如 NFS、GlusterFS、Ceph)、云存储(如 AWS EBS、Azure Disk)、本地存储(HostPath)等。PV 与存储后端进行绑定,表示集群中的可用存储资源,支持的后端存储类型详情请查看K8S官方文档

  • PersistentVolumeClaim(PVC):PVC 是 Pod 对 PV 的申请。PVC 定义了对存储资源的需求,包括存储容量、访问模式和其他属性。Pod 中的容器可以通过 PVC 来申请并使用 PV 提供的持久化存储。Kubernetes 会根据 PVC 的需求匹配可用的 PV,并将其动态绑定到 Pod 中。

PV(PersistentVolume)

下面是 hostPath PersistentVolume 的配置文件:

apiVersion: v1
kind: PersistentVolume
metadata:
  labels:
    type: local
  name: task-pv-volume
spec:
  accessModes:
  - ReadWriteOnce
  capacity:
    storage: 10Gi
  hostPath:
    path: /mnt
    type: ""
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: manual
  volumeMode: Filesystem

关键词解释

  • spec: capacity: storage: 10Gi:定义了 PV 的存储容量为 10Gi。
  • volumeMode: Filesystem:指定了 PV 的卷模式为文件系统。
  • accessModes: - ReadWriteOnce:指定了 PV 的访问模式为 ReadWriteOnce,表示此 PV 只能被单个节点挂载为读写模式。
  • persistentVolumeReclaimPolicy: Recycle:指定了 PV 回收策略为 Recycle,表示当 PV 被释放时,其存储资源将被重新使用。
  • storageClassName: manual:指定了 PV 的存储类名称为 slow。
  • hostPath.path:指定了 PV 的路径。在这里,path 指定了 PV 使用的主机路径,即 “/mnt/data”。
  • hostPath.type:指定 PV 所使用的主机路径的类型,不指定默认是:DirectoryOrCreate 还有 Directory 和 File 类型可选

pv存储卷的回收策略

  • 回收(Reclaiming) :当用户不再使用其存储卷时,他们可以从 API 中将 PVC 对象删除, 从而允许该资源被回收再利用。
  • 保留(Retain):回收策略 Retain 使得用户可以手动回收资源。
  • 删除(Delete):对于支持 Delete 回收策略的卷插件,删除动作会将 PersistentVolume 对象从 Kubernetes 中移除,同时也会从外部基础设施中移除所关联的存储资产。 动态制备的卷会继承其 StorageClass 中设置的回收策略, 该策略默认为 Delete。
  • 回收(Recycle):如果下层的卷插件支持,回收策略 Recycle 会在卷上执行一些基本的擦除 (rm -rf /thevolume/*)操作,之后允许该卷用于新的 PVC 申领。(注意:回收策略 Recycle 已被废弃。取而代之的建议方案是使用动态制备。

卷模式

Kubernetes 支持两种卷模式(volumeModes):

  • Filesystem(文件系统):文件系统模式,表示 PV 中存储的数据是文件系统数据,可以像普通文件系统一样被挂载到 Pod 中,并且可以在容器中被读取和写入。默认的卷模式是 Filesystem。
  • Block():块模式,表示 PV 中存储的数据是块设备数据,可以将 PV 挂载为块设备卷(例如 /dev/sdX),并且可以被容器用作块设备。

访问模式

  • ReadWriteOnce:卷可以被一个节点以读写方式挂载。 ReadWriteOnce 访问模式仍然可以在同一节点上运行的多个 Pod 访问该卷。
  • ReadOnlyMany:卷可以被多个节点以只读方式挂载。
  • ReadWriteMany:卷可以被多个节点以读写方式挂载。
  • ReadWriteOncePod:(目前K8S版本在1.22及以上的才支持到1.29才GA)卷可以被单个 Pod 以读写方式挂载。 如果你想确保整个集群中只有一个 Pod 可以读取或写入该 PVC, 请使用 ReadWriteOncePod 访问模式。

创建PV

kubectl create -f pv0003.yaml

创建好以后查看

kubectl get pv
NAME             CAPACITY   ACCESSMODES   RECLAIMPOLICY   STATUS      CLAIM     STORAGECLASS   REASON    AGE
pv0003           5Gi        RWO           Recycle         Available              manual                  4s

PV状态说明:

  • NAME:PVC名字
  • CAPACITY:PV大小
  • ACCESSMODES:访问模式
  • RECLAIMPOLICY:回收策略
  • STATUS:PV使用状态
  • STORAGECLASS: 动态存储名字
  • AGE:创建时间

在命令行接口(CLI)中,访问模式也使用以下缩写形式:

  • RWO - ReadWriteOnce
  • ROX - ReadOnlyMany
  • RWX - ReadWriteMany
  • RWOP - ReadWriteOncePod

PV每个阶段的状态

每个持久卷会处于以下阶段(Phase)之一:

  • Available:卷是一个空闲资源,尚未绑定到任何申领
  • Bound:该卷已经绑定到某申领
  • Released:所绑定的申领已被删除,但是关联存储资源尚未被集群回收
  • Failed:卷的自动回收操作失败

使用 kubectl describe persistentvolume 查看已绑定到 PV 的 PVC 的名称。

PVC(PersistentVolumeClaims)

创建一个PVC:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 5Gi

这里面的字段意思基本和PV里面的一样,访问模式和卷模式也是一样的,就不再解释了。

PV和PVC的绑定模式

匹配步骤:

  • 检查 PV 的状态:Kubernetes 控制器会查找状态为 Available(可用)的 PV,这些 PV 尚未被其他 PVC 绑定。
  • PV 属性的匹配:控制器会检查 PVC 的需求与 PV 的属性是否匹配。这包括容量、访问模式、持久性和标签等。
  • 针对 PVC 的需求选择 PV:控制器会选择满足 PVC 所有需求的 PV,并尝试将其与 PVC 绑定。

自动绑定

创建PV和PVC

apiVersion: v1
kind: PersistentVolume
metadata:
  name: task1
  labels:
    type: local1
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: task-pv-claim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 9Gi

查看PV和PVC状态

[root@master01 pv]# kubectl get pvc,pv
NAME                                  STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/task-pv-claim   Bound    task1    10Gi       RWO                           26s

NAME                     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                   STORAGECLASS   REASON   AGE
persistentvolume/task1   10Gi       RWO            Retain           Bound       default/task-pv-claim                           10m

都是已经绑状态

根据标签绑定

创建PV和PVC

apiVersion: v1
kind: PersistentVolume
metadata:
  name: my-pv
  labels:
    desk: "test1"       # 设置标签
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /mnt
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  selector:
    matchLabels:
      desk: "test1"     # 匹配PV的标签

查看PV和PVC状态
可以看到 my-pvc 已经绑定到 my-pv 上面了,如果没有标签自动绑定的话 则会绑定到 task2 的PV上面

[root@master01 pv]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM            STORAGECLASS   REASON   AGE
my-pv   10Gi       RWO            Retain           Bound       default/my-pvc                           3s
task1   10Gi       RWO            Retain           Available                                            46s
task2   5Gi        RWO            Retain           Available                                            46s
[root@master01 pv]# kubectl get pvc
NAME     STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
my-pvc   Bound    my-pv    10Gi       RWO                           12s

指定PV名字绑定

apiVersion: v1
kind: PersistentVolume
metadata:
  name: my-pv
spec:
  capacity:
    storage: 20Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /mnt/data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes:
    - ReadWriteOnce
  volumeName: my-pv   # 指定要绑定的 PV 的名称
  resources:
    requests:
      storage: 5Gi

查看PV和PVC绑定情况

[root@master01 pv]# kubectl get pv,pvc
NAME                     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM            STORAGECLASS   REASON   AGE
persistentvolume/my-pv   20Gi       RWO            Retain           Bound       default/my-pvc                           5s
persistentvolume/task1   10Gi       RWO            Retain           Available                                            158m
persistentvolume/task2   5Gi        RWO            Retain           Available                                            158m

NAME                           STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/my-pvc   Pending   my-pv    0                                        5s

注意事项

PV 和 PVC 之间的绑定是一对一的关系。一个 PVC 只能绑定到一个 PV 上,而一个 PV 可以同时被多个 PVC 绑定。如果没有足够的 PV 来满足 PVC 的需求,或者没有满足 PVC 的要求的可用 PV,则 PVC 将处于 Pending(挂起)状态,直到满足条件为止。

Pod使用PVC存储

创建PV和PVC:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: task-pv-volume
  labels:
    type: local
spec:
  capacity:
    storage: 10Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt/data"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: task-pv-claim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi

创建一个Pod使用上面这个PVC

apiVersion: v1
kind: Pod
metadata:
  name: task-pv-pod
spec:
  containers:
    - name: task-pv-container
      image: nginx
      ports:
        - containerPort: 80
          name: "http-server"
      volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: task-pv-storage
  volumes:
    - name: task-pv-storage
      persistentVolumeClaim:          # 指定使用PVC存储
        claimName: task-pv-claim      # PVC存储的名字

在Pod里面多次挂在PVC

apiVersion: v1
kind: Pod
metadata:
  name: test
spec:
  containers:
    - name: test
      image: nginx
      volumeMounts:
        # 网站数据挂载
        - name: config
          mountPath: /usr/share/nginx/html
          subPath: html
        # Nginx 配置挂载
        - name: config
          mountPath: /etc/nginx/nginx.conf
          subPath: nginx.conf
  volumes:
    - name: config
      persistentVolumeClaim:
        claimName: test-nfs-claim

PVC绑定常见问题

创建PVC后,一直绑定不上PV(Pending):

  • PVC的空间申请大小大于PV的大小
  • PVC的StorageClassName没有和PV的一致
  • PVC的accessModes和PV的不一致

Pod挂在PVC后,一直处于Pending状态:

  • PVC没有被创建成功,或者被创建
  • PVC和Pod不在同一个Namespace(PV是没有命名空间的,PVC是有有命名空间概念的)

删除PVC后k8s会创建一个用于回收的Pod,根据PV的回收策略进行pv的回收,回收完以后PV的状态就会变成可被绑定的状态也就是空闲状态,其他的Pending状态的PVC如果匹配到了这个PV,他就能和这个PV进行绑定。

动态存储(StorageClass)

主要功能:

  • 动态配置持久存储:StorageClass 允许管理员定义多种类型的存储,包括云存储、本地存储、网络存储等,并且可以根据用户的请求动态创建 PersistentVolume(PV)。
  • 自动绑定:一旦 PVC 请求了特定的存储类,Kubernetes 将根据 StorageClass 的定义自动创建 PV,并将其绑定到 PVC 上,简化了存储资源的管理过程。
  • 多种属性支持:StorageClass 允许管理员定义存储的各种属性,如容量、访问模式、持久性、副本数等,以满足不同应用程序的需求。
  • 灵活性和可扩展性:管理员可以根据实际需求定义多个不同的 StorageClass,并根据应用程序的需求选择合适的存储类。

环境准备

在开始之前请安装后端存储,我这里使用的是CEPH存储并且安装到K8S集群里,下面是安装方法。(所有环境中如果有用到的安装包或者容器镜像什么的都可以私信我)
安装方法:点击跳转

查看已经部署好的StorageClass

注意: 本次演示默认已经安装好ceph存储。

## sc 是 StorageClass 简写

[root@master01 ~]# kubectl get sc
NAME            PROVISIONER                     RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
rook-ceph-rbd   rook-ceph.rbd.csi.ceph.com      Retain          Immediate           true                   228d
rook-cephfs     rook-ceph.cephfs.csi.ceph.com   Retain          Immediate           true                   228d
  • rook-ceph-rbd :这个是块存储。区别:只能由一个节点进行读写操作
  • rook-cephfs:这个是共享文件存储。区别:可以在多个节点上同时挂载和访问。

适用场景:

  • RBD:适用于需要高性能、低延迟和可扩展性的场景,如数据库、块存储卷等。
  • CephFS:适用于需要共享数据、访问文件系统的场景,如共享存储、容器卷等。

查看 rook-ceph-rbd

查看命令

kubectl get sc rook-ceph-rbd -oyaml
allowVolumeExpansion: true
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: rook-ceph-rbd
parameters:
  clusterID: rook-ceph
  csi.storage.k8s.io/controller-expand-secret-name: rook-csi-rbd-provisioner
  csi.storage.k8s.io/controller-expand-secret-namespace: rook-ceph
  csi.storage.k8s.io/fstype: ext4
  csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node
  csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
  csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner
  csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
  imageFeatures: layering
  imageFormat: "2"
  pool: replicapool
provisioner: rook-ceph.rbd.csi.ceph.com
reclaimPolicy: Retain
volumeBindingMode: Immediate

关键字段解释

  • kind:表示这是 Kubernetes 存储 API 的版本,而 kind 设置为 StorageClass,表示这是一个 StorageClass 对象。
  • allowVolumeExpansion: true :这个字段用于指示是否允许对使用这个 StorageClass 创建的持久卷进行扩展。
  • metadata:这个部分包含有关 StorageClass 的元数据,其中最重要的是 name 字段,它指定了 StorageClass 的名称,这里是 “rook-ceph-rbd”。
  • parameters:这个字段定义了传递给 StorageClass 的参数。在这个例子中,这些参数包括:
    • clusterID:Rook Ceph 集群的 ID。
    • csi.storage.k8s.io/controller-expand-secret-name : 的名称和命名空间。
    • csi.storage.k8s.io/controller-expand-secret-namespace:指定扩展控制器所需的密钥.
    • csi.storage.k8s.io/fstype:指定了文件系统类型。
    • csi.storage.k8s.io/node-stage-secret-name 和 csi.storage.k8s.io/node-stage-secret-namespace:这些参数用于指定节点阶段操作所需的密钥的名称和命名空间。
      csi.storage.k8s.io/provisioner-secret-name 和 csi.storage.k8s.io/provisioner-secret-namespace:这些参数用于指定提供程序所需的密钥的名称和命名空间。
  • imageFeatures:这个参数指定了卷的特性,即它包含了支持镜像层叠(layering)的功能。镜像层叠允许在卷上创建多个镜像,每个镜像可以包含自己的修改,而不会影响其他镜像。
  • imageFormat:这个参数指定了卷的格式,即它使用的是格式版本 2。格式版本 2 是 RBD 卷的一种格式,它支持更高级的功能和性能,例如支持更大的卷和更好的快照管理。
  • pool:指定了用于存储的池名称。
  • provisioner:这个字段指定了用于创建持久卷的存储提供程序。在这里,使用的提供程序是 rook-ceph.rbd.csi.ceph.com,表明这个 StorageClass 使用了 Rook Ceph 的 RBD(Rados Block Device)CSI(Container Storage Interface)提供程序。
  • reclaimPolicy:这个字段指定了持久卷的回收策略。在这里,设置为 Retain,表示当与此 StorageClass 关联的持久卷不再需要时,它们的资源应该保留而不被自动删除。
  • volumeBindingMode:这个字段指定了持久卷的绑定模式。在这里,设置为 Immediate,表示持久卷应该立即绑定到声明,即创建声明时应立即创建卷。

StorageClass 使用方法

创建 StatefulSet 使用

部署成功以后 StorageClass 会根据PVC申请的去自动创建PV并绑定好

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: my-statefulset
spec:
  serviceName: "my-statefulset"
  replicas: 3
  selector:
    matchLabels:
      app: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: my-container
        image: nginx:latest
        ports:
        - containerPort: 80
        volumeMounts:
        - name: my-persistent-storage
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: my-persistent-storage
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "rook-ceph-rbd" # 指定刚刚查出来的 StorageClass 名字 
      resources:
        requests:
          storage: 5Gi

使用PVC申请动态存储

创建PVC成功以后会自动创建PV并绑定好,后面Pod使用方式和上面手动创建PV和PVC的方式一样。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: rook-ceph-rbd    # 指定要使用的 StorageClass 的名称
  resources:
    requests:
      storage: 1Gi                   # 请求 1GB 的存储容量

PVC扩容

直接修改 “storage: 1Gi ” 修改为2G即可,但是生效还需要等几分钟。

kubectl edit pvc my-pvc
Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐