volume的概念

对于大多数项目来说,数据文件的持久存储都是非常常见的需求,比如存储用户上传的头像、文件以及数据库数据等。然而在Kubernetes中,由于应用具有高度的动态扩缩容和编排能力,不再固定部署在某些节点上,因此如果直接将数据保存到容器的本地磁盘,既不安全,也难以满足数据持久化的需求。

对于了解云原生应用的读者来说,会知道“无状态应用”的概念——即将应用的数据和状态剥离出来,存储在外部的“云端”,比如常用的NFS(但不建议在生产中使用,因存在单点故障,可考虑分布式存储)、Ceph、GlusterFS、MinIO等。

在传统架构中,要使用这些存储就需要提前在宿主机上进行挂载,这在运维中常导致新节点部署时忘记挂载存储的问题。鉴于此,Kubernetes在设计之初就考虑了数据存储的需求,并通过Volume资源提供了抽象与解耦的能力。

容器里的数据具有短暂性,容器崩溃后重启时往往导致数据丢失,并回到最干净的状态。而当Pod中运行多个容器时,它们之间也经常需要共享文件。这些需求都可以通过Volume来解决。

Docker也有Volume的概念,但其生命周期不受管理,仅是宿主机目录的映射。而Kubernetes的Volume具有与Pod同等的生命周期,可以在容器重启时保留数据。同时,Kubernetes还支持多种类型的Volume,以满足不同的数据持久化需求。

从本质上看,Volume的作用与磁盘挂载类似,是容器内的一个目录。Pod可以通过.spec.volumes声明,容器通过.spec.containers.volumeMounts来挂载使用。其操作方式与磁盘挂载几乎无异。

您提供了Kubernetes Volume支持的各种类型,我已经整理转换为Markdown格式:

Volume类型

在传统架构中,企业内可能有自己的存储平台,比如NFS、Ceph、GlusterFS、Minio等。如果读者的环境在公有云,可以使用公有云提供的NAS、对象存储等。在Kubernetes中,Volume也支持配置以上存储,用于挂载到Pod中实现数据的持久化。

Kubernetes Volume支持的卷类型有很多,以下为常用的几种:

  • CephFS
  • GlusterFS
  • iSCSI
  • Cinder
  • NFS
  • RBD
  • HostPath

当然,也支持一些Kubernetes独有的类型:

  • ConfigMap:用于存储配置文件
  • Secret:用于存储敏感数据
  • EmptyDir:用于一个Pod内多个容器的数据共享
  • PersistentVolumeClaim:对PersistentVolume的申请

以上列举的是一些比较常用的类型,其他支持的类型可以查看Volume的官方文档:

https://kubernetes.io/docs/concepts/storage/volumes/

volume实践

通过 emptyDir 共享数据

EmptyDir 是一个特殊的 Volume 类型,与上述 Volume 不同的是,如果删除 Pod,emptyDir 卷中的数据也将被删除,所以一般 emptyDir 用于 Pod 中的不同 Container 共享数据,比如一个 Pod 存在两个容器 A 和 B,容器 A 需要使用容器 B 产生的数据,此时可以采用 emptyDir 共享数据,类似的使用如 Filebeat 收集容器内程序产生的日志。

默认情况下,emptyDir 卷支持节点上的任何介质,可能是 SSD、磁盘或网络存储,具体取 决于自身的环境。可以将 emptyDir.medium 字段设置为 Memory,让 Kubernetes 使用 tmpfs(内 存支持的文件系统),虽然 tmpfs 非常快,但是 tmpfs 在节点重启时,数据同样会被清除,并且 设置的大小会被计入到 Container 的内存限制当中。

使用 emptyDir 卷的示例如下,直接指定 emptyDir 为 {} 即可:

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: nginx
  name: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx1
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - mountPath: /opt
          name: share-volume
      - image: nginx
        name: nginx2
        imagePullPolicy: IfNotPresent
        command: ["sh","-c","sleep 1h"]
        volumeMounts:
        - mountPath: /opt
          name: share-volume
      volumes:
      - name: share-volume
        emptyDir: {}
          #medium: Memory

