RedCloud Help

9.k8s持久化存储

在k8s 中为什么要做持久化存储?

在k8s 中部署的应用都是以pod 容器的形式运行的,假如我们部署MySQL、Redis 等数据库,需要 对这些数据库产生的数据做备份。因为Pod 是有生命周期的,如果pod 不挂载数据卷,那pod 被删除或 重启后这些数据会随之消失,如果想要长久的保留这些数据就要用到pod 数据持久化存储。

9.1 k8s持久化存储:emptyDir

查看k8s支持哪些存储 kubectl explain pods.spec.volumes KIND: Pod VERSION: v1 RESOURCE: volumes <[]Object> DESCRIPTION: List of volumes that can be mounted by containers belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes Volume represents a named volume in a pod that may be accessed by any container in the pod. FIELDS: awsElasticBlockStore <Object> azureDisk <Object> azureFile <Object> cephfs <Object> cinder <Object> configMap <Object> csi <Object> downwardAPI <Object> emptyDir <Object> ephemeral <Object> fc <Object> flexVolume <Object> flocker <Object> gcePersistentDisk <Object> gitRepo <Object> glusterfs <Object> hostPath <Object> iscsi <Object> name <string> -required- nfs <Object> persistentVolumeClaim <Object> photonPersistentDisk <Object> portworxVolume <Object> projected <Object> quobyte <Object> rbd <Object> scaleIO <Object> secret <Object> storageos <Object> vsphereVolume <Object>

常用的如下:

  • emptyDir

  • hostPath

  • nfs

  • persistentVolumeClaim

  • glusterfs

  • cephfs

  • configMap

  • secret

我们想要使用存储卷,需要经历如下步骤:

  1. 定义pod 的volume,这个volume 指明它要关联到哪个存储上的

  2. 在容器中要使用volumemounts 挂载对应的存储

经过以上两步才能正确的使用存储卷

emptyDir类型的Volume是在Pod分配到Node上时被创建,Kubernetes会在Node上自动分配一个目录,因此无需指定宿主机Node上对应的目录文件。这个目录的初始内容为空,当Pod从Node上移除时,emptyDir中的数据会被永久删除。emptyDir Volume主要用于某些应用程序无需永久保存的临时目录,多个容器的共享目录等。

#创建一个pod,挂载临时目录emptyDir

Emptydir 的官方网址: https://kubernetes.io/docs/concepts/storage/volumes#emptydir

apiVersion: v1 kind: Pod metadata: name: pod-empty spec: containers: - name: container-empty image: nginx volumeMounts: - mountPath: /cache name: cache-volume volumes: - emptyDir: { } name: cache-volume
kubectl apply -f emptydir.yaml # 查看本机临时目录存在的位置,可用如下的方法: # 查询pod调度到那个节点 kubectl get pods -o wide | grep empty # 查看pod的uid kubectl get pods pod-empty -o yaml | grep uid tree /var/lib/kubelet/pods/40dd430d-38ea-45c9-a6e7-5cf471853f44 ├── containers │ └── container-empty │ └── 1046da75 ├── etc-hosts ├── plugins │ └── kubernetes.io~empty-dir │ ├── cache-volume │ │ └── ready │ └── wrapped_kube-api-access-kz74c │ └── ready └── volumes ├── kubernetes.io~empty-dir │ └── cache-volume └── kubernetes.io~projected └── kube-api-access-kz74c ├── ca.crt -> ..data/ca.crt ├── namespace -> ..data/namespace └── token -> ..data/token

由上可知,临时目录在本地的/var/lib/kubelet/pods/40dd430d-38ea-45c9-a6e7-5cf471853f44/volumes/kubernetes.io~ empty-dir/cache-volume下

9.2 k8s持久化存储: hostPath

hostPath Volume 是指Pod 挂载宿主机上的目录或文件。 hostPath Volume 使得容器可以使用宿主 机的文件系统进行存储,hostpath( 宿主机路径):节点级别的存储卷,在pod 被删除,这个存储卷还是 存在的,不会被删除,所以只要同一个pod 被调度到同一个节点上来,在pod 被删除重新被调度到这个 节点之后,对应的数据依然是存在的。

