Kubernetes 3种存储傻傻分不清楚!

存储 存储软件
Kubernetes支持几十种类型的后端存储卷,其中有几种存储卷总是给人一种分不清楚它们之间有什么区别的感觉,尤其是local与hostPath这两种存储卷类型,看上去都像是node本地存储方案嘛。当然,还另有一种volume类型是emptyDir,也有相近之处。

 Kubernetes支持几十种类型的后端存储卷,其中有几种存储卷总是给人一种分不清楚它们之间有什么区别的感觉,尤其是local与hostPath这两种存储卷类型,看上去都像是node本地存储方案嘛。当然,还另有一种volume类型是emptyDir,也有相近之处。

在Docker容器时代,我们就对Volume很熟悉了,一般来说我们是通过创建Volume数据卷,然后挂载到指定容器的指定路径下,以实现容器数据的持久化存储或者是多容器间的数据共享,当然这里说的都是单机版的容器解决方案。

[[252662]]

进入到容器集群时代后,我们看到Kubernetes按时间顺序先后提供了emptyDir、hostPath和local的本地磁盘存储卷解决方案。

emptyDir、hostPath都是Kubernetes很早就实现和支持了的技术,local volume方式则是从k8s v1.7才刚刚发布的alpha版本,目前在k8s v1.10中发布了local volume的beta版本,部分功能在早期版本中并不支持。

在展开之前,我们先讨论一个问题,就是既然都已经实现容器云平台了,我们为什么还要关注这几款本地存储卷的货呢?

粗略归纳了下,有以下几个原因:

  • 特殊使用场景需求,如需要个临时存储空间,运行cAdvisor需要能访问到node节点/sys/fs/cgroup的数据,做本机单节点的k8s环境功能测试等等。
  • 容器集群只是做小规模部署,满足开发测试、集成测试需求。
  • 作为分布式存储服务的一种补充手段,比如我在一台node主机上插了块SSD,准备给某个容器吃小灶。
  • 目前主流的两个容器集群存储解决方案是ceph和glusterfs,二者都是典型的网络分布式存储,所有的数据读、写都是对磁盘IO和网络IO的考验,所以部署存储集群时至少要使用万兆的光纤网卡和光纤交换机。如果你都没有这些硬货的话,强上分布式存储方案的结果就是收获一个以”慢动作”见长的容器集群啦。
  • 分布式存储集群服务的规划、部署和长期的监控、扩容与运行维护是专业性很强的工作,需要有专职的技术人员做长期的技术建设投入。

我们并不是说分布式存储服务不好,很多公司在云平台建设的实践中,往往是需要结合使用几种通用的与专用的存储解决方案,才能最终满足大部分的使用需求。

所以,如果这里有一款场景适合你的话,不妨了解一下这几款本地存储卷的功能特点、使用技巧与异同。

1、emptyDir

emptyDir类型的Volume在Pod分配到Node上时被创建,Kubernetes会在Node上自动分配一个目录,因此无需指定宿主机Node上对应的目录文件。 这个目录的初始内容为空,当Pod从Node上移除时,emptyDir中的数据会被***删除。

注:容器的crashing事件并不会导致emptyDir中的数据被删除。

***实践

根据官方给出的***使用实践的建议,emptyDir可以在以下几种场景下使用:

  1. apiVersion: v1 
  2. kind: Pod 
  3. metadata: 
  4.  name: test-pod 
  5. spec: 
  6.  containers: 
  7.  - image: busybox 
  8.    name: test-emptydir 
  9.    command: [ "sleep""3600" ] 
  10.    volumeMounts: 
  11.    - mountPath: /data 
  12.      name: data-volume 
  13.  volumes: 
  14.  - name: data-volume 
  15.    emptyDir: {} 