这样容器 ngixn1 和容器 nginx2就可以通过挂载到 /opt 目录来共享数据。

# kubectl get pod 
NAME                     READY   STATUS    RESTARTS   AGE
nginx-68499574bb-67grm   2/2     Running   0          68s

# kubectl exec -it nginx-68499574bb-67grm -c nginx1 --  bash
root@nginx-68499574bb-67grm:/# echo "nginx1" > 、/opt/nginx1.txt
# -c 指定pod中的容器名字

#在容器nginx2中查看nginx1创建的文件
kubectl exec -it nginx-68499574bb-67grm -c nginx2 --  bash
root@nginx-68499574bb-67grm:/# cat /opt/nginx1.txt 
nginx1

HostPath挂载

HostPath 卷可用于实现 Pod 和宿主机之间的数据共享,常用的示例有:

  • 将宿主机的时区文件挂载到 Pod
  • 将 Pod 的日志文件挂载到宿主机

以下为使用 hostPath 卷的示例,实现将主机的 /etc/localtime 文件挂载到 Pod 的 /etc/localtime (挂载路径可以不一致):

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: nginx
    name: test-container
    volumeMounts:
    - mountPath: /etc/localtime 
      name: host-time-volume
      readOnly: true
  volumes:
  - name: host-time-volume
    hostPath:
      path: /etc/localtime 
      type: File

在配置 HostPath 时,有一个 type 参数,用于表达不同的挂载类型,HostPath 卷常用的 type(类型)如下:

  • ""(空字符串):默认选项,意味着挂载 hostPath 卷之前不会执行任何检查。
  • DirectoryOrCreate:如果给定的 path 不存在任何东西,那么将根据需要创建一个权限为 0755 的空目录,和 Kubelet 具有相同的组和权限。
  • Directory:目录必须存在于给定的路径下。
  • FileOrCreate:如果给定的路径不存储任何内容,则会根据需要创建一个空文件,权限设置为 0644,和 Kubelet 具有相同的组和所有权。
  • File:文件,必须存在于给定路径中。
  • Socket:UNIX 套接字,必须存在于给定路径中。
  • CharDevice:字符设备,必须存在于给定路径中。
  • BlockDevice:块设备,必须存在于给定路径中。
# kubectl get po 
NAME      READY   STATUS    RESTARTS   AGE
test-pd   1/1     Running   0          6m29s

# 同步时间
# kubectl exec -it test-pd bash
root@test-pd:/# date
Tue Aug  1 08:56:54 CST 2023

挂载 NFS 至容器

在生产环境中,考虑到数据的高可用性,仍然不建议使用 NFS 作为后端存储。因为 NFS 存在单点故障,很多企业并非对其实现高可用,并且 NFS 的性能存在很大的瓶颈。所以生产中,建议使用分布式存储,在公有云中建议使用公有云提供的 NAS 存储来替代 NFS,并且 NAS 性能更好,可用性更高。NFS 作为一个比较流行的远端存储工具,在非生产环境中是一个比较好用的选择,可以快速地搭建使用。

接下来演示如何使用 Volume 挂载 NFS 为容器提供持久化存储。

在开始之前,需要提前配置 NFS 的权限和数据目录,具体可以参考 NFS 的文档,此处只提供 NFS 的 exporter 配置,假设 NFS 提供的数据目录为 /data/nfs,授权可挂载该目录的 IP 段为 192.168.0.0/24:

/data/nfs/ *(rw,sync,no_subtree_check,no_root_squash)

和 emptyDir、HostPath 的配置方法类似,NFS 的 Volume 配置也是在 Volumes 字段中配置的,和 emptyDir 不同的是,NFS 属于持久化存储的一种,在 Pod 删除或者重启后,数据依旧会存储在 NFS 节点上。配置 NFS 也相对简单,代码如下(挂载方式和 hostPath、emptyDir 类似,在此不再演示):

  • Server:NFS 服务器的 IP。
  • Path:NFS 服务器的共享目录。