# 查看hostPath存储卷的用法 kubectl explain pods.spec.volumes.hostPath KIND: Pod VERSION: v1 RESOURCE: hostPath <Object> DESCRIPTION: HostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath Represents a host path mapped into a pod. Host path volumes do not support ownership management or SELinux relabeling. FIELDS: path <string> -required- Path of the directory on the host. If the path is a symlink, it will follow the link to the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath type <string> Type for HostPath Volume Defaults to "" More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath

#创建一个pod,挂载hostPath 存储卷.

apiVersion: v1 kind: Pod metadata: name: test-hostpath spec: containers: - image: nginx imagePullPolicy: IfNotPresent name: test-nginx volumeMounts: - mountPath: /test-nginx name: test-volume - image: tomcat:8.5-jre8-alpine imagePullPolicy: IfNotPresent name: test-tomcat volumeMounts: - mountPath: /test-tomcat name: test-volume volumes: - name: test-volume hostPath: path: /data1 type: DirectoryOrCreate

DirectoryOrCreate 表示本地有/data1 目录,就用本地的,本地没有就会在pod 调度到的节点自 动创建一个.

查看pod在哪个物理节点上

kubectl get pods -o wide|grep hostpath

进入指定节点的物理机上,可以看到已经创建了存储目录/data1,这个/data1 会作为pod 的持久化存储目录

9.2.1 hostpath 存储卷缺点:

pod 删除之后重新创建必须调度到同一个node 节点,数据才不会丢失 可以用分布式存储: nfs,cephfs,glusterfs

9.3 k8s持久化存储:nfs

上节说的hostPath 存储,存在单点故障,pod 挂载hostPath 时,只有调度到同一个节点,数据才不会丢失。那可以使用nfs 作为持久化存储。

9.3.1 搭建nfs服务

9.3.1.1 以k8s的控制节点作为NFS服务端

1. 安装nfs服务器软件包
apt install nfs-kernel-server
2. 创建共享目录
mkdir -p /data/volumes
3.配置nfs共享
vim /etc/exports # 添加 /data/volumes *(rw,no_root_squash,no_subtree_check)
  • *(rw):允许所有客户端读写访问

  • sync:数据同步写入磁盘

  • no_root_squash:客户端 root 用户具有服务器 root 权限

  • no_subtree_check:关闭子目录检查(提高性能)

4. 重启服务并配置生效
exportfs -arv service nfs-kernel-server restart
5.客户端软件包
apt install nfs-common
6.创建本地目录
mkdir -p /test
7.手动挂载NFS共享
mount -t nfs 10.0.72.4:/data/volumes /test
8.自动挂载
vim /etc/fstab 10.0.72.4:/data/volumes /test nfs defaults 0 0 ## 使配置生效 mount -a

Pod 挂载nfs 的官方地址: https://kubernetes.io/zh/docs/concepts/storage/volumes/

apiVersion: v1 kind: Pod metadata: name: test-nfs-volume spec: containers: - name: test-nfs image: nginx imagePullPolicy: IfNotPresent ports: - containerPort: 80 protocol: TCP volumeMounts: - mountPath: /usr/share/nginx/html name: nfs-volumes volumes: - name: nfs-volumes nfs: path: /data/volumes server: 10.0.72.4

注:path: /data/volumes #nfs 的共享目录 server:10.0.72.4 机器的ip,这个是安装nfs 服务的地址

进入容器验证一下

kubectl exec -it test-nfs-volume -- /bin/bash

#上面说明挂载nfs 存储卷成功了,nfs 支持多个客户端挂载,可以创建多个pod,挂载同一个nfs 服务器共享出来的目录;但是nfs 如果宕机了,数据也就丢失了,所以需要使用分布式存储,常见的分 布式存储有glusterfs 和cephfs

9.4 k8s持久化 pvc

https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes

9.4.1 k8s PV是什么

PersistentVolume(PV)是群集中的一块存储,由管理员配置或使用存储类动态配置。 它是集群中的资源,就像pod 是k8s 集群资源一样。 PV 是容量插件,如Volumes,其生命周期独立于使用PV 的任 何单个pod。

9.4.2 k8sPVC是什么?

PersistentVolumeClaim(PVC)是一个持久化存储卷,我们在创建pod 时可以定义这个类型的存储 卷。 它类似于一个pod。 Pod 消耗节点资源,PVC 消耗PV 资源。 Pod 可以请求特定级别的资源(CPU 和 内存)。 pvc 在申请pv 的时候也可以请求特定的大小和访问模式( 例如,可以一次读写或多次只读)。

