k8s 持久化存储-常见的存储卷介绍

一、简介

当在 Kubernetes 中部署应用程序时,数据通常存储在容器内部的文件系统中。然而,容器的生命周期是临时的,当容器被删除或重新启动时,容器内的数据也会丢失。为了解决这个问题,需要在 Kubernetes 中实现数据持久化。数据持久化是将数据保存到持久化存储介质(如磁盘)上,以便在容器被重新创建或迁移到其他节点时仍然可用。数据持久化的场景包括:
1、数据存储:许多应用程序需要保存用户生成的数据,如文件、图片、日志等。通过数据持久化,可以确保这些数据在容器销毁或重启后仍然存在。
2、数据共享:在某些情况下,多个容器或 Pod 可能需要访问和共享相同的数据。通过将数据存储在持久化卷中,可以使多个容器之间共享数据,实现数据一致性和共享访问。
3、数据备份和恢复:持久化存储解决方案通常提供数据备份和恢复机制,以防止数据丢失。这对于保护重要数据、应对故障或恢复到先前的状态非常重要。
4、数据迁移和扩展:在 Kubernetes 集群中,可能需要扩展应用程序到多个节点或迁移到其他集群。通过将数据存储在持久化卷中,可以方便地将数据与应用程序一起迁移到新的环境,无需担心数据丢失。
5、数据安全:某些行业或应用程序对数据的安全性有严格的要求。持久化存储解决方案通常提供数据加密和访问控制功能,以确保数据在传输和存储过程中的安全性。`

二、存储卷简介

1、emptyDir

EmptyDir 卷的使用场景包括:
1. 临时文件共享:如果多个容器需要在同一个 Pod 中进行临时文件共享,EmptyDir 卷是一个简便的解决方案。容器可以将文件写入 EmptyDir 卷,并且其他容器可以读取和修改这些文件。当 Pod被删除或重启时,EmptyDir 卷中的文件会被清空。
2. 共享数据:某些应用程序可能需要在容器之间共享一些中间结果或临时数据。EmptyDir 卷可以作为容器之间的临时数据交换机制。容器可以将数据写入 EmptyDir 卷,其他容器可以读取和处理这些数据,从而实现数据共享和协作。
3. 缓存:EmptyDir 卷可以用作容器的缓存存储。某些应用程序可能需要将频繁访问的数据缓存到本地存储中,以提高性能。EmptyDir 卷提供了一个临时的、高速的存储介质来存储缓存数据,提供快速的读取和写入操作。
当在 Kubernetes 中使用存储卷(Volume),需要经历以下步骤:
1. 定义 Pod 的 Volume:在 Pod 的配置中定义一个 Volume,指定它要关联到哪个存储上。可以使用不同类型的 Volume,如 EmptyDir、HostPath、PersistentVolumeClaim 等,具体选择取决于需求。
2. 在容器中使用 Volume Mounts:在 Pod 的配置中,为每个容器定义 Volume Mounts,将Volume 挂载到容器内部的指定路径上。这样容器就能够访问和操作 Volume 中的数据。
3. 通过 Volume 实现数据持久化和共享:一旦 Volume 被挂载到容器中,容器就可以将数据写入Volume 或从 Volume 中读取数据。这使得数据能够在容器的生命周期内持久存在,并且可以被其他容器共享和访问。`

2.hostPath(主机目录)

