• k8s存储:卷、持久卷、存储类


    容器中的文件是临时存储在磁盘上的,数据卷的引入主要是为了解决以下几个问题:

    • 当容器升级或者崩溃时,kubelet会重建容器,容器内原有的文件会丢失;
    • 一个Pod中运行的多个容器需要共享文件。

    常用的数据卷(Volume)有:

    • 本地:例如hostPath、emptyDir;
    • 网络:例如NFS、Ceph、GlusterFS;
    • 公有云:例如AWS EBS;
    • k8s资源:例如configmap、secret。

    emptyDir:临时数据卷

    emptyDir卷是一种临时存储卷,与Pod的生命周期绑定。在Pod运行期间,emptyDir一直存在;如果Pod被删除了,emptyDir中的数据也会被永久删除。

    :容器崩溃会导致Pod被删除,因此容器崩溃期间emptyDir中的数据是安全的。

    emptyDir可以实现Pod中容器之间的数据共享。尽管Pod中的容器挂载emptyDir卷的路径可能不同,这些容器都可以读写emptyDir卷中相同的文件。

    emptyDir的配置示例如下:

    apiVersion: v1
    kind: Pod
    metadata:
      name: test-pod
    spec:
      containers:
      - image: centos
        name: writer
    	command: ["bash","-c","for i in {1..100}; do echo $i >> /data/tmpfile; sleep 1; done"]
        volumeMounts:
        - mountPath: /data
          name: data
    	  
      - image: centos
        name: reader
    	command: ["bash", "-c", "tail -f /data/tmpfile"]
    	volumeMounts:
    	  - mountPath: /data
    	 
      volumes:
      - name: data
        emptyDir: {}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    emptyDir挂在的容器目录位于Pod所在节点的以下路径:

    /var/lib/kubelet/pods/${container_ID}/volumes/kubernetes.io~empty-dir
    
    • 1

    hostPath:节点数据卷

    hostPath卷能将Pod所在宿主机节点文件系统上的文件或目录挂载到Pod中。具有相同配置(例如基于同一PodTemplate创建)的多个Pod,会由于节点上文件的不同,而在不同节点上有不同的行为。

    :HostPath卷存在许多安全风险,最佳做法是尽可能避免使用HostPath。 当必须使用HostPath卷时,它的范围应仅限于所需的文件或目录,并以只读方式挂载。HostPath卷可能会暴露特权系统凭据(例如Kubelet)或特权API(例如容器运行时套接字),可用于容器逃逸或攻击集群的其他部分。

    HostPath配置示例如下:

    apiVersion: v1
    kind: Pod
    metadata:
      name: test-pod
    spec:
      containers:
      - image: k8s.gcr.io/test-webserver
        name: test-container
        volumeMounts:
        - mountPath: /mydata
          name: test-volume1
    	- mountPath: /mylog
          name: test-volume2
      volumes:
      - name: test-volume1
        hostPath:
          path: /data       # 宿主上目录位置
          type: Directory 
      - name: test-volume2
        hostPath:
          path: /log        # 宿主上目录位置
          type: Directory 	  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    nfs:网络数据卷

    提供对NFS挂载支持,可以自动将NFS共享路径挂载到Pod中。

    搭建NFS文件共享服务器:

    [root@k8s-node1 ~]# yum install nfs-utils -y
    [root@k8s-node1 ~]# vi /etc/exports
    /ifs/kubernetes *(rw,sync,no_root_squash)
    [root@k8s-node1 ~]# mkdir -p /ifs/kubernetes
    [root@k8s-node1 ~]# systemctl start nfs
    [root@k8s-node1 ~]# systemctl enable nfs
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    所有Node节点上都要安装nfs-utils包。在其他节点挂载NFS路径:

    [root@k8s-node2 ~]# yum install nfs-utils -y
    [root@k8s-node2 ~]# mkdir -p /ifs/kubernetes 
    #手动挂载
    [root@k8s-node2 ~]# mount -t nfs 192.168.136.120:/ifs/kubernetes /ifs/kubernetes
    #开机自动挂载
    [root@k8s-node2 ~]# echo '192.168.136.120:/ifs/kubernetes  /ifs/kubernetes  nfs4  defaults  0 0' >> /etc/fstab
    [root@k8s-node2 ~]# mount -a
    #移除挂载
    [root@k8s-node2 ~]# umount -v /ifs/kubernetes/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    将nginx网站程序根目录持久化到NFS存储,为多个Pod提供网站程序文件。

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: webserver
    spec:
      selector:
        matchLabels:
    	  app: nginx
      replicas: 3
      template:
        metadata:
    	  labels:
    	    app: nginx
    	spec:
    	  containers:
    	  - name: nginx
    	    image: nginx
    		volumeMounts:
    		- name: wwwroot
    		  mountPath: /usr/share/nginx/html
    	  volumes:
    	  - name: wwwroot
    	    nfs:
    		  server: 192.168.136.120
    		  path: /ifs/kubernetes
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    PV:持久卷

    持久卷(PersistentVolume, PV)是对存储资源创建和使用的抽象,使得存储可以作为集群中的资源被管理。

    持久卷的类型

    持久卷是通过插件的形式实现的。目前k8s支持的插件包括:

    • awsElasticBlockStore
    • azureDisk
    • cephfs
    • csi 容器存储接口
    • hostPath卷
    • iscsi(iSCSI存储)
    • local 节点上挂载的本地存储设备
    • nfs 网络文件系统存储
    • vsphereVolume(vSphere VMDK卷)

    等等。

    volumeMode:卷模式

    k8s支持两种卷模式(volumeModes):Filesystem(文件系统)和Block(块存储)。volumeMode是一个可选的API参数。如果该参数被省略,默认的卷模式是Filesystem。

    volumeMode属性设置为Filesystem的卷会被Pod挂载到某个目录。如果卷的存储来自某块设备而该设备目前为空,k8s会在第一次挂载卷之前在设备上创建文件系统。

    也可以将volumeMode设置为Block,以便将卷作为原始块设备来使用。这类卷以块设备的方式交给Pod使用,其上没有任何文件系统,使用前需要格式化。

    accessModes:访问模式

    PV支持的访问模式(accessModes)有:

    • ReadWriteOnce(RWO):卷可以被一个节点以读写方式挂载。ReadWriteOnce访问模式也允许运行在同一节点上的多个 Pod 访问卷。

    • ReadOnlyMany(ROX):卷可以被多个节点以只读方式挂载。

    • ReadWriteMany(RWX):卷可以被多个节点以读写方式挂载。

    • ReadWriteOncePod:卷可以被单个Pod以读写方式挂载。
      如果想确保整个集群中只有一个Pod可以读取或写入该PVC, 要使用ReadWriteOncePod访问模式。这只支持CSI卷以及需要Kubernetes 1.22以上版本。

    参考:https://kubernetes.io/blog/2021/09/13/read-write-once-pod-access-mode-alpha/

    Phase:状态阶段

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

    • Available(可用):卷是一个空闲资源,尚未绑定到任何申领;
    • Bound(已绑定):该卷已经绑定到某申领;
    • Released(已释放):所绑定的申领已被删除,但是资源尚未被集群回收;
    • Failed(失败):卷的自动回收操作失败。

    Reclaim Policy:回收策略

    目前的回收策略有:

    • Retain(手动回收):默认策略,保留数据,需要手动回收;
    • Recycle(回收):保留PV,但是会清除PV中的数据,相当于rm -rf /thevolume/*
    • Delete(删除):删除PV和数据,与PV相关联的后端存储同时被删除。诸如AWS EBS、GCE PD、Azure Disk或OpenStack Cinder卷这类关联存储资源也被删除。

    PVC:持久卷申领

    持久卷申领(PersistentVolumeClaim, PVC)是用户对集群中PV资源的请求。

    Pod申请PVC作为卷来使用,k8s通过PVC查找绑定的PV,并在Pod中挂载。创建PVC之后,k8s控制平面将通过匹配访问模式accessModes和容量storage查找满足要求的PV。如果控制平面找到具有合适的PV,则会将PVC绑定到该PV上。

    PVC和PV的一般匹配原则如下:

    • 首先,PV.spec.accessModes要与PVC.spec.accessModes相同;
    • 其次,PV.spec.capacity.storage不能小于PVC.spec.resources.requests.storage
    • 如果有多个PV满足以上要求,则优先选择容量最接近的PV;
    • 如果没有找到合适的PV,则容器和PVC会一直处于Pending状态。

    PVC中的需求容量字段storage只能用于匹配PV,并不能起到资源限制作用。容器实际能够使用的容量大小取决于后端存储。也就是说,假设PVC中定义的资源需求为10G,PV后端使用的存储的剩余空间为100G,那么绑定该PV后,容器实际可以使用的存储大小也是100G。

    示例:创建一个nfs类型的PV

    首先确保所有Node节点操作系统已经挂载NFS。

    容器应用中使用PVC来申请PV资源(my-pod-pvc.yaml):

    ---
    apiVersion: v1
    kind: Pod
    metadata:
      name: my-pod
    spec:
      containers:
      - name: webserver
        image: nginx
    	ports:
    	- containerPort: 80
    	volumeMounts:
    	- name: wwwroot
    	  mountPath: /usr/share/nginx/html
      volumes:
      - name: wwwroot           #与volumeMounts.name保持一致
        persistentVolumeClaim:
    	  claimName: my-pvc
    	  
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata: 
      name: my-pvc       #与claimName保持一致
    spec:
      accessModes:       #访问模式
        - ReadWriteMany
      resources:
        requests:
    	  storage: 8Gi   #容量
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    查看Pods和PVC状态:

    [root@k8s-master ~]# kubectl apply -f my-pod-pvc.yaml
    [root@k8s-master ~]# kubectl get pods,pvc
    NAME                             READY   STATUS    RESTARTS        AGE
    pod/my-pod                       0/1     Pending   0               23s
    
    NAME                           STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    persistentvolumeclaim/my-pvc   Pending                                                     23s
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    由于尚未创建PV,my-pod和my-pvc的STATUS都应该为Pending

    接下来定义一个nfs类型的PV资源(my-pv.yaml):

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: my-pv
    spec:
      capacity:
        storage: 10Gi       #容量
      accessModes:
        - ReadWriteMany     #访问模式
      nfs:
        path: /if/kubernetes
    	server: 192.168.136.120
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    查看Pods、PVC和PV状态:

    [root@k8s-master ~]# kubectl apply -f my-pv.yaml
    
    #PV和PVC未绑定前
    [root@k8s-master ~]# kubectl get pv
    NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
    my-pv   10Gi       RWX            Retain           Available                                   2m20s
    
    #PV和PVC绑定后
    [root@k8s-master ~]# kubectl get pods,pvc,pv
    NAME                             READY   STATUS    RESTARTS        AGE
    pod/my-pod                       1/1     Running   0               3m8s
    
    NAME                           STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    persistentvolumeclaim/my-pvc   Bound    my-pv    1Gi        RWX                           3m8s
    
    NAME                     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM            STORAGECLASS   REASON   AGE
    persistentvolume/my-pv   10Gi       RWX            Retain           Bound    default/my-pvc                           3m2s
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    正常情况下,my-pvc和my-pv的STATUS都会变成Bound,Pods的状态也会变成Running。

    进入容器测试一下:

    [root@k8s-node1 ~]# ls /ifs/kubernetes/
    a.txt  b.txt
    
    [root@k8s-master ~]# kubectl exec -it my-pod -- bash
    root@my-pod:/# cd /usr/share/nginx/html
    root@my-pod:/usr/share/nginx/html# ls
    a.txt  b.txt
    root@my-pod:/usr/share/nginx/html# echo '

    Hello Nginx!

    ' > index.html
    root@my-pod:/usr/share/nginx/html# ls a.txt b.txt index.html
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    StorageClass:存储类

    PV的供给有两种方式:静态供给和动态供给。像前面那样,集群管理员手动创建若干PV卷,供容器消费使用,即是静态供给的方式。静态供给的缺点是维护成本很高。

    集群管理员也可以使用StorageClass实现PV资源的动态供给。每个StorageClass都包含provisioner、parameters和reclaimPolicy字段, 这些字段会在StorageClass需要动态分配PV时会使用到。

    Provisioner:存储类制备器

    每个存储类都有一个provisioner,用来决定使用哪个卷插件制备PV。

    目前,NFS没有内部制备器,需要使用外部制备器插件来实现PV的动态供给。

    官方内部支持的制备器参见:https://kubernetes.io/zh-cn/docs/concepts/storage/storage-classes/#provisioner
    NFS外部制备器插件参考:https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner

    NFS provisioner插件部署:

    [root@k8s-master nfs-storageClass]# ls
    class.yaml  deployment.yaml  rbac.yaml
    #修改NFS服务器地址和共享文件路径
    [root@k8s-master nfs-storageClass]# grep NFS deployment.yaml
                - name: NFS_SERVER
                - name: NFS_PATH
    
    #授权访问api-server
    [root@k8s-master nfs-storageClass]# kubectl apply -f rbac.yaml
    #部署nfs provisioner插件
    [root@k8s-master nfs-storageClass]# kubectl apply -f deployment.yaml
    #创建存储类
    [root@k8s-master nfs-storageClass]# kubectl apply -f class.yaml
    
    #查看存储类
    [root@k8s-master nfs-storageClass]# kubectl get sc
    NAME                  PROVISIONER                                   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
    managed-nfs-storage   k8s-sigs.io/nfs-subdir-external-provisioner   Delete          Immediate           false                  2m3s
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    :部署配置文件下载地址为 https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner/tree/master/deploy

    NFS StorageClass动态供给PV

    修改前面的示例,使用nfs存储类来实现动态供给PV卷(pod-scnfs.yaml)。

    ---
    apiVersion: v1
    kind: Pod
    metadata:
      name: pod-sc-nfs
    spec:
      containers:
      - name: pod-sc-nfs
        image: nginx
    	ports:
    	- containerPort: 80
    	volumeMounts:
    	- name: sc-nfs-pvc
    	  mountPath: /usr/share/nginx/html
      volumes:
      - name: sc-nfs-pvc           #与volumeMounts.name保持一致
        persistentVolumeClaim:
    	  claimName: test-nfs-scclaim
    	  
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata: 
      name: test-nfs-scclaim       #与claimName保持一致
    spec:
      storageClassName: "managed-nfs-storage"   #与部署nfs provisioner插件时class.yaml中metadata.name保持一致
      accessModes:       #访问模式
        - ReadWriteMany
      resources:
        requests:
    	  storage: 1Gi   #容量
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    无需手动创建PV,检查部署后状态:

    [root@k8s-master ~]# kubectl apply -f pod-scnfs.yaml
    [root@k8s-master ~]# kubectl get pods
    NAME                                      READY   STATUS    RESTARTS        AGE
    my-pod                                    1/1     Running   0               5h40m
    nfs-client-provisioner-5d5775b9bb-5657c   1/1     Running   0               21m
    pod-sc-nfs                                1/1     Running   0               24s
    [root@k8s-master ~]#
    [root@k8s-master ~]# kubectl get pvc,pv
    NAME                                     STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
    persistentvolumeclaim/my-pvc             Bound    my-pv                                      1Gi        RWX                                  5h41m
    persistentvolumeclaim/test-nfs-scclaim   Bound    pvc-14c4221b-f3af-465c-b431-eddc1191a864   1Gi        RWX            managed-nfs-storage   33s
    
    NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                      STORAGECLASS          REASON   AGE
    persistentvolume/my-pv                                      1Gi        RWX            Retain           Bound    default/my-pvc                                            5h41m
    persistentvolume/pvc-14c4221b-f3af-465c-b431-eddc1191a864   1Gi        RWX            Delete           Bound    default/test-nfs-scclaim   managed-nfs-storage            33s
    [root@k8s-master ~]#
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    可以看到,动态分配的PV的STORAGECLASS一栏为managed-nfs-storage,回收策略为Delete

    查看NFS Server上的共享文件路径:

    [root@k8s-master ~]# kubectl exec -it pod-sc-nfs -- bash
    root@pod-sc-nfs:/# df -Th
    Filesystem                                                                                        Type     Size  Used Avail Use% Mounted on
    ...
    192.168.136.120:/ifs/kubernetes/default-test-nfs-scclaim-pvc-14c4221b-f3af-465c-b431-eddc1191a864 nfs4      37G  3.7G   34G  10% /usr/share/nginx/html
    ...
    root@pod-sc-nfs:/# cd /usr/share/nginx/html
    root@pod-sc-nfs:/usr/share/nginx/html# ls
    root@pod-sc-nfs:/usr/share/nginx/html# touch test-nfs-file-001
    root@pod-sc-nfs:/usr/share/nginx/html# exit
    
    [root@k8s-node1 ~]# ll /ifs/kubernetes/
    total 4
    -rw-r--r--. 1 root root  0 Aug 12 23:26 a.txt
    -rw-r--r--. 1 root root  0 Aug 12 23:29 b.txt
    drwxrwxrwx. 2 root root  6 Aug 13 05:23 default-test-nfs-scclaim-pvc-14c4221b-f3af-465c-b431-eddc1191a864
    -rw-r--r--. 1 root root 20 Aug 12 23:49 index.html
    [root@k8s-node1 ~]# ls /ifs/kubernetes/default-test-nfs-scclaim-pvc-14c4221b-f3af-465c-b431-eddc1191a864/
    test-nfs-file-001
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    测试Delete回收策略是否会生效:

    [root@k8s-master ~]# kubectl delete -f pod-scnfs.yaml
    pod "pod-sc-nfs" deleted
    persistentvolumeclaim "test-nfs-scclaim" deleted
    
    #查看绑定的PV是否一并被删除
    [root@k8s-master ~]# kubectl get pvc,pv,sc
    NAME                           STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    persistentvolumeclaim/my-pvc   Bound    my-pv    1Gi        RWX                           5h55m
    
    NAME                     CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM            STORAGECLASS   REASON   AGE
    persistentvolume/my-pv   1Gi        RWX            Retain           Bound    default/my-pvc                           5h55m
    
    NAME                                              PROVISIONER                                   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
    storageclass.storage.k8s.io/managed-nfs-storage   k8s-sigs.io/nfs-subdir-external-provisioner   Delete          Immediate           false                  36m
    
    #查看NFS Server上的共享文件路径是否同时被删除
    [root@k8s-node1 ~]# ls /ifs/kubernetes/
    a.txt  b.txt  index.html
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    如果想在删除PVC时,对PV中的数据进行归档,可以将nfs provisioner插件部署配置文件class.yaml中的参数

    archiveOnDelete: "false"
    
    • 1

    修改为true后重新部署。这样NFS Server上共享文件路径中的数据就会留存一份归档。

    References
    【1】https://kubernetes.io/zh-cn/docs/concepts/storage/volumes/
    【2】https://kubernetes.io/zh-cn/docs/concepts/storage/persistent-volumes/
    【3】https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/configure-persistent-volume-storage/
    【4】https://kubernetes.io/zh-cn/docs/concepts/storage/storage-classes/

  • 相关阅读:
    MySQL——练习
    Pinia 是否可以代替 Vuex ?
    SQL 2008 R2 和vCenter 5.1安装步骤与AQ
    电缆故障测试仪是什么 都有哪些功能和特点--TFN FB11
    你是怎么分辨数据库 Htap 能力强弱的
    聊聊工作中,如何提升自己的编程能力?
    BSPHP 未授权访问 信息泄露
    多媒体应用设计师 第7章 多媒体数字压缩编码技术基础
    数据结构——排序算法——希尔排序
    RocketMQ源码分析(九)之AllocateMappedFileService
  • 原文地址:https://blog.csdn.net/Sebastien23/article/details/126276294