前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【玩转腾讯云】K8s存储 —— Attach/Detach Controller与TKE现网案例分析

【玩转腾讯云】K8s存储 —— Attach/Detach Controller与TKE现网案例分析

原创
作者头像
ivan-cai
修改2020-03-20 14:49:20
2.7K1
修改2020-03-20 14:49:20
举报
文章被收录于专栏:ivan专栏ivan专栏

k8s中涉及存储的组件主要有:attach/detach controller、pv controller、volume manager、volume plugins、scheduler。每个组件分工明确:

  • attach/detach controller:负责对volume进行attach/detach
  • pv controller:负责处理pv/pvc对象,包括pv的provision/delete(cbs intree的provisioner设计成了external provisioner,独立的cbs-provisioner来负责cbs pv的provision/delete)
  • volume manager:主要负责对volume进行mount/unmount
  • volume plugins:包含k8s原生的和各厂商的的存储插件
    • 原生的包括:emptydir、hostpath、flexvolume、csi等
    • 各厂商的包括:aws-ebs、azure、我们的cbs等
  • scheduler:涉及到volume的调度。比如ebs、csi关于单node最大可attach磁盘数量的predicate策略

这里借其他博客的图,展示一下kubernetes的存储架构:

控制器模式是k8s非常重要的概念,一般一个controller会去管理一个或多个API对象,以让对象从实际状态/当前状态趋近于期望状态。

所以attach/detach controller的作用其实就是去attach期望被attach的volume,detach期望被detach的volume。后续结合现网案例(也向社区提交了pr),深入理解下ad controller。

数据结构

对于ad controller来说,理解了其内部的数据结构,再去理解逻辑就事半功倍。ad controller在内存中维护2个数据结构:

  1. actualStateOfWorld —— 表征实际状态(后面简称asw)
  2. desiredStateOfWorld —— 表征期望状态(后面简称dsw)

很明显,对于声明式API来说,是需要随时比对实际状态和期望状态的,所以ad controller中就用了2个数据结构来分别表征实际状态和期望状态。

actualStateOfWorld

actualStateOfWorld 包含2个map:

  • attachedVolumes: 包含了那些ad controller认为被成功attach到nodes上的volumes
  • nodesToUpdateStatusFor: 包含要更新node.Status.VolumesAttached 的nodes
attachedVolumes
如何填充数据?

1、在启动ad controller时,会populate asw,此时会list集群内所有node对象,然后用这些node对象的node.Status.VolumesAttached 去填充attachedVolumes

2、之后只要有需要attach的volume被成功attach了,就会调用MarkVolumeAsAttachedGenerateAttachVolumeFunc 中)来填充到attachedVolumes中

如何删除数据?

1、只有在volume被detach成功后,才会把相关的volume从attachedVolumes中删掉。(GenerateDetachVolumeFunc 中调用MarkVolumeDetached)

nodesToUpdateStatusFor
如何填充数据?

1、detach volume失败后,将volume add back到nodesToUpdateStatusFor

代码语言:txt
复制
- `GenerateDetachVolumeFunc` 中调用`AddVolumeToReportAsAttached`
如何删除数据?

1、在detach volume之前会先调用RemoveVolumeFromReportAsAttachednodesToUpdateStatusFor中先删除该volume相关信息

desiredStateOfWorld

desiredStateOfWorld 中维护了一个map:

nodesManaged:包含被ad controller管理的nodes,以及期望attach到这些node上的volumes。

nodesManaged
如何填充数据?

1、在启动ad controller时,会populate asw,list集群内所有node对象,然后把由ad controller管理的node填充到nodesManaged

2、ad controller的nodeInformer watch到node有更新也会把node填充到nodesManaged

3、另外在populate dsw和podInformer watch到pod有变化(add, update)时,往nodesManaged 中填充volume和pod的信息

4、desiredStateOfWorldPopulator 中也会周期性地去找出需要被add的pod,此时也会把相应的volume和pod填充到nodesManaged

如何删除数据?

1、当删除node时,ad controller中的nodeInformer watch到变化会从dsw的nodesManaged 中删除相应的node

2、当ad controller中的podInformer watch到pod的删除时,会从nodesManaged 中删除相应的volume和pod

3、desiredStateOfWorldPopulator 中也会周期性地去找出需要被删除的pod,此时也会从nodesManaged 中删除相应的volume和pod

流程简述

ad controller的逻辑比较简单:

1、首先,list集群内所有的node和pod,来populate actualStateOfWorld (attachedVolumes )和desiredStateOfWorld (nodesManaged)

2、然后,单独开个goroutine运行reconciler,通过触发attach, detach操作周期性地去reconcile asw(实际状态)和dws(期望状态)

  • 触发attach,detach操作也就是,detach该被detach的volume,attach该被attach的volume

3、之后,又单独开个goroutine运行DesiredStateOfWorldPopulator ,定期去验证dsw中的pods是否依然存在,如果不存在就从dsw中删除

现网案例

接下来结合现网的一个案例,来详细看看reconciler的逻辑。