1. 直接访问主机文件系统:hostPath 卷允许容器直接访问主机节点上的文件系统。这对于需要访问主机节点上的特定文件或目录的应用程序非常有用。例如,某些日志收集器可能需要访问主机上的日志文件。
2. 主机依赖性:hostPath 卷的挂载是与特定主机节点紧密关联的,因此在调度 Pod 时需要特别注意。如果 Pod 被调度到另一个主机节点上,它将无法访问之前挂载的主机文件系统
3. 危险性:hostPath 卷的使用需要谨慎,因为容器可以访问主机节点上的任何文件或目录,包括敏感的系统文件。此外,如果多个 Pod 使用相同的 hostPath 卷并且并发写入同一个文件,可能会导致冲突和数据损坏。
4. 开发和测试环境:hostPath 卷特别适用于开发和测试环境,因为它可以方便地将主机节点上的文件或目录映射到容器中,从而加快开发和测试工作流程。`

3.NFS(远程目录)

1. 基本原理:NFS 允许将存储资源(文件或目录)通过网络共享给其他计算机。NFS 服务器将存储资源公开为共享,并将其挂载到客户端计算机上。客户端可以像访问本地文件系统一样访问这些共享资源。
2. 持久化存储方案:在 Kubernetes 中,NFS 可以作为持久化存储方案使用。它可以提供可靠的存储,并在 pod 被重新调度到不同的节点时保持数据的一致性。
3. 分布式访问:NFS 允许多个客户端同时访问共享资源。这使得它非常适合那些需要多个容器或Pod 共享数据的场景,例如数据库系统或分布式文件共享。
4. 跨平台兼容性:NFS 是一种跨平台的文件系统协议,支持在不同操作系统之间进行文件共享。这使得它成为在混合操作系统集群中实现持久化存储的理想选择。
5. 灵活性:NFS 提供了对存储资源的灵活访问和管理。管理员可以根据需求创建和配置 NFS 服务器,设置共享权限以及实施访问控制策略。
6. 备份和恢复:NFS 具备备份和恢复数据的能力。通过使用备份工具和策略,可以对 NFS 共享的数据进行定期备份,以防止数据丢失或损坏。

4.PVC

K8s PV 是什么?
PersistentVolume(PV):PV 是集群中的存储资源,可以是物理存储设备、网络存储或云存储等。PV 独立于 Pod 存在,它们是集群级别的资源,由集群管理员进行配置和管理。PV 具有持久性,即使 Pod被删除,PV 中的数据也会保留。PV 定义了存储的容量、访问模式和其他属性。
K8s PVC 是什么?
PersistentVolumeClaim(PVC):PVC 是 Pod 对 PV 的申请,它表示 Pod 需要一定的存储资源来满足其持久化存储需求。PVC 在 Pod 和 PV 之间建立了一个抽象层,使得 Pod 无需关心底层存储的具体细节。PVC 定义了 Pod 对存储的需求,包括存储容量、访问模式等。
K8s PVC 和 PV 工作原理
当创建一个 PersistentVolume(PV)和 PersistentVolumeClaim(PVC)时,它们之间的相互作用和工作原理如下:
1. PV 的供应方式:
 • 静态配置:集群管理员预先创建 PV 对象,包含了实际存储资源的详细信息。这些 PV存在于 Kubernetes API 中,供用户使用。
 • 动态配置:当静态 PV 无法匹配用户的 PVC 时,Kubernetes 可以尝试为 PVC 动态地配置 PV。这基于 StorageClass,PVC 必须请求存储类,并且管理员必须创建和配置该类以进行动态配置。
 2. 绑定:
 • 用户创建 PVC 并指定所需的存储资源和访问模式。
 • 在找到可用的 PV 之前,PVC 保持未绑定状态。
 3. 使用:
 • 首先,存储管理员创建 PV,并将其划分为多个存储卷。
 • 用户在创建 Pod 之前创建 PVC,并指定所需的 PV 大小和访问模式。
 • 创建 PVC 后,可以将其作为存储卷使用,并在 Pod 定义中引用该 PVC。
 • PV 和 PVC 是一对一的关系。一旦 PV 被某个 PVC 绑定,其他 PVC 就不能使用它。
 • 如果没有合适的 PV 可用,PVC 将处于挂起状态,直到有一个合适的 PV 可用为止。
 4. 回收策略:
 • 当删除使用 PVC 作为存储卷的 Pod 时,PVC 与 PV 的绑定将解除。
 • PV 有不同的回收策略可供选择:
 • Retain(保留):删除 PVC 后,PV 保留,并且数据仍然存在。PV 不会被绑定到其他 PVC 上,可以再次使用。
 • Recycle(回收,不推荐):删除 PVC 后,PV 会被清空数据并准备重新使用。这个回收策略在 Kubernetes 1.15 版本后被废弃。
 • Delete(删除):删除 PVC 后,PV 也会从 Kubernetes 中移除,并且相关的存储资源也会被删除。`