9.4.3 k8s PVC和PV的工作原理

PV是群集中的资源。PVC是对这些资源的请求。 PV和PVC之间的相互作用遵循以下生命周期:

  • pv的供应方式

    • 可以通过两种方式配置PV:静态或动态

      • 静态

        集群管理员创建了许多PV。它们包含可供群集用户使用的实际存储的详细信息。它们存在于 Kubernetes API 中,可供使用。

      • 动态

        当管理员创建的静态PV 都不匹配用户的PersistentVolumeClaim 时,群集可能会尝试为PVC 专门动 态配置卷。此配置基于StorageClasses,PVC 必须请求存储类,管理员必须创建并配置该类,以便进行 动态配置。

  • 绑定

用户创建pvc 并指定需要的资源和访问模式。在找到可用pv 之前,pvc 会保持未绑定状态

  • 使用

    • 需要找一个存储服务器,把它划分成多个存储空间

    • k8s管理员可以把这些存储空间定义成多个pv

    • 在pod中使用pvc类型的存储卷之前需要先创建pvc,通过定义需要使用的pv的大小和对应的访问模式,找到合适的pv;

    • pvc被创建之后,就可以当成存储卷来使用了,我们在定义pod时就可以使用这个pvc的存储卷

    • pvc和pv它们是一一对应的关系,pv如果被pvc绑定了,就不能被其他pvc使用了。

    • 我们在创建pvc的时候,应该确保和底下的pv能绑定,如果没有合适的pv,那么pvc就会处在pending状态。

  • 回收策略

    当我们创建pod 时如果使用pvc 做为存储卷,那么它会和pv 绑定,当删除pod,pvc 和pv 绑定就会 解除,解除之后和pvc 绑定的pv 卷里的数据需要怎么处理,目前,卷可以保留,回收或删除: retain recycle delete。

    • retain

      当删除pvc 的时候,pv 仍然存在,处于released 状态,但是它不能被其他pvc 绑定使用,里面的数 据还是存在的,当我们下次再使用的时候,数据还是存在的,这个是默认的回收策略

    • delete 删除pvc 时即会从Kubernetes 中移除PV,也会从

    • 相关的外部设施中删除存储资产

9.4.3.1 创建pod,使用pvc作为持久化存储卷

9.4.3.1.1 创建nfs共享目录
#在宿主机创建NFS需要的共享目录 mkdir /data/volume_test/v{1,2} -p #配置nfs 共享宿主机上的/data/volume_test/v1..v2 目录 cat /etc/exports /data/volumes *(rw,no_root_squash) /data/volume_test/v1 *(rw,no_root_squash) /data/volume_test/v2 *(rw,no_root_squash) #重新加载配置,使配置成效 exportfs -arv
9.4.3.1.2 如何编写pv的资源清单文件
# 查看定义pv需要的字段 kubectl explain pv KIND: PersistentVolume VERSION: v1 DESCRIPTION: PersistentVolume (PV) is a storage resource provisioned by an administrator. It is analogous to a node. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes FIELDS: apiVersion <string> kind <string> metadata <Object> spec <Object> status <Object> Status represents the current information/status for the persistent volume. Populated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistent-volumes #查看定义nfs类型的pv需要的字段 kubectl explain pv.spec.nfs KIND: PersistentVolume VERSION: v1 RESOURCE: nfs <Object> DESCRIPTION: NFS represents an NFS mount on the host. Provisioned by an admin. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs Represents an NFS mount that lasts the lifetime of a pod. NFS volumes do not support ownership management or SELinux relabeling. FIELDS: path <string> -required- Path that is exported by the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs readOnly <boolean> ReadOnly here will force the NFS export to be mounted with read-only permissions. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs server <string> -required- Server is the hostname or IP address of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs
9.4.3.1.3 创建pv

参考:https://kubernetes.io/zh/docs/concepts/storage/persistent-volumes/#reclaiming