注意 Kubernetes 所有的节点都需要安装上 nfs-utils 才可以正常挂载 NFS。

本节演示了类型为 emptyDir、hostPath、NFS 的 Volume 的使用,在配置管理 ConfigMap、Secret 时也讲解了类型为 Secret 和 ConfigMap 的使用,后面的章节将为读者讲解 persistentVolumeClaim 以及更高级的存储配置的使用。

对于其他类型的 Volume 配置,原理和配置方法类似,具体可以参考:https://kubernetes.io/docs/concepts/storage/volumes/。

安装nfs

在node安装nfs

yum install -y nfs-utils

# vi /etc/exports
/data/nfs/ *(rw,sync,no_subtree_check,no_root_squash)


# mkdir -p /data/nfs
# exportfs -r
# systemctl start nfs

在master查看

# ip是node节点的ip地址
# showmount -e 192.168.5.62
Export list for 192.168.5.62:
/data/nfs *

挂载nfs到容器

apiVersion: v1
kind: Pod
metadata:
  name: test-pod-nfs
spec:
  containers:
  - image: nginx
    name: test-container-nfs
    volumeMounts:
    - mountPath: /opt
      name: nfs
  volumes:
  - name: nfs
    nfs:
      server: 192.168.5.62
      path: /data/nfs

在nfs中创建文件查看在容器是否存在

# 在node节点操作
# echo "nfs" > /data/nfs/nfs


#进入容器查看
# kubectl exec -it test-pod-nfs bash 
root@test-pod-nfs:/# cat /opt/nfs 
nfs
root@test-pod-nfs:/# 

PersistentVolume

使用场景

虽然 Volume 已经可以接入大部分存储后端,但是实际使用时还有诸多问题,比如:

  • 当某个数据卷不再被挂载使用时,里面的数据如何处理?
  • 如果想要实现只读挂载如何处理?
  • 如果想要只能有一个 Pod 挂载如何处理?

如上所述,对于很多复杂的需求 Volume 可能难以实现,并且无法对存储卷的生命周期进行管理。另一个很大的问题是,在企业内使用 Kubernetes 的不仅仅是 Kubernetes 管理员,可能还有开发人员、测试人员以及初学 Kubernetes 的技术人员,对于 Kubernetes 的 Volume 或者相关存储平台的配置参数并不了解,所以无法自行完成存储的配置。

为此,Kubernetes 引入了两个新的 API 资源:PersistentVolume 和 PersistentVolumeClaim。PersistentVolume(简称 PV)是由 Kubernetes 管理员设置的存储,PersistentVolumeClaim(简称 PVC)是对 PV 的请求,表示需要什么类型的 PV。它们同样是集群中的一类资源,但其生命周期比较独立,管理员可以单独对 PV 进行增删改查,不受 Pod 的影响,生命周期可能比挂载它的其他资源还要长。如果一个 Kubernetes 集群的使用者并非只有 Kubernetes 管理员,那么可以通过提前创建 PV,用以解决对存储概念不是很了解的技术人员对存储的需求。和单独配置 Volume 类似,PV 也可以使用 NFS、GFS、CEPH 等常用的存储后端,并且可以提供更加高级的配置,比如访问模式、空间大小以及回收策略等。目前 PV 的提供方式有两种:静态或动态。静态 PV 由管理员提前创建,动态 PV 无须提前创建(动态 PV 在后面章节进行讲解)。

PV 回收策略

当用户使用完卷时,可以从 API 中删除 PVC 对象,从而允许回收资源。回收策略会告诉 PV 如何处理该卷。目前回收策略可以设置为 Retain、Recycle 和 Delete:

  • Retain:保留,该策略允许手动回收资源,当删除 PVC 时,PV 仍然存在,Volume 被视为已释放,管理员可以手动回收卷。
  • Recycle:回收,如果 Volume 插件支持,Recycle 策略会对卷执行 rm -rf 清理该 PV,并使其可用于下一个新的 PVC,但是本策略将来会被弃用,目前只有 NFS 和 HostPath 支持该策略。
  • Delete:删除,如果 Volume 插件支持,删除 PVC 时会同时删除 PV,动态卷默认为 Delete,目前支持 Delete 的存储后端包括 AWS EBS、GCE PD、Azure Disk、OpenStack Cinder等。