5.storangclass(存储类)

Kubernetes 提供了 StorageClass 来自动创建 PV,以减轻管理员的维护成本。以下是关于Kubernetes PV、PVC 和 StorageClass 的更新内容:
PV(PersistentVolume)是 Kubernetes 中的持久化存储资源,而 PVC(PersistentVolumeClaim)是 Pod 对 PV 的申请。传统的 PV 和 PVC 模式需要管理员预先创建 PV,然后将其与 PVC 进行一对一的绑定。这种方式在大规模部署中可能导致管理复杂性和维护成本的增加。
为了解决这个问题,Kubernetes 引入了 StorageClass 机制。StorageClass 是一种动态创建 PV 的模板。集群管理员可以创建多个 StorageClass,每个 StorageClass 定义了存储的类型、访问模式和其他属性。
当用户创建一个 PVC 并指定 StorageClass 时,Kubernetes 会自动根据该 StorageClass 创建一个 PV,并将其与 PVC 进行绑定。这样,管理员无需手动创建 PV,而是根据 StorageClass 的定义,动态生成 PV来满足用户的存储需求。
StorageClass 提供了以下好处:
1. 简化管理:StorageClass 允许管理员创建一组可重用的存储配置模板,而无需为每个 PVC 手动创建和管理 PV。这降低了管理成本和复杂性。
2. 动态供应:使用 StorageClass,管理员可以根据需求动态创建 PV,从而自动为 PVC 提供存储资源。这种动态供应的机制使得存储的分配更加灵活和自动化。
3. 可选性和灵活性:StorageClass 允许管理员根据存储的不同特性和需求创建不同的StorageClass。用户可以根据自己的需求选择合适的 StorageClass,并使用相应的存储资源。`

三、实战

1. emptyDir(临时目录)

vim emptyDir.yaml
---
apiVersion: v1
kind: Pod
metadata: 
  name: emptydir
  labels:
    app: emptyDir
spec:
  containers:
  - name: empty
    image: nginx
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: cache
      mountPath: /cache
  volumes:
  - emptyDir: {}
    name: cache
测试:
#查看调度到了那台机器上
kubectl get pods -owide
#查看pod的uid
kubectl get pods  -o yaml | grep uid
#登录到被调度到的节点
cd /var/lib/kubelet/pods/$uid/
cd volumes/kubernetes.io~empty-dir/cache/
在宿主机创建aa目录
mkdir aa
登录pod查看
kubectl exec -it emptydir /bin/sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
# cd cache
# ls
aa
# 
在pod创建bb目录
mkdir bb
在宿主机查看
[root@lx110 cache]# ls
aa  bb
删除pod查看是否目录被清空
kubectl delete pods emptydir 
[root@lx110 cache]# ls
[root@lx110 cache]# 

2.hostPath

vim hostPath.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: hostpath
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: hostpath
      mountPath: /cache-nginx
  - name: tomcat
    image: docker.io/library/tomcat:latest
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: hostpath
      mountPath: /cache-tomcat
  volumes:
  - name: hostpath
    hostPath:
      path: /data
      type: DirectoryOrCreate
测试:
#查看调度到了那台机器
kubectl get pods -owide
#在宿主机目录上创建cc目录
mkdir /data/cc
登录到pod上
kubectl exec -it hostpath -c tomcat /bin/sh
kubectl exec -it hostpath -c nginx /bin/sh
查看/cache-tomcat和/cache-nginx和宿主机/data/目录下一样

type:

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

3.NFS


1、搭建NFS服务(控制节点)
yum install nfs-utils -y
#在宿主机创建 NFS 需要的共享目录
mkdir /data/volumes -pv
  mkdir: created directory '/data'
  mkdir: created directory '/data/volumes'
systemctl start nfs-server
vim /etc/exports
  /data/volumes *(rw,no_root_squash)
exportfs -arv
systemctl restart nfs-server
systemctl enable nfs-server
其余节点安装nfs节点驱动
yum install nfs-utils -y
systemctl start nfs-server
测试连接192.168.1.100/data/volumes
showmount -e 192.168.1.100
挂载
mount -t nfs 192.168.1.100:/data/volumes /data2/
手动卸载
umount /data2
2、编写yaml文件
vim nfs.yaml
---
apiVersion: v1
kind: Pod
metadata:
  name: nfs
spec:
  containers:
  - name: test-nfs
    image: nginx
    imagePullPolicy: IfNotPresent
    ports:
    - containerPort: 80
      protocol: TCP
    volumeMounts:
    - name: nfs-volumes
      mountPath: /usr/share/nginx/html
  volumes:
  - name: nfs-volumes
    nfs:
      path: /data/volumes
      server: 192.168.1.100
3、测试
echo "123" >> /data/volumes/index.html
kubectl get pods -owide
NAME                       READY   STATUS    RESTARTS      AGE    IP              NODE    NOMINATED NODE   READINESS GATES
busybox-6bb56cf754-sx2dt   1/1     Running   8 (30m ago)   4d     10.247.66.155   lx120   <none>           <none>
hostpath                   2/2     Running   0             104m   10.246.71.98    lx110   <none>           <none>
nfs                        1/1     Running   0             25s    10.247.66.157   lx120   <none>           <none>
[root@lx100 test]# curl 10.247.66.157:80
123

4.PVC

静态配置

1、创建nfs共享目录
[root@lx100 test]# mkdir /data/volume_test/v{1,2} -p
[root@lx100 test]# vim /etc/exports
/data/volumes *(rw,no_root_squash)
/data/volume_test/v1 *(rw,no_root_squash)
/data/volume_test/v2 *(rw,no_root_squash)
[root@lx100 test]# exportfs -arv
exporting *:/data/volume_test/v2
exporting *:/data/volume_test/v1
exporting *:/data/volumes
2、创建pv
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: testpv1
  labels:
    app: v1
spec:
  capacity:
    storage: 1Gi
  accessModes: ["ReadWriteOnce"] #单路读写,只允许一个pod去读写
  nfs:
    path: /data/volume_test/v1
    server: 192.168.1.100
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: testpv2
  labels:
    app: v2
spec:
  capacity:
    storage: 2Gi
  accessModes: ["ReadWriteMany"] #多路读写,允许多个pod读写
  nfs:
    path: /data/volume_test/v2
    server: 192.168.1.100
查看pv
kubectl get pv --show-labels #状态为Available为可用状态
3、创建PVC
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: testpvc
  labels:
    app: v1
spec:
  selector:
    matchLabels:
      app: v1
  accessModes: ["ReadWriteOnce"]
  resources:
    requests:
      storage: 1Gi
测试:
[root@lx100 test]# kubectl get pv
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM             STORAGECLASS   REASON   AGE
testpv1   1Gi        RWO            Retain           Bound       default/testpvc                           10m
testpv2   2Gi        RWX            Retain           Available                                             10m
[root@lx100 test]# kubectl get pvc
NAME      STATUS   VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE
testpvc   Bound    testpv1   1Gi        RWO                           35s
创建Pod挂载pvc
---
apiVersion: v1
kind: Pod
metadata:
  name: pvctest1
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: pvc
      mountPath: /usr/share/nginx/html
  volumes:
  - persistentVolumeClaim:
      claimName: testpvc
    name: pvc
测试
#查看pod信息
kubectl get pods -owide
NAME                       READY   STATUS    RESTARTS      AGE    IP              NODE    NOMINATED NODE   READINESS GATES
busybox-6bb56cf754-sx2dt   1/1     Running   9 (41m ago)   4d1h   10.247.66.155   lx120   <none>           <none>
hostpath                   2/2     Running   0             175m   10.246.71.98    lx110   <none>           <none>
nfs                        1/1     Running   0             71m    10.247.66.157   lx120   <none>           <none>
pvctest1                   1/1     Running   0             113s   10.247.66.158   lx120   <none>           <none>
[root@lx100 test]# echo 2355555 >> /data/volume_test/v1/index.html
[root@lx100 test]# curl 10.247.66.158 
2355555

storangeclass(存储类)

安装nfs provisioner,用于配合存储类动态生成pv
参考:https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner
上传nfs-subdir-external-provisioner.tar.gz并导入镜像
ctr -n=k8s.io image import nfs-subdir-external-provisioner.tar.gz
#创建运行nfs-provisioner需要的SA
vim serviceaccount.yaml 
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-provisioner
#对sa授权
kubectl create clusterrolebinding nfs-provisioner --clusterrole=cluster-admin --serviceaccount=default:nfs-provisioner

#安装nfs-provisioner程序
mkdir /data/nfs_pro -p
vim /etc/exports
/data/volumes *(rw,no_root_squash)
/data/volume_test/v1 *(rw,no_root_squash)
/data/volume_test/v2 *(rw,no_root_squash)
/data/nfs_pro *(rw,no_root_squash)
exportfs -arv
vim nfs-deployment.yaml
---
kind: Deployment
apiVersion: apps/v1
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
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: nfs-client-root
          mountPath: /persistentvolumes
        env:
        - name: PROVISIONER_NAME
          value: example.com/nfs
        - name: NFS_SERVER
          value: 192.168.1.20
        - name: NFS_PATH
          value: /data/nfs_pro
      volumes:
      - name: nfs-client-root
        nfs:
          server: 192.168.1.20
          path: /data/nfs_pro
#创建storageclass,动态供给PV
vim nfs-storageclass.yaml
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs
provisioner: example.com/nfs  #应该与上面deployment文件中的env- name: PROVISIONER_NAME值一致
kubectl apply -f nfs-storageclass.yaml
#查看storageclass是否被创建
kubectl get sc
NAME   PROVISIONER       RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
nfs    example.com/nfs   Delete          Immediate           false                  14m
vim claim.yaml
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: test-claim1
spec:
  accessModes: ["ReadWriteMany"]
  resources:
    requests:
      storage: 1Gi
  storageClassName: nfs
[root@lx20 k8s]# kubectl get pvc
NAME          STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
test-claim1   Bound    pvc-ebdb2ca5-e18f-489e-92a4-ff938a4a310b   1Gi        RWX            nfs            13m
vim read-pod.yaml
---
kind: Pod
apiVersion: v1
metadata:
  name: podtestpvc
spec:
  containers:
  - name: pod
    image: nginx
    imagePullPolicy: IfNotPresent
    volumeMounts:
      - name: nfs-pvc
        mountPath: /usr/share/nginx/html
  restartPolicy: "Never"
  volumes:
  - name: nfs-pvc
    persistentVolumeClaim:
      claimName: test-claim1
kubectl get pods
echo liangxi >> /data/nfs_pro/default-test-claim1-pvc-ebdb2ca5-e18f-489e-92a4-ff938a4a310b/index.html
kubectl get pods -owide
curl http://10.243.166.13
liangxi
#查看详细信息
kubectl describe pod podtestpvc
Volumes:
  nfs-pvc:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  test-claim1
    ReadOnly:   false