什么是Local Persistent Volumes
在kubernetes 1.14版本中,
Local Persistent Volumes
已变为正式版本(GA),Local PV的概念在1.7中被首次提出(alpha),并在1.10版本中升级到beat版本。现在用户终于可以在生产环境中使用Local PV的功能和API了。
首先:Local Persistent Volumes
代表了直接绑定在计算节点上的一块本地磁盘。
kubernetes提供了一套卷插件(volume plugin)标准,使得k8s集群的工作负载可以使用多种块存储和文件存储。大部分磁盘插件都使用了远程存储,这是为了让持久化的数据与计算节点彼此独立,但远程存储通常无法提供本地存储那么强的读写性能。有了Local PV 插件,kubernetes负载现在可以用同样的volume api,在容器中使用本地磁盘。(分布式文件系统和数据库一直是 Local PV 的主要用例)
这跟hostPath有什么区别
hostPath是一种volume,可以让pod挂载宿主机上的一个文件或目录(如果挂载路径不存在,则创建为目录或文件并挂载)。
最大的不同在于调度器是否能理解磁盘和node的对应关系,一个使用hostPath的pod,当他被重新调度时,很有可能被调度到与原先不同的node上,这就导致pod内数据丢失了。而使用Local PV的pod,总会被调度到同一个node上(否则就调度失败)。
如何使用Local PV
首先 需要创建StorageClass
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
注意到这里
volumeBindingMode
字段的值是WaitForFirstConsumer
。这种bindingmode意味着:kubernetes的pv控制器会将这类pv的binding延迟,直到有一个使用了对应pvc的pod被创建出来且该pod被调度完毕。这时候才会将pv和pvc进行binding,并且这时候pv的选择会结合调度的node和pv的nodeaffinity。
-
手动创建PV。但是必须要注意的是,Local PV必须要填写nodeAffinity
apiVersion: v1
kind: PersistentVolume
metadata:
name: example-pv
spec:
capacity:
storage: 100Gi
# volumeMode field requires BlockVolume Alpha feature gate to be enabled.
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:
- k8s-n1
注意:目前,Local PV 的本地持久存储允许我们直接使用节点上的一块磁盘、一个分区或者一个目录作为持久卷的存储后端,但暂时还不提供动态配置支持,也就是说:你得先把 PV 准备好。(一台主机就创建一个PV,在nodeAffinity写不同的节点 k8s-n1、 k8s-n2、 k8s-n3),其他内容和一个普通 PV 无异,只是多了一个 nodeAffinity。
-
接下来可以创建各种workload,记得要在workload的模板中声明volumeClaimTemplates。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: local-test
spec:
serviceName: "local-service"
replicas: 3
selector:
matchLabels:
app: local-test
template:
metadata:
labels:
app: local-test
spec:
containers:
- name: test-container
image: k8s.gcr.io/busybox
command:
- "/bin/sh"
args:
- "-c"
- "sleep 100000"
volumeMounts:
- name: local-vol
mountPath: /usr/test-pod
volumeClaimTemplates:
- metadata:
name: local-vol
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "local-storage"
resources:
requests:
storage: 100Gi
注意到这里
volumeClaimTemplates.spec.storageClassName
是local-storage
,即我们一开始创建的storageclass实例的名字。
使用Local PV的pod的调度流程
上面这个statefulset创建后,控制器会为其创建对应的PVC,并且会为PVC查找符合条件的PV,但是由于我们在
local-storage
中配置了WaitForFirstConsumer
,所以控制器不会处理pvc和pv的bind状态处于pending状态;
-
同时,调度器在调度该pod时,predicate算法中也会根据PVC的要求去找到可用的PV,并且会过滤掉“与Local PV的affinity”不匹配的node。最终,调度器发现:
- pv:
example-pv
满足了pvc的要求;- node:
k8s-n1
节点满足了pv:example-pv
的nodeAffinity要求。于是乎调度器尝试将pv和pvc bind起来,并且对pod进行重新调度。
-
重新调度pod时调度器发现pod的pvc资源得到了满足(都bound了pv),且bound的pv的nodeAffinity与node:
k8s-n1
匹配。于是将pod调度到node:k8s-n1
上。完成调度。
如何创建Local PV
- 在机器上创建目录:
mkdir -p /mnt/disks/ssd1
- 在机器上执行命令,将某个卷挂载到该目录:
mount -t /dev/vdc /mnt/disks/ssd1
- 在集群中创建对应的storageClass. 参见上文。
- 手动创建本地卷的PV,或者通过provisioner去自动创建。手动创建的模板见上文。
如何删除Local PV
对于已经被bind并被pod使用的Local PV,删除一定要按照流程来 , 要不然会删除失败:
- 删除使用这个pv的pod
- 从node上移除这个磁盘(按照一个pv一块盘)
- 删除pvc
- 删除pv
注意事项
- 使用local pv时必须定义nodeAffinity,Kubernetes Scheduler需要使用PV的nodeAffinity描述信息来保证Pod能够调度到有对应local volume的Node上。
- 创建local PV之前,你需要先保证有对应的storageClass已经创建。并且该storageClass的
volumeBindingMode
必须是WaitForFirstConsumer
以标识延迟Volume Binding。- 节点上local volume的初始化需要我们人为去完成(比如local disk需要pre-partitioned, formatted, and mounted. 共享存储对应的Directories也需要pre-created),并且人工创建这个local PV,当Pod结束,我们还需要手动的清理local volume,然后手动删除该local PV对象。因此,
persistentVolumeReclaimPolicy
只能是Retain
。- Local PV在生产中使用,也是需要谨慎的,毕竟它本质上还是使用的是节点上的本地存储,如果没有相应的存储副本机制,那意味着一旦节点或者磁盘异常,使用该volume的Pod也会异常,甚至出现数据丢失,除非你明确知道这个风险不会对你的应用造成很大影响或者允许数据丢失。
Local volumes的最佳实践
- 为了更好的IO隔离效果,建议将一整块磁盘作为一个存储卷使用;
- 为了得到存储空间的隔离,建议为每个存储卷使用一个独立的磁盘分区;
- 避免重新创建同名的 Node,否则会导致新 Node 无法识别已绑定旧 Node 的 PV 否则,系统可能会认为新Node节点包含旧的PV。
- 对于具有文件系统的存储卷,建议在fstab条目和该卷的mount安装点的目录名中使用它们的UUID(例如ls -l /dev/disk/by-uuid的输出)。 这种做法可确保不会安装错误的本地卷,即使其设备路径发生了更改(例如,如果/dev/sda1在添加新磁盘时变为/dev/sdb1)。 此外,这种做法将确保如果创建了具有相同名称的另一个节点时,该节点上的任何卷仍然都会是唯一的,而不会被误认为是具有相同名称的另一个节点上的卷。
原文地址:https://blog.51cto.com/2032872/2430897