PV 访问策略

在实际使用 PV 时,可能针对不同的应用会有不同的访问策略,比如某类 Pod 可以读写,某类 Pod 只能读,或者需要配置是否可以被多个不同的 Pod 同时读写等,此时可以使用 PV 的访问策略进行简单控制,目前支持的访问策略如下:

  • ReadWriteOnce:可以被单节点以读写模式挂载,命令行中可以被缩写为 RWO。
  • ReadOnlyMany:可以被多个节点以只读模式挂载,命令行中可以被缩写为 ROX。
  • ReadWriteMany:可以被多个节点以读写模式挂载,命令行中可以被缩写为 RWX。
  • ReadWriteOncePod:只能被一个 Pod 以读写的模式挂载,命令中可以被缩写为 RWOP(1.22以上版本)。

虽然 PV 在创建时可以指定不同的访问策略,但是也要后端的存储支持才行。PV创建成功不表示可以使用,是否能正常使用一般创建完成Pod使用时来判断,比如一般情况下大部分块存储是不支持 ReadWriteMany 的,具体后端存储支持的访问模式可以参考 Kubernetes 官方文档。

在企业内,可能存储很多不同类型的存储,比如 NFS、Ceph、GlusterFS 等,针对不同类型的后端存储具有不同的配置方式,这也是对集群管理员的一种挑战,因为集群管理员需要对每种存储都有所了解。

已经将您提供的内容转换成Markdown格式:

基于 NFS 的 PV 创建

创建一个基于 NFS 的 PV(PV 目前没有 Namespace 隔离,不需要指定命名空间,在任意命名空间下创建的 PV 均可以在其他 Namespace 使用):

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nfs
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: nfs
  nfs:
    path: /opt
    server: 192.168.5.62

配置参数说明:

  • capacity:容量配置。
  • volumeMode:卷的模式,目前支持 Filesystem(文件系统)和 Block(块),其中 Block 类型需要后端存储支持,默认为文件系统。
  • accessModes:该 PV 的访问模式。
  • storageClassName:PV 的类,一个特定类型的 PV 只能绑定到特定类别的 PVC。
  • persistentVolumeReclaimPolicy:回收策略。
  • nfs:NFS 服务配置,包括以下两个选项。
    • path:NFS 上的共享目录。
    • server:NFS 的 IP 地址。

注意一般情况下,企业内的 NFS 很有可能是没有高可用性的,所以在企业内部要谨慎使用 NFS 作为后台存储,可以使用公有云的 NAS 服务,和 NFS 的协议一致,或者使用支持文件系统类型的分布式存储,比如 Ceph 等。

# kubectl create -f pv.yaml 
persistentvolume/pv-nfs created
# kubectl get pv
NAME     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv-nfs   1Gi        RWX            Recycle          Available           nfs                     3s

基于 HostPath 的 PV 创建

可以创建一个基于 hostPath 的 PV,和配置 NFS 的 PV 类似,只需要配置 hostPath 字段即可,其他配置基本一致:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-hostpath
spec:
  capacity:
    storage: 1Gi 
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: hostpath
  hostPath: 
    path: /data

配置参数说明:

  • hostPath:宿主机路径配置

注意 使用 hostPath 类型需要固定 Pod 所在的节点,防止 Pod 漂移造成数据丢失。

PV 的状态

在创建 PV 后,可以通过 kubectl get pv 查看已经创建的 PV 的状态:

# kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS    CLAIM             STORAGECLASS    REASON    AGE
pv-nfs    1Gi        RWX            Recycle          Bound     default/pvc-nfs   nfs                           2m
pv-host   1Gi        RWO            Retain           Available                    hostpath                      1m