查看下创建出来的pod,这里只截取了与volume有关的部分,其他无关内容直接省略:

  1. # kubectl describe pod test-pod 
  2. Name:         test-pod 
  3. Namespace:    default 
  4. Node:         kube-node2/172.16.10.102 
  5. ...... 
  6.    Environment:    <none> 
  7.    Mounts: 
  8.      /data from data-volume (rw) 
  9. ...... 
  10. Volumes: 
  11.  data-volume: 
  12.    Type:    EmptyDir (a temporary directory that shares a pod's lifetime) 
  13.    Medium: 
  14. ...... 

可以进入到容器中查看下实际的卷挂载结果:

  1. # kubectl exec -it test-pod -c test-emptydir /bin/sh 

2、hostPath

hostPath类型则是映射node文件系统中的文件或者目录到pod里。在使用hostPath类型的存储卷时,也可以设置type字段,支持的类型有文件、目录、File、Socket、CharDevice和BlockDevice。

下面是来自官网对hostPath的使用场景和注意事项的介绍。

使用场景:

  • 当运行的容器需要访问Docker内部结构时,如使用hostPath映射/var/lib/docker到容器;
  • 当在容器中运行cAdvisor时,可以使用hostPath映射/dev/cgroups到容器中;
  • 注意事项:
  • 配置相同的pod(如通过podTemplate创建),可能在不同的Node上表现不同,因为不同节点上映射的文件内容不同
  • 当Kubernetes增加了资源敏感的调度程序,hostPath使用的资源不会被计算在内
  • 宿主机下创建的目录只有root有写权限。你需要让你的程序运行在privileged container上,或者修改宿主机上的文件权限。

hostPath volume 实验

下面我们在测试k8s环境中创建一个hostPath volume使用示例。

  1. apiVersion: v1 
  2. kind: Pod 
  3. metadata: 
  4.  name: test-pod2 
  5. spec: 
  6.  containers: 
  7.  - image: busybox 
  8.    name: test-hostpath 
  9.    command: [ "sleep""3600" ] 
  10.    volumeMounts: 
  11.    - mountPath: /test-data 
  12.      name: test-volume 
  13.  volumes: 
  14.  - name: test-volume 
  15.    hostPath: 
  16.      # directory location on host 
  17.      path: /data 
  18.      # this field is optional 
  19.      type: Directory 

查看下pod创建结果,观察volumes部分:

  1. # kubectl describe pod test-pod2 
  2. Name:         test-pod2 
  3. Namespace:    default 
  4. Node:         kube-node2/172.16.10.102 
  5. ...... 
  6.    Mounts: 
  7.      /test-data from test-volume (rw) 
  8. ...... 
  9. Volumes: 
  10.  test-volume: 
  11.    Type:          HostPath (bare host directory volume) 
  12.    Path:          /data 
  13.    HostPathType:  Directory 
  14. ...... 

我们登录到容器中,进入挂载的/test-data目录中,创建个测试文件。

  1. # kubectl exec  -it test-pod2 -c test-hostpath /bin/sh 
  2. / # echo 'testtesttest' > /test-data/test.log 
  3. / # exit 

我们在运行该pod的node节点上,可以看到如下的文件和内容。

  1. [root@kube-node2 test-data]# cat /test-data/test.log 
  2. testtesttest 

现在,我们把该pod删除掉,再看看node节点上的hostPath使用的目录与数据会有什么变化。

  1. [root@kube-node1 ~]# kubectl delete pod test-pod2 
  2. pod "test-pod2" deleted 

到运行原pod的node节点上查看如下。

  1. [root@kube-node2 test-data]# ls -l 
  2. total 4 
  3. -rw-r--r-- 1 root root 13 Nov 14 00:25 test.log 
  4. [root@kube-node2 test-data]# cat /test-data/test.log 
  5. testtesttest 

在使用hostPath volume卷时,即便pod已经被删除了,volume卷中的数据还在!

单节点的k8s本地测试环境与hostPath volume

有时我们需要搭建一个单节点的k8s测试环境,就利用到hostPath作为后端的存储卷,模拟真实环境提供PV、StorageClass和PVC的管理功能支持。

  1. apiVersion: storage.k8s.io/v1 
  2. kind: StorageClass 
  3. metadata: 
  4.  namespace: kube-system 
  5.  name: standard 
  6.  annotations: 
  7.    storageclass.beta.kubernetes.io/is-default-class: "true" 
  8.  labels: 
  9.    addonmanager.kubernetes.io/mode: Reconcile 
  10. provisioner: kubernetes.io/host-path 
  11. ​ 

该场景仅能用于单节点的k8s测试环境中

3、emptyDir和hostPath在功能上的异同分析

二者都是node节点的本地存储卷方式;

  • emptyDir可以选择把数据存到tmpfs类型的本地文件系统中去,hostPath并不支持这一点;
  • hostPath除了支持挂载目录外,还支持File、Socket、CharDevice和BlockDevice,既支持把已有的文件和目录挂载到容器中,也提供了“如果文件或目录不存在,就创建一个”的功能;
  • emptyDir是临时存储空间,完全不提供持久化支持;
  • hostPath的卷数据是持久化在node节点的文件系统中的,即便pod已经被删除了,volume卷中的数据还会留存在node节点上;

4、local volume的概念

这是一个很新的存储类型,建议在k8s v1.10+以上的版本中使用。该local volume类型目前还只是beta版。

Local volume 允许用户通过标准PVC接口以简单且可移植的方式访问node节点的本地存储。 PV的定义中需要包含描述节点亲和性的信息,k8s系统则使用该信息将容器调度到正确的node节点。

配置要求

  • 使用local-volume插件时,要求使用到了存储设备名或路径都相对固定,不会随着系统重启或增加、减少磁盘而发生变化。
  • 静态provisioner配置程序仅支持发现和管理挂载点(对于Filesystem模式存储卷)或符号链接(对于块设备模式存储卷)。 对于基于本地目录的存储卷,必须将它们通过bind-mounted的方式绑定到发现目录中。

StorageClass与延迟绑定

官方推荐在使用local volumes时,创建一个StorageClass并把volumeBindingMode字段设置为“WaitForFirstConsumer”。

虽然local volumes还不支持动态的provisioning管理功能,但我们仍然可以创建一个StorageClass并使用延迟卷绑定的功能,将volume binding延迟至pod scheduling阶段执行。

这样可以确保PersistentVolumeClaim绑定策略将Pod可能具有的任何其他node节点约束也进行评估,例如节点资源要求、节点选择器、Pod亲和性和Pod反亲和性。

  1. kind: StorageClass 
  2. apiVersion: storage.k8s.io/v1 
  3. metadata: 
  4.  namelocal-storage 
  5. provisioner: kubernetes.io/no-provisioner 
  6. volumeBindingMode: WaitForFirstConsumer  

外部static provisioner

配置local volume后,可以使用一个外部的静态配置器来帮助简化本地存储的管理。 Provisioner 配置程序将通过为每个卷创建和清理PersistentVolumes来管理发现目录下的卷。

Local storage provisioner要求管理员在每个节点上预配置好local volumes,并指明该local volume是属于以下哪种类型:

  • Filesystem volumeMode (default) PVs – 需要挂载到发现目录下面。
  • Block volumeMode PVs – 需要在发现目录下创建一个指向节点上的块设备的符号链接。

一个local volume,可以是挂载到node本地的磁盘、磁盘分区或目录。

Local volumes虽然可以支持创建静态PersistentVolume,但到目前为止仍不支持动态的PV资源管理。

这意味着,你需要自己手动去处理部分PV管理的工作,但考虑到至少省去了在创建pod时手动定义和使用PV的工作,这个功能还是很值得的。

创建基于Local volumes的PV的示例

  1. apiVersion: v1 
  2. kind: PersistentVolume 
  3. metadata: 
  4.  name: example-pv 
  5. spec: 
  6.  capacity: 
  7.    storage: 100Gi 
  8.  volumeMode: Filesystem 
  9.  accessModes: 
  10.  - ReadWriteOnce 
  11.  persistentVolumeReclaimPolicy: Delete 
  12.  storageClassName: local-storage 
  13.  local
  14.    path: /mnt/disks/ssd1 
  15.  nodeAffinity: 
  16.    required: 
  17.      nodeSelectorTerms: 
  18.      - matchExpressions: 
  19.        - key: kubernetes.io/hostname 
  20.          operator: In 
  21.          values
  22.          - example-node 
  23. ​ 
  • nodeAffinity字段是必须配置的,k8s依赖于这个标签为你定义的Pods在正确的nodes节点上找到需要使用的local volumes。
  • 使用volumeMode字段时,需要启用BlockVolume 这一Alpha feature特性。
  • volumeMode字段的默认值是Filesystem,但也支持配置为Block,这样就会把node节点的local volume作为容器的一个裸块设备挂载使用。

数据安全风险

local volume仍受node节点可用性方面的限制,因此并不适用于所有应用程序。 如果node节点变得不健康,则local volume也将变得不可访问,使用这个local volume的Pod也将无法运行。 使用local voluems的应用程序必须能够容忍这种降低的可用性以及潜在的数据丢失,是否会真得导致这个后果将取决于node节点底层磁盘存储与数据保护的具体实现了。

5、hostPath与local volume在功能上的异同分析

二者都基于node节点本地存储资源实现了容器内数据的持久化功能,都为某些特殊场景下提供了更为适用的存储解决方案;

前者时间很久了,所以功能稳定,而后者因为年轻,所以功能的可靠性与稳定性还需要经历时间和案例的历练,尤其是对Block设备的支持还只是alpha版本;

二者都为k8s存储管理提供了PV、PVC和StorageClass的方法实现;

  • local volume实现的StorageClass不具备完整功能,目前只支持卷的延迟绑定;
  • hostPath是单节点的本地存储卷方案,不提供任何基于node节点亲和性的pod调度管理支持;
  • local volume适用于小规模的、多节点的k8s开发或测试环境,尤其是在不具备一套安全、可靠且性能有保证的存储集群服务时;

6、local volume的安装配置方法

local-volume项目及地址

https://github.com/kubernetes-incubator/external-storage/tree/master/local-volume

Step 1:配置k8s集群使用本地磁盘存储

如果使用block块设备,则需要启用Alpha的功能特性:k8s v1.10+

  1. $ export KUBE_FEATURE_GATES=”BlockVolume=true” 

注:如果是已经部署好的k8s v1.10+集群,需要为几个主要组件均开启对该特性的支持后,才能使用block块设备功能。如果k8s是低于1.10版本,则还需要启用其它的几个功能特性,因为在低版本中这些功能特性还都是alpha版本的。

根据大家搭建k8s的方法的不同,下面提供了四种情况下的配置说明。

  1. Option 1: GCE(Google Compute Engine)集群​​ 

使用kube-up.sh启动的GCE集群将自动格式化并挂载所请求的Local SSDs,因此您可以使用预先生成的部署规范部署配置器并跳至步骤4,除非您要自定义配置器规范或存储类。

  1. $ NODE_LOCAL_SSDS_EXT=<n>,<scsi|nvme>,fs cluster/kube-up.sh 
  2. $ kubectl create -f provisioner/deployment/kubernetes/gce/class-local-ssds.yaml 
  3. $ kubectl create -f provisioner/deployment/kubernetes/gce/provisioner_generated_gce_ssd_volumes.yaml 

Option 2: GKE(Google Kubernetes Engine)集群

GKE集群将自动格式化并挂载所请求的Local SSDs。在GKE documentation中有更详细的说明。

然后,跳至步骤4。

Option 3: 使用裸机环境搭建的集群

1.根据应用程序的使用要求对每个节点上的本地数据磁盘进行分区和格式化。 2.定义一个StorageClass,并在一个发现目录下挂载所有要使用的存储文件系统。 发现目录是在configmap中指定,见下文。 3.如上所述,使用KUBEFEATUREGATES配置Kubernetes API Server, controller-manager, scheduler, 和所有节点上的 kubelets。 4.如果没有使用默认的Kubernetes调度程序策略,则必须启用以下特性:

  • Pre-1.9: NoVolumeBindConflict
  • 9+: VolumeBindingChecker

说明:在我们使用测试环境中,是一套3节点的k8s测试环境,为了模拟测试local volume功能,直接结合使用了下面option4中提供的ram disks测试方法,创建了3个tmpfs格式的文件系统挂载资源。

Option 4: 使用一个本机单节点的测试集群

创建/mnt/disks目录,并在该目录下挂载几个子目录。下面是使用三个ram disks做一个真实存储卷的模拟测试。

  1. $ mkdir /mnt/fast-disks 
  2. for vol in vol1 vol2 vol3; 
  3. do 
  4.    mkdir -p /mnt/fast-disks/$vol 
  5.    mount -t tmpfs $vol /mnt/fast-disks/$vol 
  6. done 

(2)创建单机k8s本地测试集群

  1. $ ALLOW_PRIVILEGED=true LOG_LEVEL=5 FEATURE_GATES=$KUBE_FEATURE_GATES hack/local-up-cluster.sh​​ 

Step 2: 创建一个StorageClass (1.9+)

要延迟卷绑定直到pod调度并处理单个pod中的多个本地PV,必须创建StorageClass并将volumeBindingMode设置为WaitForFirstConsumer。

  1. Only create this for K8s 1.9+ 
  2. apiVersion: storage.k8s.io/v1 
  3. kind: StorageClass 
  4. metadata: 
  5.  name: fast-disks 
  6. provisioner: kubernetes.io/no-provisioner 
  7. volumeBindingMode: WaitForFirstConsumer 
  8. # Supported policies: Delete, Retain 
  9. reclaimPolicy: Delete 
  10. $ kubectl create -f provisioner/deployment/kubernetes/example/default_example_storageclass.yaml 
  11. ​ 

yaml文件请到local volume项目文件中查找需要使用的yaml文件

Step 3: 创建local persistent volumes

Option 1: local volume static provisioner 方式

配置一个外部的静态配置器。

(1)生成Provisioner的ServiceAccount,Roles,DaemonSet和ConfigMap规范,并对其进行自定义配置。

此步骤使用helm模板生成需要的配置规格。 有关设置说明,请参阅helm README。

使用默认值生成Provisioner的配置规格,请运行:

helm template ./helm/provisioner > ./provisioner/deployment/kubernetes/provisioner_generated.yaml

这里是将模板经过渲染后得到最终使用的各项资源定义文件。 如果是使用自定义的配置文件的话:

  1. helm template ./helm/provisioner --values custom-values.yaml > ./provisioner/deployment/kubernetes/provisioner_generated.yaml​​ 

(2)部署Provisioner

如果用户对Provisioner的yaml文件的内容感到满意,就可以使用kubectl创建Provisioner的DaemonSet和ConfigMap了。

  1. # kubectl create -f ./provisioner/deployment/kubernetes/provisioner_generated.yaml 
  2. configmap "local-provisioner-config" created 
  3. daemonset.extensions "local-volume-provisioner" created 
  4. serviceaccount "local-storage-admin" created 
  5. clusterrolebinding.rbac.authorization.k8s.io "local-storage-provisioner-pv-binding" created 
  6. clusterrole.rbac.authorization.k8s.io "local-storage-provisioner-node-clusterrole" created 
  7. clusterrolebinding.rbac.authorization.k8s.io "local-storage-provisioner-node-binding" created 

(3)检查已自动发现的local volumes

一旦启动,外部static provisioner将发现并自动创建出 local-volume PVs。

我们查看下上面测试中创建出的PVs有哪些:

  1. # kubectl get pv 
  2. NAME                CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM     STORAGECLASS   REASON    AGE 
  3. local-pv-436f0527   495Mi      RWO            Delete           Available             fast-disks               2m 
  4. local-pv-77a4ffb0   495Mi      RWO            Delete           Available             fast-disks               2m 
  5. local-pv-97f7ec5c   495Mi      RWO            Delete           Available             fast-disks               2m 
  6. local-pv-9f0ddba3   495Mi      RWO            Delete           Available             fast-disks               2m 
  7. local-pv-a0dfdc91   495Mi      RWO            Delete           Available             fast-disks               2m 
  8. local-pv-a52333e3   495Mi      RWO            Delete           Available             fast-disks               2m 
  9. local-pv-bed86926   495Mi      RWO            Delete           Available             fast-disks               2m 
  10. local-pv-d037a0d1   495Mi      RWO            Delete           Available             fast-disks               2m 
  11. local-pv-d26c3252   495Mi      RWO            Delete           Available             fast-disks               2m 
  12. ​ 

因为是有3个node节点,每个上面的/mnt/fast-disks自动发现目录下挂载了3个文件系统,所以这里查询的结果是生成了9个PVs

查看某一个PV的详细描述信息:

  1. # kubectl describe pv local-pv-436f0527 
  2. Name:              local-pv-436f0527 
  3. Labels:            <none> 
  4. Annotations:       pv.kubernetes.io/provisioned-by=local-volume-provisioner-kube-node2-c3733876-b56f-11e8-990b-080027395360 
  5. Finalizers:        [kubernetes.io/pv-protection] 
  6. StorageClass:      fast-disks 
  7. Status:            Available 
  8. Claim:             
  9. Reclaim Policy:    Delete 
  10. Access Modes:      RWO 
  11. Capacity:          495Mi 
  12. Node Affinity:     
  13.  Required Terms:   
  14.    Term 0:        kubernetes.io/hostname in [kube-node2] 
  15. Message:           
  16. Source: 
  17.    Type:  LocalVolume (a persistent volume backed by local storage on a node) 
  18.    Path:  /mnt/fast-disks/vol2 
  19. Events:    <none> 
  20. ​ 

此时就可以直接通过引用名为fast-disks的storageClassName名称来声明使用上述PV并将其绑定到PVC。

Option 2: 手动创建 local persistent volume

参照前文介绍local volume概念的章节中已经讲解过的PersistentVolume使用示例。

Step 4: 创建 local persistent volume claim

  1. kind: PersistentVolumeClaim 
  2. apiVersion: v1 
  3. metadata: 
  4.  name: example-local-claim 
  5. spec: 
  6.  accessModes: 
  7.  - ReadWriteOnce 
  8.  resources: 
  9.    requests: 
  10.      storage: 50Mi 
  11.  storageClassName: fast-disks 
  12. ​ 

请在使用时替换为您实际的存储容量需求和storageClassName值。

  1. # kubectl create -f local-pvc.yaml 
  2. persistentvolumeclaim "example-local-claim" created 
  3. # kubectl get pvc 
  4. NAME                  STATUS    VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   AGE 
  5. example-local-claim   Pending     
  6. # kubectl describe pvc example-local-claim 
  7. Name:          example-local-claim 
  8. Namespace:     default 
  9. StorageClass:  fast-disks 
  10. Status:        Pending 
  11. Volume:         
  12. Labels:        <none> 
  13. Annotations:   <none> 
  14. Finalizers:    [kubernetes.io/pvc-protection] 
  15. Capacity:       
  16. Access Modes:   
  17. Events: 
  18.  Type    Reason                Age               From                         Message 
  19.  ----    ------                ----              ----                         ------- 
  20. ​ 

Normal WaitForFirstConsumer 6s (x6 over 59s) persistentvolume-controller waiting for first consumer to be created before binding

我们可以看到存储卷延迟绑定的效果,在绑定到容器前,该PVC的状态会是pending

Step 5:创建一个测试Pod并引用上面创建的PVC

  1. apiVersion: v1 
  2. kind: Pod 
  3. metadata: 
  4.  namelocal-pvc-pod 
  5. spec: 
  6.  containers: 
  7.  - image: busybox 
  8.    name: test-local-pvc 
  9.    command: [ "sleep""3600" ] 
  10.    volumeMounts: 
  11.    - mountPath: /data 
  12.      name: data-volume 
  13.  volumes: 
  14.  - name: data-volume 
  15.    persistentVolumeClaim: 
  16.      claimName: example-local-claim 
  17. ​ 

创建并查看:

  1. # kubectl create -f example-local-pvc-pod.yaml 
  2. pod "local-pvc-pod" created 
  3. # kubectl get pods -o wide 
  4. NAME                             READY     STATUS    RESTARTS   AGE       IP            NODE 
  5. client1                          1/1       Running   67         64d       172.30.80.2   kube-node3 
  6. local-pvc-pod                    1/1       Running   0          2m        172.30.48.6   kube-node1 
  7. ​ 

查看pod中容器挂载PVC的配置详情,这里只截取了部分信息:

  1. # kubectl describe pod local-pvc-pod 
  2. Name:         local-pvc-pod 
  3. Namespace:    default 
  4. Node:         kube-node1/172.16.10.101 
  5. Start Time:   Thu, 15 Nov 2018 16:39:30 +0800 
  6. Labels:       <none> 
  7. Annotations:  <none> 
  8. Status:       Running 
  9. IP:           172.30.48.6 
  10. Containers: 
  11.  test-local-pvc: 
  12. ...... 
  13.    Mounts: 
  14.      /data from data-volume (rw) 
  15.      /var/run/secrets/kubernetes.io/serviceaccount from default-token-qkhcf (ro) 
  16. Conditions: 
  17.  Type           Status 
  18.  Initialized    True 
  19.  Ready          True 
  20.  PodScheduled   True 
  21. Volumes: 
  22.  data-volume: 
  23.    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace) 
  24.    ClaimName:  example-local-claim 
  25.    ReadOnly:   false 
  26. ...... 
  27. [root@kube-node1 ~]# kubectl exec -it local-pvc-pod -c test-local-pvc /bin/sh 
  28. / # ls 
  29. bin   data  dev   etc   home  proc  root  sys   tmp   usr   var 
  30. / # df -h 
  31. Filesystem                Size      Used Available Use% Mounted on 
  32. overlay                  41.0G      8.1G     32.8G  20% / 
  33. tmpfs                    64.0M         0     64.0M   0% /dev 
  34. tmpfs                   495.8M         0    495.8M   0% /sys/fs/cgroup 
  35. vol3                    495.8M         0    495.8M   0% /data 
  36. ​ 

