Kubernetes支持几十种类型的后端存储卷,其中有几种存储卷总是给人一种分不清楚它们之间有什么区别的感觉,尤其是local与hostPath这两种存储卷类型,看上去都像是node本地存储方案嘛。当然,还另有一种volume类型是emptyDir,也有相近之处。
在Docker容器时代,我们就对Volume很熟悉了,一般来说我们是通过创建Volume数据卷,然后挂载到指定容器的指定路径下,以实现容器数据的持久化存储或者是多容器间的数据共享,当然这里说的都是单机版的容器解决方案。
进入到容器集群时代后,我们看到Kubernetes按时间顺序先后提供了emptyDir、hostPath和local的本地磁盘存储卷解决方案。
emptyDir、hostPath都是Kubernetes很早就实现和支持了的技术,local volume方式则是从k8s v1.7才刚刚发布的alpha版本,目前在k8s v1.10中发布了local volume的beta版本,部分功能在早期版本中并不支持。
在展开之前,我们先讨论一个问题,就是既然都已经实现容器云平台了,我们为什么还要关注这几款本地存储卷的货呢?
粗略归纳了下,有以下几个原因:
我们并不是说分布式存储服务不好,很多公司在云平台建设的实践中,往往是需要结合使用几种通用的与专用的存储解决方案,才能最终满足大部分的使用需求。
所以,如果这里有一款场景适合你的话,不妨了解一下这几款本地存储卷的功能特点、使用技巧与异同。
1、emptyDir
emptyDir类型的Volume在Pod分配到Node上时被创建,Kubernetes会在Node上自动分配一个目录,因此无需指定宿主机Node上对应的目录文件。 这个目录的初始内容为空,当Pod从Node上移除时,emptyDir中的数据会被***删除。
注:容器的crashing事件并不会导致emptyDir中的数据被删除。
***实践
根据官方给出的***使用实践的建议,emptyDir可以在以下几种场景下使用:
- apiVersion: v1
- kind: Pod
- metadata:
- name: test-pod
- spec:
- containers:
- - image: busybox
- name: test-emptydir
- command: [ "sleep", "3600" ]
- volumeMounts:
- - mountPath: /data
- name: data-volume
- volumes:
- - name: data-volume
- emptyDir: {}
查看下创建出来的pod,这里只截取了与volume有关的部分,其他无关内容直接省略:
- # kubectl describe pod test-pod
- Name: test-pod
- Namespace: default
- Node: kube-node2/172.16.10.102
- ......
- Environment: <none>
- Mounts:
- /data from data-volume (rw)
- ......
- Volumes:
- data-volume:
- Type: EmptyDir (a temporary directory that shares a pod's lifetime)
- Medium:
- ......
可以进入到容器中查看下实际的卷挂载结果:
- # kubectl exec -it test-pod -c test-emptydir /bin/sh
2、hostPath
hostPath类型则是映射node文件系统中的文件或者目录到pod里。在使用hostPath类型的存储卷时,也可以设置type字段,支持的类型有文件、目录、File、Socket、CharDevice和BlockDevice。
下面是来自官网对hostPath的使用场景和注意事项的介绍。
使用场景:
hostPath volume 实验
下面我们在测试k8s环境中创建一个hostPath volume使用示例。
- apiVersion: v1
- kind: Pod
- metadata:
- name: test-pod2
- spec:
- containers:
- - image: busybox
- name: test-hostpath
- command: [ "sleep", "3600" ]
- volumeMounts:
- - mountPath: /test-data
- name: test-volume
- volumes:
- - name: test-volume
- hostPath:
- # directory location on host
- path: /data
- # this field is optional
- type: Directory
查看下pod创建结果,观察volumes部分:
- # kubectl describe pod test-pod2
- Name: test-pod2
- Namespace: default
- Node: kube-node2/172.16.10.102
- ......
- Mounts:
- /test-data from test-volume (rw)
- ......
- Volumes:
- test-volume:
- Type: HostPath (bare host directory volume)
- Path: /data
- HostPathType: Directory
- ......
我们登录到容器中,进入挂载的/test-data目录中,创建个测试文件。
- # kubectl exec -it test-pod2 -c test-hostpath /bin/sh
- / # echo 'testtesttest' > /test-data/test.log
- / # exit
我们在运行该pod的node节点上,可以看到如下的文件和内容。
- [root@kube-node2 test-data]# cat /test-data/test.log
- testtesttest
现在,我们把该pod删除掉,再看看node节点上的hostPath使用的目录与数据会有什么变化。
- [root@kube-node1 ~]# kubectl delete pod test-pod2
- pod "test-pod2" deleted
到运行原pod的node节点上查看如下。
- [root@kube-node2 test-data]# ls -l
- total 4
- -rw-r--r-- 1 root root 13 Nov 14 00:25 test.log
- [root@kube-node2 test-data]# cat /test-data/test.log
- testtesttest
在使用hostPath volume卷时,即便pod已经被删除了,volume卷中的数据还在!
单节点的k8s本地测试环境与hostPath volume
有时我们需要搭建一个单节点的k8s测试环境,就利用到hostPath作为后端的存储卷,模拟真实环境提供PV、StorageClass和PVC的管理功能支持。
- apiVersion: storage.k8s.io/v1
- kind: StorageClass
- metadata:
- namespace: kube-system
- name: standard
- annotations:
- storageclass.beta.kubernetes.io/is-default-class: "true"
- labels:
- addonmanager.kubernetes.io/mode: Reconcile
- provisioner: kubernetes.io/host-path
-
该场景仅能用于单节点的k8s测试环境中
3、emptyDir和hostPath在功能上的异同分析
二者都是node节点的本地存储卷方式;
4、local volume的概念
这是一个很新的存储类型,建议在k8s v1.10+以上的版本中使用。该local volume类型目前还只是beta版。
Local volume 允许用户通过标准PVC接口以简单且可移植的方式访问node节点的本地存储。 PV的定义中需要包含描述节点亲和性的信息,k8s系统则使用该信息将容器调度到正确的node节点。
配置要求
StorageClass与延迟绑定
官方推荐在使用local volumes时,创建一个StorageClass并把volumeBindingMode字段设置为“WaitForFirstConsumer”。
虽然local volumes还不支持动态的provisioning管理功能,但我们仍然可以创建一个StorageClass并使用延迟卷绑定的功能,将volume binding延迟至pod scheduling阶段执行。
这样可以确保PersistentVolumeClaim绑定策略将Pod可能具有的任何其他node节点约束也进行评估,例如节点资源要求、节点选择器、Pod亲和性和Pod反亲和性。
- kind: StorageClass
- apiVersion: storage.k8s.io/v1
- metadata:
- name: local-storage
- provisioner: kubernetes.io/no-provisioner
- volumeBindingMode: WaitForFirstConsumer
外部static provisioner
配置local volume后,可以使用一个外部的静态配置器来帮助简化本地存储的管理。 Provisioner 配置程序将通过为每个卷创建和清理PersistentVolumes来管理发现目录下的卷。
Local storage provisioner要求管理员在每个节点上预配置好local volumes,并指明该local volume是属于以下哪种类型:
一个local volume,可以是挂载到node本地的磁盘、磁盘分区或目录。
Local volumes虽然可以支持创建静态PersistentVolume,但到目前为止仍不支持动态的PV资源管理。
这意味着,你需要自己手动去处理部分PV管理的工作,但考虑到至少省去了在创建pod时手动定义和使用PV的工作,这个功能还是很值得的。
创建基于Local volumes的PV的示例
- apiVersion: v1
- kind: PersistentVolume
- metadata:
- name: example-pv
- spec:
- capacity:
- storage: 100Gi
- volumeMode: Filesystem
- accessModes:
- - ReadWriteOnce
- persistentVolumeReclaimPolicy: Delete
- storageClassName: local-storage
- local:
- path: /mnt/disks/ssd1
- nodeAffinity:
- required:
- nodeSelectorTerms:
- - matchExpressions:
- - key: kubernetes.io/hostname
- operator: In
- values:
- - example-node
-
数据安全风险
local volume仍受node节点可用性方面的限制,因此并不适用于所有应用程序。 如果node节点变得不健康,则local volume也将变得不可访问,使用这个local volume的Pod也将无法运行。 使用local voluems的应用程序必须能够容忍这种降低的可用性以及潜在的数据丢失,是否会真得导致这个后果将取决于node节点底层磁盘存储与数据保护的具体实现了。
5、hostPath与local volume在功能上的异同分析
二者都基于node节点本地存储资源实现了容器内数据的持久化功能,都为某些特殊场景下提供了更为适用的存储解决方案;
前者时间很久了,所以功能稳定,而后者因为年轻,所以功能的可靠性与稳定性还需要经历时间和案例的历练,尤其是对Block设备的支持还只是alpha版本;
二者都为k8s存储管理提供了PV、PVC和StorageClass的方法实现;
6、local volume的安装配置方法
local-volume项目及地址
https://github.com/kubernetes-incubator/external-storage/tree/master/local-volume
Step 1:配置k8s集群使用本地磁盘存储
如果使用block块设备,则需要启用Alpha的功能特性:k8s v1.10+
- $ export KUBE_FEATURE_GATES=”BlockVolume=true”
注:如果是已经部署好的k8s v1.10+集群,需要为几个主要组件均开启对该特性的支持后,才能使用block块设备功能。如果k8s是低于1.10版本,则还需要启用其它的几个功能特性,因为在低版本中这些功能特性还都是alpha版本的。
根据大家搭建k8s的方法的不同,下面提供了四种情况下的配置说明。
- Option 1: GCE(Google Compute Engine)集群
使用kube-up.sh启动的GCE集群将自动格式化并挂载所请求的Local SSDs,因此您可以使用预先生成的部署规范部署配置器并跳至步骤4,除非您要自定义配置器规范或存储类。
- $ NODE_LOCAL_SSDS_EXT=<n>,<scsi|nvme>,fs cluster/kube-up.sh
- $ kubectl create -f provisioner/deployment/kubernetes/gce/class-local-ssds.yaml
- $ 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调度程序策略,则必须启用以下特性:
说明:在我们使用测试环境中,是一套3节点的k8s测试环境,为了模拟测试local volume功能,直接结合使用了下面option4中提供的ram disks测试方法,创建了3个tmpfs格式的文件系统挂载资源。
Option 4: 使用一个本机单节点的测试集群
创建/mnt/disks目录,并在该目录下挂载几个子目录。下面是使用三个ram disks做一个真实存储卷的模拟测试。
- $ mkdir /mnt/fast-disks
- $ for vol in vol1 vol2 vol3;
- do
- mkdir -p /mnt/fast-disks/$vol
- mount -t tmpfs $vol /mnt/fast-disks/$vol
- done
(2)创建单机k8s本地测试集群
- $ 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。
- # Only create this for K8s 1.9+
- apiVersion: storage.k8s.io/v1
- kind: StorageClass
- metadata:
- name: fast-disks
- provisioner: kubernetes.io/no-provisioner
- volumeBindingMode: WaitForFirstConsumer
- # Supported policies: Delete, Retain
- reclaimPolicy: Delete
- $ kubectl create -f provisioner/deployment/kubernetes/example/default_example_storageclass.yaml
-
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
这里是将模板经过渲染后得到最终使用的各项资源定义文件。 如果是使用自定义的配置文件的话:
- helm template ./helm/provisioner --values custom-values.yaml > ./provisioner/deployment/kubernetes/provisioner_generated.yaml
(2)部署Provisioner
如果用户对Provisioner的yaml文件的内容感到满意,就可以使用kubectl创建Provisioner的DaemonSet和ConfigMap了。
- # kubectl create -f ./provisioner/deployment/kubernetes/provisioner_generated.yaml
- configmap "local-provisioner-config" created
- daemonset.extensions "local-volume-provisioner" created
- serviceaccount "local-storage-admin" created
- clusterrolebinding.rbac.authorization.k8s.io "local-storage-provisioner-pv-binding" created
- clusterrole.rbac.authorization.k8s.io "local-storage-provisioner-node-clusterrole" created
- clusterrolebinding.rbac.authorization.k8s.io "local-storage-provisioner-node-binding" created
(3)检查已自动发现的local volumes
一旦启动,外部static provisioner将发现并自动创建出 local-volume PVs。
我们查看下上面测试中创建出的PVs有哪些:
- # kubectl get pv
- NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
- local-pv-436f0527 495Mi RWO Delete Available fast-disks 2m
- local-pv-77a4ffb0 495Mi RWO Delete Available fast-disks 2m
- local-pv-97f7ec5c 495Mi RWO Delete Available fast-disks 2m
- local-pv-9f0ddba3 495Mi RWO Delete Available fast-disks 2m
- local-pv-a0dfdc91 495Mi RWO Delete Available fast-disks 2m
- local-pv-a52333e3 495Mi RWO Delete Available fast-disks 2m
- local-pv-bed86926 495Mi RWO Delete Available fast-disks 2m
- local-pv-d037a0d1 495Mi RWO Delete Available fast-disks 2m
- local-pv-d26c3252 495Mi RWO Delete Available fast-disks 2m
-
因为是有3个node节点,每个上面的/mnt/fast-disks自动发现目录下挂载了3个文件系统,所以这里查询的结果是生成了9个PVs
查看某一个PV的详细描述信息:
- # kubectl describe pv local-pv-436f0527
- Name: local-pv-436f0527
- Labels: <none>
- Annotations: pv.kubernetes.io/provisioned-by=local-volume-provisioner-kube-node2-c3733876-b56f-11e8-990b-080027395360
- Finalizers: [kubernetes.io/pv-protection]
- StorageClass: fast-disks
- Status: Available
- Claim:
- Reclaim Policy: Delete
- Access Modes: RWO
- Capacity: 495Mi
- Node Affinity:
- Required Terms:
- Term 0: kubernetes.io/hostname in [kube-node2]
- Message:
- Source:
- Type: LocalVolume (a persistent volume backed by local storage on a node)
- Path: /mnt/fast-disks/vol2
- Events: <none>
-
此时就可以直接通过引用名为fast-disks的storageClassName名称来声明使用上述PV并将其绑定到PVC。
Option 2: 手动创建 local persistent volume
参照前文介绍local volume概念的章节中已经讲解过的PersistentVolume使用示例。
Step 4: 创建 local persistent volume claim
- kind: PersistentVolumeClaim
- apiVersion: v1
- metadata:
- name: example-local-claim
- spec:
- accessModes:
- - ReadWriteOnce
- resources:
- requests:
- storage: 50Mi
- storageClassName: fast-disks
-
请在使用时替换为您实际的存储容量需求和storageClassName值。
- # kubectl create -f local-pvc.yaml
- persistentvolumeclaim "example-local-claim" created
- # kubectl get pvc
- NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
- example-local-claim Pending
- # kubectl describe pvc example-local-claim
- Name: example-local-claim
- Namespace: default
- StorageClass: fast-disks
- Status: Pending
- Volume:
- Labels: <none>
- Annotations: <none>
- Finalizers: [kubernetes.io/pvc-protection]
- Capacity:
- Access Modes:
- Events:
- Type Reason Age From Message
- ---- ------ ---- ---- -------
-
Normal WaitForFirstConsumer 6s (x6 over 59s) persistentvolume-controller waiting for first consumer to be created before binding
我们可以看到存储卷延迟绑定的效果,在绑定到容器前,该PVC的状态会是pending
Step 5:创建一个测试Pod并引用上面创建的PVC
- apiVersion: v1
- kind: Pod
- metadata:
- name: local-pvc-pod
- spec:
- containers:
- - image: busybox
- name: test-local-pvc
- command: [ "sleep", "3600" ]
- volumeMounts:
- - mountPath: /data
- name: data-volume
- volumes:
- - name: data-volume
- persistentVolumeClaim:
- claimName: example-local-claim
-
创建并查看:
- # kubectl create -f example-local-pvc-pod.yaml
- pod "local-pvc-pod" created
- # kubectl get pods -o wide
- NAME READY STATUS RESTARTS AGE IP NODE
- client1 1/1 Running 67 64d 172.30.80.2 kube-node3
- local-pvc-pod 1/1 Running 0 2m 172.30.48.6 kube-node1
-
查看pod中容器挂载PVC的配置详情,这里只截取了部分信息:
- # kubectl describe pod local-pvc-pod
- Name: local-pvc-pod
- Namespace: default
- Node: kube-node1/172.16.10.101
- Start Time: Thu, 15 Nov 2018 16:39:30 +0800
- Labels: <none>
- Annotations: <none>
- Status: Running
- IP: 172.30.48.6
- Containers:
- test-local-pvc:
- ......
- Mounts:
- /data from data-volume (rw)
- /var/run/secrets/kubernetes.io/serviceaccount from default-token-qkhcf (ro)
- Conditions:
- Type Status
- Initialized True
- Ready True
- PodScheduled True
- Volumes:
- data-volume:
- Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
- ClaimName: example-local-claim
- ReadOnly: false
- ......
- [root@kube-node1 ~]# kubectl exec -it local-pvc-pod -c test-local-pvc /bin/sh
- / # ls
- bin data dev etc home proc root sys tmp usr var
- / # df -h
- Filesystem Size Used Available Use% Mounted on
- overlay 41.0G 8.1G 32.8G 20% /
- tmpfs 64.0M 0 64.0M 0% /dev
- tmpfs 495.8M 0 495.8M 0% /sys/fs/cgroup
- vol3 495.8M 0 495.8M 0% /data
-
再回过头来看下PVC的状态,已经变成了Bound:
- # kubectl get pvc
- NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
- example-local-claim Bound local-pv-a0dfdc91 495Mi RWO fast-disks 1h
-
7、一个关于local volume功能局限性问题的讨论
在上面的实验过程中不知道大家有没有发现一处问题,就是我们在定义PVC时是指定的申请50Mi的空间,而实际挂载到测试容器上的存储空间是495.8M,刚好是我们在某个node节点上挂载的一个文件系统的全部空间。
为什么会这样呢?这就是我们所使用的这个local persistent volume外部静态配置器的功能局限性所在了。它不支持动态的PV空间申请管理。
也就是说,虽然通过这个静态PV配置器,我们省去了手写PV YAML文件的痛苦,但仍然需要手工处理这项工作:
那如果以前给某容器分配的一个存储空间不够用了怎么办?
给大家的一个建议是使用Linux下的LVM(逻辑分区管理)来管理每个node节点上的本地磁盘存储空间。
8、如果容器需要使用block块设备怎么配置
有几点会与上面的配置方法上不同。
首先,是要在k8s主要的组件上均开启用于支持block块设备的特性。
- KUBE_FEATURE_GATES="BlockVolume=true"
其次是,定义一个”Block”类型的volumeMode PVC,为容器申请一个”Block”类型的PV。
- kind: PersistentVolumeClaim
- apiVersion: v1
- metadata:
- name: example-block-local-claim
- spec:
- accessModes:
- - ReadWriteOnce
- resources:
- requests:
- storage: 50Mi
- volumeMode: Block
- storageClassName: fast-disks
-
9、Local volumes的***实践
10、停用local volume的方法
当您想要停用本地卷时,这是一个可能的工作流程。
参考资料:
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/
一条铁律 在业内,redis开发规范中有一条铁律如下所示 线上Redis禁止使用Keys正...
1.背景 缓存的使用一定是今后开发中100%会用到的技术,尤其是Redis相关的问题,...
2020年开年至今,全球都被新冠疫情所笼罩,在相当一段时间内,保持社交距离、自我...
选择香港服务器租用服务时,我们总是考虑获得降低到最低成本 - 但是,理解所谓的...
很多组织如今需要采用现代工作场所解决方案以保持领先地位。数字化转型不仅对于I...
你还记得大学里学的高数、物理的公式吗?专业课的知识点现在又还能背出来几条?回...
1.我怕一毕业,身边就少了那群能让我笑的疯子。 2.没有什么过不去,只是再也回...
1.缓存概述 在很久很久以前人类和洪水作斗争的过程中,水库发挥了至关重要的作用...
数据、设备和分布式计算都是边缘计算设施的重要组成部分。人们需要了解边缘计算...
我们在租用 香港服务器 之后,就有可能遇到各种各样的问题,服务器遭受网络攻击...