问题描述
  • 一个statefulsets(sts)引用了多个pvc cbs,我们更新sts时,会删除旧pod,创建新pod,如果新pod调度到和旧pod相同的节点,此时如果有多个cbs被同时调度到某一个node上挂载,就可能会让这些pod一直处于ContainerCreating
现象
  • kubectl describe pod
88565 describe pod.png
88565 describe pod.png
  • kubelet log
88565 kubelet.png
88565 kubelet.png
  • kubectl get node xxx -oyamlvolumesAttachedvolumesInUse
代码语言:txt
复制
volumesAttached:
  - devicePath: /dev/disk/by-id/virtio-disk-6w87j3wv
    name: kubernetes.io/qcloud-cbs/disk-6w87j3wv
volumesInUse:
  - kubernetes.io/qcloud-cbs/disk-6w87j3wv
  - kubernetes.io/qcloud-cbs/disk-7bfqsft5
初步分析
  • 从pod的事件可以看出来:ad controller认为cbs attach成功了,然后kubelet没有mount成功。
  • 但是从kubelet日志却发现Volume not attached according to node status ,也就是说kubelet认为cbs没有按照node的状态去挂载。这个从node info也可以得到证实:volumesAttached 中的确没有这个cbs盘(disk-7bfqsft5)。
  • node info中还有个现象:volumesInUse 中还有这个cbs。说明没有unmount成功

很明显,cbs要能被pod成功使用,需要ad controller和volume manager的协同工作。所以这个问题的定位首先要明确:

  1. volume manager为什么认为volume没有按照node状态挂载,ad controller却认为volume attch成功了?
  2. volumesAttachedvolumesInUse 在ad controller和kubelet之间充当什么角色?

