K8S临时存储-本地存储-PV和PVC的使用-动态存储(StorageClass)

发布于:2024-04-19 ⋅ 阅读:(28) ⋅ 点赞:(0)

介绍

容器中的文件在磁盘上是临时存放的,当容器崩溃或停止时容器上面的数据未保存, 因此在容器生命周期内创建或修改的所有文件都将丢失。 在崩溃期间,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