其中有一个字段 STATUS 表示当前 PV 的状态,会有以下几种状态:

  • Available:可用,没有被 PVC 绑定的空闲资源。
  • Bound:已绑定,已经被 PVC 绑定。
  • Released:已释放,PVC 被删除,但是资源还未被重新使用。
  • Failed:失败,自动回收失败。

PersistentVolumeClaim

由 Kubernetes 管理员提前创建了 PV,我们应该怎样使用它呢?这里介绍 Kubernetes 的另一个概念 PersistentVolumeClaim(简称 PVC)。PVC 是其他技术人员在 Kubernetes 上对存储的申请,它可以标明一个程序需要用到什么样的后端存储、多大的空间以及以什么访问模式进行挂载。这一点和 Pod 的 QoS 配置类似,Pod 消耗节点资源,PVC 消耗 PV 资源,Pod 可以请求特定级别的资源(CPU 和内存),PVC 可以请求特定的大小和访问模式的 PV。例如申请一个大小为 5Gi 且只能被一个 Pod 只读访问的存储。

在实际使用时,虽然用户通过 PVC 获取存储支持,但是用户可能需要具有不同性质的 PV 来解决不同的问题,比如使用 SSD 硬盘来提高性能。所以集群管理员需要根据不同的存储后端来提供各种 PV,而不仅仅是大小和访问模式的区别,并且无须让用户了解这些卷的具体实现方式和存储类型,达到了存储的解藕,降低了存储使用的复杂度。

PVC 的创建

我们列举了几个常用的存储类型的配置方式,接下来看一下如何让 PVC 和这些 PV 绑定。

注意 PVC 和 PV 进行绑定的前提条件是一些参数必须匹配,比如 accessModes、storageClassName、volumeMode 都需要相同,并且 PVC 的 storage 需要小于等于 PV 的 storage 配置。

创建一个 PVC 即可与 PV 绑定(注意 PV 和 PVC 加粗的部分,PV 和 PVC 进行绑定并非是名字相同,而是 StorageClassName 相同且其他参数一致才可以进行绑定),代码如下:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: task-pv-claim
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: hostpath
  resources:
    requests:
      storage: 1Gi

注意 PVC 具有命名空间隔离性,上述代码没有添加 namespace 参数,会默认创建在 default 命名空间。如果需要给其他命名空间的 Pod 使用,就应将其创建在指定的命名空间。

PVC 的使用

上述创建了 PV,并使用 PVC 与其绑定,现在还差一步就能让程序使用这块存储,那就是将 PVC 挂载到 Pod。和之前的挂载方式类似,PVC 的挂载也是通过 volumes 字段进行配置的,只不过之前需要根据不同的存储后端填写很多复杂的参数,而使用 PVC 进行挂载时,只填写 PVC 的名字即可,不需要再关心任何的存储细节,这样即使不是 Kubernetes 管理员,不懂存储的其他技术人员想要使用存储,也可以非常简单地进行配置和使用。

pvc-nfs

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nfs
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: nfs
  nfs:
    path: /data/nfs
    server: 192.168.5.62

① capacity 指定 PV 的容量为 1G。

② accessModes 指定访问模式为 ReadWriteOnce,支持的访问模式有:

  • ReadWriteOnce:PV 能以 read-write 模式 mount 到单个节点。

  • ReadOnlyMany:PV 能以 read-only 模式 mount 到多个节点。

  • ReadWriteMany :PV 能以 read-write 模式 mount 到多个节点。