再回过头来看下PVC的状态,已经变成了Bound:

  1. # kubectl get pvc 
  2. NAME                        STATUS    VOLUME              CAPACITY   ACCESS MODES   STORAGECLASS   AGE 
  3. example-local-claim         Bound     local-pv-a0dfdc91   495Mi      RWO            fast-disks     1h 
  4. ​ 

7、一个关于local volume功能局限性问题的讨论

在上面的实验过程中不知道大家有没有发现一处问题,就是我们在定义PVC时是指定的申请50Mi的空间,而实际挂载到测试容器上的存储空间是495.8M,刚好是我们在某个node节点上挂载的一个文件系统的全部空间。

为什么会这样呢?这就是我们所使用的这个local persistent volume外部静态配置器的功能局限性所在了。它不支持动态的PV空间申请管理。

也就是说,虽然通过这个静态PV配置器,我们省去了手写PV YAML文件的痛苦,但仍然需要手工处理这项工作:

  • 手工维护在ConfigMap中指定的自动发现目录下挂载的文件系统资源,或者是block设备的符号链接;
  • 我们需要对能够使用的本地存储资源提前做一个全局的规划,然后划分为各种尺寸的卷后挂载到自动发现目录下,当然了只要是还有空闲存储资源,现有现挂载也是可以的。

