日常中经常使用pv、pvc来保障有状态服务的数据持久化, 自从kubernertes v1.14对local PV GA之后, 直接使用目录或者是块设备来做为持久化pv变得更为方便,毕竟使用分布式存储还是有些成本的.借这个机会说一说 pv、pvc延迟绑定问题.
PV
如果没有使用动态pv provisioner, 则只能事先由集群管理员手工进行pv的准备.
一般分成挂载 –> 格式 –> 目录,最安全的是直接把整个块设备当成local PV,这样好保证IO隔离.
1 | apiVersion: v1 |
PVC
在对象中使用pvc来绑定pv
1 | ... |
default storageclass
如果pv, pvc中使用了一个不存在的storageclass, 也不会发生报错,这是因为集群中存在一个默认的storageclass, 虽然指定的storagelcass可以不存在, 但是在做绑定决策的时候,还是会考虑pv与pvc的storageclass定义, 因此如果没有使用storageclass, 直接置空即可,集群自动使用default storageclass.
storageclass
当然,也可以自定义,storageclass, 生成一个storageclass比较简单
1 | apiVersion: storage.k8s.io/v1 |
需要注意的是,在它的 provisioner
字段,我们指定的是 no-provisioner
。这是因为我们这里是手动创建的 PV,所以不需要动态来生成 PV
对于local PV,另一个比较重要的的参数volumeBindingMode: WaitForFirstConsumer
这个参数到底什么意思呢?为什么需要它?
WaitForFirstConsumer
考虑这么一种情况:
有一个pod, 使用的pvc叫pvc-1, 我们希望它只运行在node-2上,在当前的集群中存在两台主机符合pod的pvc的要求, 假如node-1上是pv-1, node-2上是pv-2,这两个完全一样.
这时如果创建pod, pv控制器看到pv-1与pvc-1是匹配的,因此将它们绑定在一起, 如果没有其它限制条件, 在调度阶段pod将会被调度到node-1上, 这显然与我们的期望不同,我们是希望它调度到node-2上,pv与pvc的绑定关系是发生在调度之前的,就会造成pv与pvc的绑定成功, 但是pod的调度却不能成功的局面. 因此,需要一种机制来将这种绑定延迟到调度阶段,这就是WaitForFirstConsumer的作用
WaitForFirstConsumer其实就是告诉volume控制器,虽然找到了适合的pv, 但请不要现在就执行绑定操作,而要等到第一个声明使用了该pvc的pod出现在调度器之后,调度器综合考虑所有的调度规则后, 最终决定跟哪个pv进行绑定
通过这个延迟绑定机制,原本实时发生的 PVC 和 PV 的绑定过程,就被延迟到了 Pod 第一次调度的时候在调度器中进行,从而保证了这个绑定结果不会影响 Pod 的正常调度
通过延迟绑定机制,原本实时发生的 PVC 和 PV 的绑定过程,就被延迟到了 Pod 第一次调度的时候在调度器中进行,从而保证了这个绑定结果不会影响 Pod 的正常调度
static local PV provisioner
local PV不支持动态绑定, k8s-sigs也提供了一种可以自动管理local PV的插件, 详情在github
这个插件主要做的事情就是通过监控某一目录下是否有新的挂载点或者是块设备来自动地根据模块生成pv, 这样就需要手工地创建,同时也支持在删除pvc的时候自动清除pv.
当static provisioner启动后, 它就会通过daemonset自动地检查每个宿主机的指定目录, 然后调用kubernetes api自动创建pv
主要组件实现以下功能:
- Discovery: The discovery routine periodically reads the configured discovery directories and looks for new mount points that don’t have a PV, and creates a PV for it.
- Deleter: The deleter routine is invoked by the Informer when a PV phase changes. If the phase is Released, then it cleans up the volume and deletes the PV API object.
- Cache: A central cache stores all the Local PersistentVolumes that the provisioner has created. It is populated by a PV informer that filters out the PVs that belong to this node and have been created by this provisioner. It is used by the Discovery and Deleter routines to get the existing PVs.
- Controller: The controller runs a sync loop that coordinates the other components. The discovery and deleter run serially to simplify synchronization with the cache and create/delete operations.
本人目前没有在生产环境下使用这个,只是在测试环境下使用, 感兴趣的话可以参考github.