③ persistentVolumeReclaimPolicy 指定当 PV 的回收策略为 Recycle,支持的策略有:

  • Retain: 需要管理员手工回收。

  • Recycle:启动一个pod容器来,清除 PV 中的数据,效果相当于执行 rm -rf /thevolume/*。

  • Delete: 删除 Storage Provider 上的对应存储资源,例如 AWS EBS、GCE PD、Azure Disk、- OpenStack Cinder Volume 等。

④ storageClassName 指定 PV 的 class 为 nfs。相当于为 PV 设置了一个分类,PVC 可以指定 class 申请相应 class 的 PV。

⑤ 指定 PV 在 NFS 服务器上对应的目录。

创建pvc

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-nfs
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
  storageClassName: nfs
# kubectl get pv,pvc
NAME                           CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM             STORAGECLASS   REASON   AGE
persistentvolume/pv-nfs        1Gi        RWX            Recycle          Bound       default/pvc-nfs   nfs                     4m48s

NAME                            STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/pvc-nfs   Bound    pv-nfs   1Gi        RWX            nfs            23s

创建deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - name: nfs
          mountPath: /data
      volumes:
      - name: nfs
        persistentVolumeClaim:
          claimName: pvc-nfs

验证

[root@k8s-master ~]# kubectl get pod 
NAME                               READY   STATUS              RESTARTS   AGE
nginx-deployment-976b6f459-cpzjk   0/1     ContainerCreating   0          2s
test-pod-nfs                       1/1     Running             0          5d
[root@k8s-master ~]# kubectl get pod 
NAME                               READY   STATUS    RESTARTS   AGE
nginx-deployment-976b6f459-cpzjk   1/1     Running   0          44s
test-pod-nfs                       1/1     Running   0          5d
[root@k8s-master ~]# kubectl exec -it nginx-deployment-976b6f459-cpzjk bash 
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@nginx-deployment-976b6f459-cpzjk:/# cd /data/
root@nginx-deployment-976b6f459-cpzjk:/data# mkdir pod
root@nginx-deployment-976b6f459-cpzjk:/data# 



在nfs服务查看
[root@k8s-node01 nfs]# ls /data/nfs/
nfs pod

删除pvc

[root@k8s-master ~]# kubectl delete -f deploy.yaml 
[root@k8s-master ~]# kubectl delete -f pvc.yaml 

在retain策略中,会将pv中的数据保留,但其 PV 状态会一直处于 Released,不能被其他 PVC 申请。为了重新使用存储资源,可以删除并重新创建 pv。删除操作只是删除了 PV 对象,存储空间中的数据并不会被删除。

动态存储 StorageClass

虽然使用 PV 和 PVC 能屏蔽一些存储使用上的细节,降低了存储使用的复杂度,但是也会有另一个问题无法解决。当公司 Kubernetes 集群很多,并且使用它们的技术人员过多时,对于 PV 的创建是一个很耗时、耗力的工作,并且达到一定规模后,过多的 PV 将难以维护。所以就需要某种机制用于自动管理 PV 的生命周期,比如创建、删除、自动扩容等,于是 Kubernetes 就设计了一个名为 StorageClass(缩写为 SC,没有命名空间隔离性)的东西,通过它可以动态管理集群中的 PV,这样 Kubernetes 管理员就无须浪费大量的时间在 PV 的管理中。

在 Kubernetes 中,管理员可以只创建 StorageClass“链接”到后端不同的存储,比如 Ceph、GlusterFS、OpenStack 的 Cinder、其他公有云提供的存储等,之后有存储需求的技术人员,创建一个 PVC 指向对应的 StorageClass 即可,StorageClass 会自动创建 PV 供 Pod 使用,也可以使用 StatefulSet 的 volumeClaimTemplate 自动分别为每个 Pod 申请一个 PVC。

定义 StorageClass

定义一个 StorageClass 的示例如下:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: gluster-heketi
provisioner: kubernetes.io/glusterfs  
parameters:
  resturl: "http://10.10.10.100:8080" 
  restuser: "admin"
  secretNamespace: "default"
  secretName: "heketi-secret"

该示例是使用 StorageClass 链接 GlusterFS 的一个示例,PV 和 Volume 可以直接配置 GlusterFS,其配置参数和 StorageClass 的 parameters 类似,只不过 PV 是单独供 PVC 使用的,Volume 是针对某个 Pod 使用的,而 StorageClass 是一个全局的配置,没有命名空间隔离性,任何命名空间下有存储需求的应用都可以配置对应的 StorageClass 来获取存储。

StorageClass 还提供了一些更高级的配置,比如 provisioner、parameters、reclaimPolicy、allowVolumeExpansion 等:

  • Provisioner:指定通过哪个插件来创建 PV,必须指定此字段。
  • Parameters:针对后端配置的不同参数。
  • ReclaimPolicy:回收策略,可以是 Delete、Retain,默认为 Delete。
  • AllowVolumeExpansion:是否允许对 PV 进行扩容。

该小节通过一个简单的示例来说明如何配置一个 StorageClass,对于其他不同类型的存储配置方式只是 parameters 不一样,使用方式都是一样的。

PV/PVC动态供给项目实战

部署步骤

1、部署nfs

3个节点都下载:

# yum -y install nfs-utils

主节点配置nfs服务端

[root@k8s-node01 ~]# mkdir /data/nfs
[root@k8s-node01 ~]# chmod 777  -R /data/nfs
[root@k8s-node01 ~]# cat /etc/exports
/data/nfs *(rw,no_root_squash,no_all_squash,sync) 
[root@k8s-node01 ~]# systemctl restart nfs

2、定义一个storageclass

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage
provisioner: test-nfs-storage #这里的名称要和provisioner配置文件中的环境变量PROVISIONER_NAME保持一致
parameters:
#  archiveOnDelete: "false"
  archiveOnDelete: "true"
reclaimPolicy: Retain

3、部署授权

因为storage自动创建pv需要经过kube-apiserver,所以要进行授权

  • 创建1个sa

  • 创建1个clusterrole,并赋予应该具有的权限,比如对于一些基本api资源的增删改查;

  • 创建1个clusterrolebinding,将sa和clusterrole绑定到一起;这样sa就有权限了;

  • 然后pod中再使用这个sa,那么pod再创建的时候,会用到sa,sa具有创建pv的权限,便可以自动创建pv;

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default        #根据实际环境设定namespace,下面类同
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
    # replace with namespace where provisioner is deployed
  namespace: default
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io

4、部署一个自动创建pv的服务

这里自动创建pv的服务由nfs-client-provisioner 完成

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: default  #与RBAC文件中的namespace保持一致
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nfs-client-provisioner
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-provisioner
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          # 注意镜像版本  有的版本不兼容
          image: q1617299788/nfs-subdir-external-provisioner:v4.0.2
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: test-nfs-storage  
            - name: NFS_SERVER
              value: 192.168.5.62   #NFS Server IP地址
            - name: NFS_PATH
              value:  /data/nfs    #NFS挂载卷
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.5.62  #NFS Server IP地址
            path: "/data/nfs"     #NFS 挂载卷

创建:

[root@k8s-master ~]# kubectl apply -f sc.yaml 
[root@k8s-master ~]# kubectl apply -f rbac.yaml
[root@k8s-master ~]# kubectl apply -f nfs.yaml

查看创建好的storageclass:

[root@k8s-master ~]# kubectl get sc
NAME                  PROVISIONER   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
managed-nfs-storage   nfs           Delete          Immediate           false                  3m23s

nfs-client-provisioner 会以pod运行在k8s中,

[root@k8s-master ~]# kubectl get pod 
NAME                                      READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-6866f976d9-s6vhx   1/1     Running   0          27s

我们部署一个nginx服务,让其html下面自动挂载数据卷,

apiVersion: v1
kind: Service
metadata:
  name: nginx-headless
spec:
  clusterIP: None   #None值,就是表示无头服务
  selector:
    app: nginx
  ports:
  - name: web
    port: 80
    protocol: TCP
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  podManagementPolicy: OrderedReady  #pod名-> 0-N,删除N->0
  replicas: 3  #三个副本
  revisionHistoryLimit: 10
  serviceName: nginx-headless
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:  #name没写,会默认生成的
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - name: web #填vcp名字
          mountPath: /var/www/html
      imagePullSecrets:
      - name: registry-op.test.cn
  volumeClaimTemplates:
  - metadata:
      name: web
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: managed-nfs-storage  #存储类名,指向我们已经建好的
      volumeMode: Filesystem
      resources:
        requests:
          storage: 512M

查看pod

# kubectl get pod -l app=nginx
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          4m16s
web-1   1/1     Running   0          4m9s
web-2   1/1     Running   0          4m3s

进入其中一个容器,创建一个文件:

[root@k8s-master pvc-test]# kubectl exec -it web-0 bash
root@web-0:/# cd /var/www/html/
root@web-0:/var/www/html# ls
root@web-0:/var/www/html# mkdir 123
root@web-0:/var/www/html# 

查看nfs目录

[root@k8s-node01 nfs]# ll
total 0
drwxrwxrwx. 3 root root 17 Aug  6 18:47 default-web-web-0-pvc-567713f0-b838-4967-8f3c-86300b7894b9
drwxrwxrwx. 2 root root  6 Aug  6 18:40 default-web-web-1-pvc-82d32cd9-87f4-42fb-a5d2-4c59914183e8
drwxrwxrwx. 2 root root  6 Aug  6 18:40 default-web-web-2-pvc-50b97e1b-5db5-470d-8b97-08646a5241d6

关于StorageClass回收策略对数据的影响

1、第一种配置

archiveOnDelete: "false"
reclaimPolicy: Delete   #默认没有配置,默认值为Delete 

测试结果

  1. pod删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
  2. sc删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
  3. 删除PVC后,PV被删除且NFS Server对应数据被删除

2、第二种配置

archiveOnDelete: "false"
reclaimPolicy: Retain

测试结果

  1. pod删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
  2. sc删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
  3. 删除PVC后,PV不会别删除,且状态由Bound变为Released,NFS Server对应数据被保留
  4. 重建sc后,新建PVC会绑定新的pv,旧数据可以通过拷贝到新的PV中

3、第三种配置

archiveOnDelete: "ture"
reclaimPolicy: Retain

测试结果

  1. pod删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
  2. sc删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
  3. 删除PVC后,PV不会别删除,且状态由Bound变为Released,NFS Server对应数据被保留
  4. 重建sc后,新建PVC会绑定新的pv,旧数据可以通过拷贝到新的PV中

4、第四种配置

archiveOnDelete: "ture"
reclaimPolicy: Delete

测试结果

  1. pod删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
  2. sc删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
  3. 删除PVC后,PV不会别删除,且状态由Bound变为Released,NFS Server对应数据被保留
  4. 重建sc后,新建PVC会绑定新的pv,旧数据可以通过拷贝到新的PV中

总结:除以第一种配置外,其他三种配置在PV/PVC被删除后数据依然保留

常见问题

1、如何设置默认的StorageClass

有两种方法,一种是用kubectl patch,一种是在yaml文件直接注明

kubectl patch

#设置default时是为"true" 
# kubectl patch storageclass managed-nfs-storage -p  '{ "metadata" : { "annotations" :{"storageclass.kubernetes.io/is-default-class": "true"}}}'

# kubectl get sc  #名字后面有个"default"字段
NAME                            PROVISIONER        RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
managed-nfs-storage (default)   test-nfs-storage   Retain          Immediate           false                  28h

#取消default,值为"false"

# kubectl patch storageclass managed-nfs-storage -p  '{ "metadata" : { "annotations" :{"storageclass.kubernetes.io/is-default-class": "false"}}}' 

# kubectl get sc
NAME                  PROVISIONER        RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
managed-nfs-storage   test-nfs-storage   Retain          Immediate           false                  28h

yaml文件

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage
  annotations:
    "storageclass.kubernetes.io/is-default-class": "true"   #添加此注释,这个变为default storageclass
provisioner: test-nfs-storage #这里的名称要和provisioner配置文件中的环境变量PROVISIONER_NAME保持一致
parameters:
#  archiveOnDelete: "false"
  archiveOnDelete: "true" 
reclaimPolicy: Retain

2、如何使用默认的StorageClass

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-www
#  annotations:
#    volume.beta.kubernetes.io/storage-class: "managed-nfs-storage"  ##这里就可以不用指定 
spec: 
#  storageClassName: "managed-nfs-storage"  ##这里就可以不用指定
  accessModes:
    - ReadWriteMany
    #- ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
Logo

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

更多推荐