apiVersion: v1 kind: PersistentVolume metadata: name: v1 spec: capacity: storage: 1Gi accessModes: [ "ReadWriteOnce" ] nfs: path: /data/volume_test/v1 server: 10.0.72.4 --- apiVersion: v1 kind: PersistentVolume metadata: name: v2 spec: capacity: storage: 2Gi accessModes: [ "ReadOnlyMany" ] nfs: path: /data/volume_test/v2 server: 10.0.72.4
kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE v1 1Gi RWO Retain Available 11s v2 2Gi ROX Retain Available 10s

STATUS是Available,表示pv是可用的。

9.4.3.1.4 创建pvc 和符合条件的pv绑定
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: my-pvc spec: accessModes: [ "ReadOnlyMany" ] resources: requests: storage: 2Gi
kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE v1 1Gi RWO Retain Available 3m47s v2 2Gi ROX Retain Bound default/my-pvc 3m46s # STATUS是Bound,表示这个pv已经被my-pvc绑定了 kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE my-pvc Bound v2 2Gi ROX 21s # pvc 的名字绑定到pv绑定的v2这个pv-pvc可使用的容量是2G
9.4.3.1.5 创建pod,挂载pvc
apiVersion: v1 kind: Pod metadata: name: pod-pvc spec: containers: - name: nginx image: nginx volumeMounts: - mountPath: /usr/share/nginx/html name: nginx-html volumes: - name: nginx-html persistentVolumeClaim: claimName: my-pvc

9.4.3 k8s存储:storagecalss

上面介绍的PV 和PVC 模式都是需要先创建好PV,然后定义好PVC 和pv 进行一对一的Bond,但是如 果PVC 请求成千上万,那么就需要创建成千上万的PV,对于运维人员来说维护成本很高,Kubernetes 提 供一种自动创建PV 的机制,叫StorageClass,它的作用就是创建PV 的模板。k8s 集群管理员通过创建 storageclass 可以动态生成一个存储卷pv 供k8s pvc 使用。

每个StorageClass 都包含字段provisioner,parameters 和reclaimPolicy。

具体来说,StorageClass 会定义以下两部分:

  1. PV的属性,比如存储的大小,类型等;

  2. 创建这种PV需要使用到的存储插件,比如Ceph ,NFS等

有了这两部分信息,Kubernetes 就能够根据用户提交的PVC,找到对应的StorageClass,然后 Kubernetes 就会调用 StorageClass 声明的存储插件,创建出需要的PV。 每个StorageClass 都包含字段provisioner,parameters 和reclaimPolicy。 具体来说,StorageClass 会定义以下两部分:

  1. PV 的属性 ,比如存储的大小、类型等;

  2. 创建这种PV 需要使用到的存储插件,比如Ceph、NFS 等

有了这两部分信息,Kubernetes 就能够根据用户提交的PVC,找到对应的StorageClass,然后 Kubernetes 就会调用 StorageClass 声明的存储插件,创建出需要的PV。

kubectl explain storageclass KIND: StorageClass VERSION: storage.k8s.io/v1 DESCRIPTION: StorageClass describes the parameters for a class of storage for which PersistentVolumes can be dynamically provisioned. StorageClasses are non-namespaced; the name of the storage class according to etcd is in ObjectMeta.Name. FIELDS: allowVolumeExpansion <boolean> AllowVolumeExpansion shows whether the storage class allow volume expand allowedTopologies <[]Object> Restrict the node topologies where volumes can be dynamically provisioned. Each volume plugin defines its own supported topology specifications. An empty TopologySelectorTerm list means there is no topology restriction. This field is only honored by servers that enable the VolumeScheduling feature. apiVersion <string> APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources kind <string> Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds metadata <Object> Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata mountOptions <[]string> Dynamically provisioned PersistentVolumes of this storage class are created with these mountOptions, e.g. ["ro", "soft"]. Not validated - mount of the PVs will simply fail if one is invalid. parameters <map[string]string> Parameters holds the parameters for the provisioner that should create volumes of this storage class. provisioner <string> -required- Provisioner indicates the type of the provisioner. reclaimPolicy <string> Dynamically provisioned PersistentVolumes of this storage class are created with this reclaimPolicy. Defaults to Delete. volumeBindingMode <string> VolumeBindingMode indicates how PersistentVolumeClaims should be provisioned and bound. When unset, VolumeBindingImmediate is used. This field is only honored by servers that enable the VolumeScheduling feature.