那如果以前给某容器分配的一个存储空间不够用了怎么办?

给大家的一个建议是使用Linux下的LVM(逻辑分区管理)来管理每个node节点上的本地磁盘存储空间。

  • 创建一个大的VG分组,把一个node节点上可以使用的存储空间都放进去;
  • 按未来一段时间内的容器存储空间使用预期,提前批量创建出一部分逻辑卷LVs,都挂载到自动发现目录下去;
  • 不要把VG中的存储资源全部用尽,预留少部分用于未来给个别容器扩容存储空间的资源;
  • 使用lvextend为特定容器使用的存储卷进行扩容;

8、如果容器需要使用block块设备怎么配置

有几点会与上面的配置方法上不同。

首先,是要在k8s主要的组件上均开启用于支持block块设备的特性。

  1. KUBE_FEATURE_GATES="BlockVolume=true"​​ 

其次是,定义一个”Block”类型的volumeMode PVC,为容器申请一个”Block”类型的PV。

  1. kind: PersistentVolumeClaim 
  2. apiVersion: v1 
  3. metadata: 
  4.  name: example-block-local-claim 
  5. spec: 
  6.  accessModes: 
  7.  - ReadWriteOnce 
  8.  resources: 
  9.    requests: 
  10.      storage: 50Mi 
  11.  volumeMode: Block 
  12.  storageClassName: fast-disks 
  13. ​ 