这里只对分析volume manager做简要分析。

  • 根据Volume not attached according to node status 在代码中找到对应的位置,发现在GenerateVerifyControllerAttachedVolumeFunc 中。仔细看代码逻辑,会发现
    • volume manager的reconciler会先确认该被unmount的volume被unmount掉
    • 然后确认该被mount的volume被volume
      • 此时会先从volume manager的dsw缓存中获取要被mount的volumes(volumesToMountpodsToMount
      • 然后遍历,验证每个volumeToMount是否已经attach了
        • 这个volumeToMount是由podManager中的podInformer加入到相应内存中,然后desiredStateOfWorldPopulator周期性同步到dsw中的
      • 验证逻辑中,在GenerateVerifyControllerAttachedVolumeFunc中会去遍历本节点的node.Status.VolumesAttached,如果没有找到就报错(Volume not attached according to node status
  • 所以可以看出来,volume manager就是根据volume是否存在于__node.Status.VolumesAttached 中来判断volume有无被attach成功
  • 那谁去填充node.Status.VolumesAttached ?ad controller的数据结构nodesToUpdateStatusFor 就是用来存储要更新到node.Status.VolumesAttached 上的数据的。
  • 所以,如果ad controller那边没有更新node.Status.VolumesAttached,而又新建了pod,__desiredStateOfWorldPopulator 从podManager中的内存把新建pod引用的volume同步到了volumesToMount中,在验证volume是否attach时,就会报错(Volume not attached according to node status)
    • 当然,之后由于kublet的syncLoop里面会调用WaitForAttachAndMount 去等待volumeattach和mount成功,由于前面一直无法成功,等待超时,才会有会面timeout expired 的报错

所以接下来主要需要看为什么ad controller那边没有更新node.Status.VolumesAttached

详细分析

接下来详细分析下ad controller的逻辑,看看为什么会没有更新node.Status.VolumesAttached,但从事件看ad controller却又认为volume已经挂载成功。

reconciler详细流程

流程简述中表述可见,ad controller主要逻辑是在reconciler中。

  • reconciler定时去运行reconciliationLoopFunc,周期为100ms。
  • reconciliationLoopFunc的主要逻辑在reconcile()中:

1、 首先,确保该被detach的volume被detach掉

代码语言:txt
复制
 - 遍历asw中的`attachedVolumes`,对于每个volume,判断其是否存在于dsw中
   - 去dsw.nodesManaged中判断node和volume是否存在
   - 存在的话,再判断volume是否存在
 - 如果volume存在于asw,且不存在于dsw,则意味着需要进行detach
 - 之后,根据`node.Status.VolumesInUse`来判断volume是否已经unmount完成,unmount完成或者等待6min timeout时间到后,会继续detach逻辑
 - 在执行detach volume之前,会先调用`RemoveVolumeFromReportAsAttached`从asw的`nodesToUpdateStatusFor`中去删除要detach的volume
 - 然后patch node,也就等于从`node.status.VolumesAttached`删除这个volume
 - 之后进行detach,detach失败主要分2种
   - 如果真正执行了`volumePlugin`的具体实现`DetachVolume`失败,会把volume add back到`nodesToUpdateStatusFor`(之后在attach逻辑结束后,会再次patch node)
   - 如果是operator_excutor判断还没到backoff周期,就会返回`backoffError`,直接跳过`DetachVolume`
 - backoff周期起始为500ms,之后指数递增至2min2s。已经detach失败了的volume,在每个周期期间进入detach逻辑都会直接返回`backoffError`

2、之后,确保该被attach的volume被attach成功

代码语言:txt
复制
 - 遍历dsw的`nodesManaged`,判断volume是否已经被attach到该node,如果已经被attach到该node,则跳过attach操作
    - 去asw.attachedVolumes中判断是否存在,若不存在就认为没有attach到node
    - 若存在,再判断node,node也匹配就返回`attachedConfirmed`
    - 而`attachedConfirmed`是由asw中`AddVolumeNode`去设置的,`MarkVolumeAsAttached`设置为true。(true即代表该volume已经被attach到该node了)
 - 之后判断是否禁止多挂载,再由operator_excutor去执行attach

3、 最后,UpdateNodeStatuses去更新node status

案例分析
  • 前提
    • 单个cvm不支持并发attach/detach多块cbs
    • sts+cbs(pvc),pod recreate前后调度到相同的node
  • 首先,删除sts,pod被删除时,由于pod引用多块cbs,而cbs又不支持并发detach,所以必定有cbs detach失败,失败后就会backoff重试。
    1. 由于detach失败,该volume也不会从asw的attachedVolumes中删除
  • 由于detach时,
    1. 先从node.status.VolumesAttached中删除volume,之后才去执行detach
    2. detach时返回backoffError不会把该volumeadd back node.status.VolumesAttached
  • 之后,我们在backoff周期中(假如就为第一个周期的500ms中间)再次创建sts,pod被调度到之前的node
  • 而pod一旦被创建,就会被添加到dsw的nodesManaged
  • reconciler详细流程中的第2步,会去判断volume是否被attach,此时发现该volume同时存在于asw和dws中,并且由于detach失败,也会在检测时发现还attach,从而设置attachedConfirmed为true
  • ad controller就认为该volume被attach成功了
  • 之后再一次进入reconciler详细流程中第1步的detach逻辑进行判断时,发现要detach的volume已经存在于dsw.nodesManaged了,这样volume同时存在于asw和dsw中了,实际状态和期望状态一致,被认为就不需要进行detach了。
  • 这样,该volume之后就再也不会被add back到node.status.VolumesAttached。所以就出现了现象中的node info中没有该volume,而ad controller又认为该volume被attach成功了
  • 由于kubelet(volume manager)与controller manager是异步的,而它们之间交互是依据node.status.VolumesAttached ,所以volume manager在验证volume是否attach成功,发现node.status.VolumesAttached中没有这个voume,也就认为没有attach成功,所以就有了现象中的报错Volume not attached according to node status
  • 之后kubelet的syncPod在等待pod所有的volume attach和mount成功时,就超时了(现象中的另一个报错timeout expired wating...)。
  • 所以pod一直处于ContainerCreating
小结
  • 所以,该案例出现的原因是:
    • sts+cbs,使用多块cbs的pod recreate时间被调度到相同的node
    • 由于detach失败,backoff期间创建sts,导致ad controller中的dsw和asw数据一致,从而认为volume被attach成功,不再需要被detach。
    • 又由于detach时,是先从node.status.VolumesAttached中删除该volume,再去执行真正的DetachVolume。backoff期间直接返回backoffError,跳过DetachVolume,不会add back
    • 所以ad controller认为volume不再需要被attach时,node.status.VolumesAttached中就永远没有了该volume
    • 而kubelet与ad controller交互就通过node.status.VolumesAttached,所以kubelet认为没有attach成功,新创建的pod就一直处于ContianerCreating
  • 据此,我们可以发现关键点在于node.status.VolumesAttached和以下两个逻辑:
    1. detach时backoffError,不会add back
    2. detach是先删除,失败再add back
  • 所以只要想办法能在任何情况下add back就不会有问题了。根据以上两个逻辑就对应有2种解决方案:
    1. backoffError时,也add back
    2. 一进入detach逻辑就判断是否backoffError(处于backoff周期中),是就跳过之后所有detach逻辑,不删除就不需要add back了。
  • 当然这两个解决方案,我们团队都有实现,都有提到kubernetes社区:
    1. pr #72914
      • 有个缺点:patch node的请求数增加了10+次/(s * volume)
    2. pr #88572

总结

  • AD Controller负责存储的Attach、Detach。通过比较asw和dsw来判断是否需要attach/detach。最终attach和detach结果会体现在node.status.VolumesAttached
  • 以上描述的现网案例,有一定前提:
    • pod重建前后调度到相同node
    • 需要在detach的backoff周期中重新创建pod
  • 需要强调的是:现网案例的现象,不只是cbs VolumePlugin的问题,而是一个VolumePlugins共有的问题。理论上,只要磁盘还存在,任何原因导致的detach失败都会造成该现象。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 数据结构
    • actualStateOfWorld
      • attachedVolumes
      • nodesToUpdateStatusFor
    • desiredStateOfWorld
      • nodesManaged
  • 流程简述
  • 现网案例
    • 问题描述
      • 现象
        • 初步分析
          • 详细分析
            • reconciler详细流程
            • 案例分析
          • 小结
          • 总结
          相关产品与服务
          容器服务
          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档