provisioner:供应商,storageclass 需要有一个供应者,用来确定我们使用什么样的存储来创建 pv,常见的provisioner 如下

(https://kubernetes.io/zh/docs/concepts/storage/storage-classes/):

image_25.png

provisioner 既可以由内部供应商提供,也可以由外部供应商提供,如果是外部供应商可以参考 https://github.com/kubernetes-incubator/external-storage/下提供的方法创建。

https://github.com/kubernetes-sigs/sig-storage-lib-external-provisioner

以NFS 为例,要想使用NFS,我们需要一个nfs-client 的自动装载程序,称之为provisioner,这 个程序会使用我们已经配置好的NFS 服务器自动创建持久卷,也就是自动帮我们创建PV。

reclaimPolicy:回收策略 allowVolumeExpansion:允许卷扩展,PersistentVolume 可以配置成可扩展。将此功能设置为true 时,允许用户通过编辑相应的 PVC 对象来调整卷大小。当基础存储类的allowVolumeExpansion 字段设 置为 true 时,以下类型的卷支持卷扩展。

image_26.png

注意:此功能仅用于扩容卷,不能用于缩小卷。

9.4.3.1 安装 nfs provisioner,用于配合存储类动态生成pv

9.4.3.1.1 创建运行 nfs-provisioner需要的sa账号

apiVersion: v1 kind: ServiceAccount metadata: name: nfs-provisioner

9.4.3.1.2 对sa授权

kubectl create clusterrolebinding nfs-provisioner-clusterrolebinding --clusterrole=cluster-admin --serviceaccount=default:nfs-provisioner

9.4.3.1.3 安装nfs-provisioner程序

mkdir /data/nfs_pro -p # 把/data/nfs_pro变成nfs共享的目录 vim /etc/exports /data/nfs_pro *(rw,no_root_squash) exportfs -arv service nfs-kernel-server restart
apiVersion: apps/v1 kind: Deployment metadata: name: nfs-provisioner spec: selector: matchLabels: app: nfs-provisioner replicas: 1 strategy: type: Recreate template: metadata: labels: app: nfs-provisioner spec: serviceAccount: nfs-provisioner containers: - name: nfs-provisioner image: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0 volumeMounts: - mountPath: /persistentvolumes name: nfs-client-root env: - name: PROVISIONER_NAME value: example.com/nfs - name: NFS_SERVER value: 10.0.72.4 - name: NFS_PATH value: /data/nfs_pro volumes: - name: nfs-client-root nfs: server: 10.0.72.4 path: /data/nfs_pro

查看是否运行正常

9.4.3.2 创建storageclass,动态供给pv

apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: nfs provisioner: example.com/nfs
kubectl -f nfs-storageclass.yaml kubectl get storageclass NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE nfs example.com/nfs Delete Immediate false 12s # 显示内容如上,说明storageclass创建成功了。

9.4.3.3 创建pvc,通过storageclass动态生成pv

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: test-claim1 spec: accessModes: ["ReadWriteMany"] resources: requests: storage: 1Gi storageClassName: nfs
kubectl apply -f claim.yaml # 查看是否动态生成了pv,pvc是否创建成功了,并和pv绑定 kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE my-pvc Bound v2 2Gi ROX 138m test-claim1 Bound pvc-3b41f26b-0a96-478e-89a7-6fc49a8ecc8b 1Gi RWX nfs 2m31s #通过上面可以看到test-claim1 的pvc 已经成功创建了,绑定的pv 是pvc-3b41f26b-0a96-478e-89a7-6fc49a8ecc8b,这个pv 是由storageclass 调用nfs provisioner 自动生成的。

步骤总结:

  1. 供应商: 创建一个nfs provisioner

  2. 创建storageclass,storageclass指定刚才创建的供应商

  3. 创建pvc,这个pvc指定storageclass

9.4.3.4 创建pod,挂在storageclass动态生成的pvc: test-claim1

apiVersion: v1 kind: Pod metadata: name: read-pod spec: containers: - name: read-pod image: nginx imagePullPolicy: IfNotPresent volumeMounts: - mountPath: /usr/share/nginx/html name: nfs-pvc restartPolicy: "Never" volumes: - name: nfs-pvc persistentVolumeClaim: claimName: test-claim1
12 January 2026