9、Local volumes的***实践

  • 为了更好的IO隔离效果,建议将一整块磁盘作为一个存储卷使用;
  • 为了得到存储空间的隔离,建议为每个存储卷使用一个独立的磁盘分区;
  • 在仍然存在指定了某个node节点的亲和性关系的旧PV时,要避免重新创建具有相同节点名称的node节点。 否则,系统可能会认为新节点包含旧的PV。
  • 对于具有文件系统的存储卷,建议在fstab条目和该卷的mount安装点的目录名中使用它们的UUID(例如ls -l /dev/disk/by-uuid的输出)。 这种做法可确保不会安装错误的本地卷,即使其设备路径发生了更改(例如,如果/dev/sda1在添加新磁盘时变为/dev/sdb1)。 此外,这种做法将确保如果创建了具有相同名称的另一个节点时,该节点上的任何卷仍然都会是唯一的,而不会被误认为是具有相同名称的另一个节点上的卷。
  • 对于没有文件系统的原始块存储卷,请使用其唯一ID作为符号链接的名称。 根据您的环境,/dev/disk/by-id/中的卷ID可能包含唯一的硬件序列号。 否则,应自行生成一个唯一ID。 符号链接名称的唯一性将确保如果创建了另一个具有相同名称的节点,则该节点上的任何卷都仍然是唯一的,而不会被误认为是具有相同名称的另一个节点上的卷。

10、停用local volume的方法

当您想要停用本地卷时,这是一个可能的工作流程。

  • 关闭使用这些卷的Pods;
  • 从node节点上移除local volumes(比如unmounting, 拔出磁盘等等);
  • 手动删除相应的PVCs对象;
  • Provisioner将尝试清理卷,但会由于卷不再存在而失败;
  • 手动删除相应的PVs对象。 注:以上工作也是拜我们所使用的外部静态配置器所赐。

参考资料:

https://blog.csdn.net/watermelonbig/article/details/84108424

https://github.com/kubernetes-incubator/external-storage/tree/master/local-volume https://kubernetes.io/docs/concepts/storage/volumes/

责任编辑:武晓燕 来源: K8S中文社区
相关推荐

2021-03-10 08:56:37

Zookeeper

2021-02-08 23:47:51

文件存储块存储对象存储

2024-02-29 09:08:56

Encoding算法加密

2021-07-27 07:31:16

JavaArrayList数组

2022-05-15 21:52:04

typeTypeScriptinterface

2020-10-30 08:20:04

SD卡TF卡存储

2023-09-03 21:18:07

Python编程语言

2018-05-22 16:24:20

HashMapJavaJDK

2023-02-27 15:46:19

数据元元数据

2020-03-03 17:35:09

Full GCMinor

2016-11-04 12:51:46

Unix网络IO 模型

2021-11-09 06:01:35

前端JITAOT

2022-02-25 09:14:33

类变量共享实例变量

2020-11-11 07:32:18

MySQL InnoDB 存储

2021-01-13 08:10:26

接口IEnumeratorIEnumerable

2023-04-11 15:57:49

JavaScriptCSSHTML

2019-11-21 14:22:12

WiFiWLAN区别

2021-02-14 22:33:23

Java字符字段

2021-11-01 13:10:48

私有云混合云行业云

2021-06-07 09:20:56

Javascript运算符开发
点赞
收藏

51CTO技术栈公众号