溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Kubernetes attach/detach controller邏輯漏洞致使pod啟動(dòng)失敗該怎么辦

發(fā)布時(shí)間:2021-12-01 16:31:42 來源:億速云 閱讀:140 作者:柒染 欄目:云計(jì)算

這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)碛嘘P(guān)Kubernetes attach/detach controller邏輯漏洞致使pod啟動(dòng)失敗該怎么辦,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

前言

通過深入學(xué)習(xí)k8s attach/detach controller源碼,了解現(xiàn)網(wǎng)案例發(fā)現(xiàn)的attach/detach controller bug發(fā)生的原委,并給出解決方案。


現(xiàn)網(wǎng)案例現(xiàn)象

我們首先了解下現(xiàn)網(wǎng)案例的問題和現(xiàn)象;然后去深入理解ad controller維護(hù)的數(shù)據(jù)結(jié)構(gòu);之后根據(jù)數(shù)據(jù)結(jié)構(gòu)與ad controller的代碼邏輯,再來詳細(xì)分析現(xiàn)網(wǎng)案例出現(xiàn)的原因和解決方案。從而深入理解整個(gè)ad controller。

問題描述
  • 一個(gè)statefulsets(sts)引用了多個(gè)pvc cbs,我們更新sts時(shí),刪除舊pod,創(chuàng)建新pod,此時(shí)如果刪除舊pod時(shí)cbs detach失敗,且創(chuàng)建的新pod調(diào)度到和舊pod相同的節(jié)點(diǎn),就可能會(huì)讓這些pod一直處于ContainerCreating

現(xiàn)象
  • kubectl describe pod

Kubernetes attach/detach controller邏輯漏洞致使pod啟動(dòng)失敗該怎么辦

  • kubelet log

Kubernetes attach/detach controller邏輯漏洞致使pod啟動(dòng)失敗該怎么辦

  • kubectl get node xxx -oyamlvolumesAttachedvolumesInUse

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

k8s存儲(chǔ)簡(jiǎn)述

k8s中attach/detach controller負(fù)責(zé)存儲(chǔ)插件的attach/detach。本文結(jié)合現(xiàn)網(wǎng)出現(xiàn)的一個(gè)案例來分析ad controller的源碼邏輯,該案例是因k8s的ad controller bug導(dǎo)致的pod創(chuàng)建失敗。

k8s中涉及存儲(chǔ)的組件主要有:attach/detach controller、pv controller、volume manager、volume plugins、scheduler。每個(gè)組件分工明確:

  • attach/detach controller:負(fù)責(zé)對(duì)volume進(jìn)行attach/detach

  • pv controller:負(fù)責(zé)處理pv/pvc對(duì)象,包括pv的provision/delete(cbs intree的provisioner設(shè)計(jì)成了external provisioner,獨(dú)立的cbs-provisioner來負(fù)責(zé)cbs pv的provision/delete)

  • volume manager:主要負(fù)責(zé)對(duì)volume進(jìn)行mount/unmount

  • volume plugins:包含k8s原生的和各廠商的的存儲(chǔ)插件

    • 原生的包括:emptydir、hostpath、flexvolume、csi等

    • 各廠商的包括:aws-ebs、azure、我們的cbs等

  • scheduler:涉及到volume的調(diào)度。比如對(duì)ebs、csi等的單node最大可attach磁盤數(shù)量的predicate策略

Kubernetes attach/detach controller邏輯漏洞致使pod啟動(dòng)失敗該怎么辦

控制器模式是k8s非常重要的概念,一般一個(gè)controller會(huì)去管理一個(gè)或多個(gè)API對(duì)象,以讓對(duì)象從實(shí)際狀態(tài)/當(dāng)前狀態(tài)趨近于期望狀態(tài)。

所以attach/detach controller的作用其實(shí)就是去attach期望被attach的volume,detach期望被detach的volume。

后續(xù)attach/detach controller簡(jiǎn)稱ad controller。

ad controller數(shù)據(jù)結(jié)構(gòu)

對(duì)于ad controller來說,理解了其內(nèi)部的數(shù)據(jù)結(jié)構(gòu),再去理解邏輯就事半功倍。ad controller在內(nèi)存中維護(hù)2個(gè)數(shù)據(jù)結(jié)構(gòu):

  1. actualStateOfWorld —— 表征實(shí)際狀態(tài)(后面簡(jiǎn)稱asw)

  2. desiredStateOfWorld —— 表征期望狀態(tài)(后面簡(jiǎn)稱dsw)

很明顯,對(duì)于聲明式API來說,是需要隨時(shí)比對(duì)實(shí)際狀態(tài)和期望狀態(tài)的,所以ad controller中就用了2個(gè)數(shù)據(jù)結(jié)構(gòu)來分別表征實(shí)際狀態(tài)和期望狀態(tài)。

actualStateOfWorld

actualStateOfWorld 包含2個(gè)map:

  • attachedVolumes: 包含了那些ad controller認(rèn)為被成功attach到nodes上的volumes

  • nodesToUpdateStatusFor: 包含要更新node.Status.VolumesAttached 的nodes

attachedVolumes

如何填充數(shù)據(jù)?

1、在啟動(dòng)ad controller時(shí),會(huì)populate asw,此時(shí)會(huì)list集群內(nèi)所有node對(duì)象,然后用這些node對(duì)象的node.Status.VolumesAttached 去填充attachedVolumes。

2、之后只要有需要attach的volume被成功attach了,就會(huì)調(diào)用MarkVolumeAsAttachedGenerateAttachVolumeFunc 中)來填充到attachedVolumes中。

如何刪除數(shù)據(jù)?

1、只有在volume被detach成功后,才會(huì)把相關(guān)的volume從attachedVolumes中刪掉。(GenerateDetachVolumeFunc 中調(diào)用MarkVolumeDetached)

nodesToUpdateStatusFor

如何填充數(shù)據(jù)?

1、detach volume失敗后,將volume add back到nodesToUpdateStatusFor

- GenerateDetachVolumeFunc 中調(diào)用AddVolumeToReportAsAttached

如何刪除數(shù)據(jù)?

1、在detach volume之前會(huì)先調(diào)用RemoveVolumeFromReportAsAttachednodesToUpdateStatusFor中先刪除該volume相關(guān)信息

desiredStateOfWorld

desiredStateOfWorld 中維護(hù)了一個(gè)map:

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

nodesManaged

如何填充數(shù)據(jù)?

1、在啟動(dòng)ad controller時(shí),會(huì)populate asw,list集群內(nèi)所有node對(duì)象,然后把由ad controller管理的node填充到nodesManaged

2、ad controller的nodeInformer watch到node有更新也會(huì)把node填充到nodesManaged

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

4、desiredStateOfWorldPopulator 中也會(huì)周期性地去找出需要被add的pod,此時(shí)也會(huì)把相應(yīng)的volume和pod填充到nodesManaged

如何刪除數(shù)據(jù)?

1、當(dāng)刪除node時(shí),ad controller中的nodeInformer watch到變化會(huì)從dsw的nodesManaged 中刪除相應(yīng)的node

2、當(dāng)ad controller中的podInformer watch到pod的刪除時(shí),會(huì)從nodesManaged 中刪除相應(yīng)的volume和pod

3、desiredStateOfWorldPopulator 中也會(huì)周期性地去找出需要被刪除的pod,此時(shí)也會(huì)從nodesManaged 中刪除相應(yīng)的volume和pod

ad controller流程簡(jiǎn)述

ad controller的邏輯比較簡(jiǎn)單:

1、首先,list集群內(nèi)所有的node和pod,來populate actualStateOfWorld (attachedVolumes )和desiredStateOfWorld (nodesManaged)

2、然后,單獨(dú)開個(gè)goroutine運(yùn)行reconciler,通過觸發(fā)attach, detach操作周期性地去reconcile asw(實(shí)際狀態(tài))和dws(期望狀態(tài))

  • 觸發(fā)attach,detach操作也就是,detach該被detach的volume,attach該被attach的volume

3、之后,又單獨(dú)開個(gè)goroutine運(yùn)行DesiredStateOfWorldPopulator ,定期去驗(yàn)證dsw中的pods是否依然存在,如果不存在就從dsw中刪除

現(xiàn)網(wǎng)案例

接下來結(jié)合上面所說的現(xiàn)網(wǎng)案例,來詳細(xì)看看reconciler的邏輯。

案例初步分析
  • 從pod的事件可以看出來:ad controller認(rèn)為cbs attach成功了,然后kubelet沒有mount成功。

  • 但是從kubelet日志卻發(fā)現(xiàn)Volume not attached according to node status ,也就是說kubelet認(rèn)為cbs沒有按照node的狀態(tài)去掛載。這個(gè)從node info也可以得到證實(shí):volumesAttached 中的確沒有這個(gè)cbs盤(disk-7bfqsft5)。

  • node info中還有個(gè)現(xiàn)象:volumesInUse 中還有這個(gè)cbs。說明沒有unmount成功

很明顯,cbs要能被pod成功使用,需要ad controller和volume manager的協(xié)同工作。所以這個(gè)問題的定位首先要明確:

  1. volume manager為什么認(rèn)為volume沒有按照node狀態(tài)掛載,ad controller卻認(rèn)為volume attch成功了?

  2. volumesAttachedvolumesInUse 在ad controller和kubelet之間充當(dāng)什么角色?

這里只對(duì)分析volume manager做簡(jiǎn)要分析。

  • 根據(jù)Volume not attached according to node status 在代碼中找到對(duì)應(yīng)的位置,發(fā)現(xiàn)在GenerateVerifyControllerAttachedVolumeFunc 中。仔細(xì)看代碼邏輯,會(huì)發(fā)現(xiàn)

    • 此時(shí)會(huì)先從volume manager的dsw緩存中獲取要被mount的volumes(volumesToMountpodsToMount

    • 然后遍歷,驗(yàn)證每個(gè)volumeToMount是否已經(jīng)attach了

    • 驗(yàn)證邏輯中,在GenerateVerifyControllerAttachedVolumeFunc中會(huì)去遍歷本節(jié)點(diǎn)的node.Status.VolumesAttached,如果沒有找到就報(bào)錯(cuò)(Volume not attached according to node status

    • 這個(gè)volumeToMount是由podManager中的podInformer加入到相應(yīng)內(nèi)存中,然后desiredStateOfWorldPopulator周期性同步到dsw中的

    • volume manager的reconciler會(huì)先確認(rèn)該被unmount的volume被unmount掉

    • 然后確認(rèn)該被mount的volume被mount

  • 所以可以看出來,volume manager就是根據(jù)volume是否存在于node.Status.VolumesAttached 中來判斷volume有無被attach成功。

  • 那誰去填充node.Status.VolumesAttached ?ad controller的數(shù)據(jù)結(jié)構(gòu)nodesToUpdateStatusFor 就是用來存儲(chǔ)要更新到node.Status.VolumesAttached 上的數(shù)據(jù)的。

  • 所以,如果ad controller那邊沒有更新node.Status.VolumesAttached,而又新建了pod,desiredStateOfWorldPopulator 從podManager中的內(nèi)存把新建pod引用的volume同步到了volumesToMount中,在驗(yàn)證volume是否attach時(shí),就會(huì)報(bào)錯(cuò)(Volume not attached according to node status)

    • 當(dāng)然,之后由于kublet的syncLoop里面會(huì)調(diào)用WaitForAttachAndMount 去等待volumeattach和mount成功,由于前面一直無法成功,等待超時(shí),才會(huì)有會(huì)面timeout expired 的報(bào)錯(cuò)

所以接下來主要需要看為什么ad controller那邊沒有更新node.Status.VolumesAttached。

ad controller的reconciler詳解

接下來詳細(xì)分析下ad controller的邏輯,看看為什么會(huì)沒有更新node.Status.VolumesAttached,但從事件看ad controller卻又認(rèn)為volume已經(jīng)掛載成功。

從流程簡(jiǎn)述中表述可見,ad controller主要邏輯是在reconciler中。

  • reconciler定時(shí)去運(yùn)行reconciliationLoopFunc,周期為100ms。

  • reconciliationLoopFunc的主要邏輯在reconcile()中:

    • 遍歷dsw的nodesManaged,判斷volume是否已經(jīng)被attach到該node,如果已經(jīng)被attach到該node,則跳過attach操作

    • 去asw.attachedVolumes中判斷是否存在,若不存在就認(rèn)為沒有attach到node

    • attachedConfirmed是由asw中AddVolumeNode去設(shè)置的,MarkVolumeAsAttached設(shè)置為true。(true即代表該volume已經(jīng)被attach到該node了)

    • 若存在,再判斷node,node也匹配就返回attachedConfirmed

    • 之后判斷是否禁止多掛載,再由operator_excutor去執(zhí)行attach

    • 遍歷asw中的attachedVolumes,對(duì)于每個(gè)volume,判斷其是否存在于dsw中

    • 如果volume存在于asw,且不存在于dsw,則意味著需要進(jìn)行detach

    • 之后,根據(jù)node.Status.VolumesInUse來判斷volume是否已經(jīng)unmount完成,unmount完成或者等待6min timeout時(shí)間到后,會(huì)繼續(xù)detach邏輯

    • 在執(zhí)行detach volume之前,會(huì)先調(diào)用RemoveVolumeFromReportAsAttached從asw的nodesToUpdateStatusFor中去刪除要detach的volume

    • 然后patch node,也就等于從node.status.VolumesAttached刪除這個(gè)volume

    • 之后進(jìn)行detach,detach失敗主要分2種

    • backoff周期起始為500ms,之后指數(shù)遞增至2min2s。已經(jīng)detach失敗了的volume,在每個(gè)周期期間進(jìn)入detach邏輯都會(huì)直接返回backoffError

    • 根據(jù)nodeName去dsw.nodesManaged中判斷node是否存在

    • 存在的話,再根據(jù)volumeName判斷volume是否存在

    • 如果真正執(zhí)行了volumePlugin的具體實(shí)現(xiàn)DetachVolume失敗,會(huì)把volume add back到nodesToUpdateStatusFor(之后在attach邏輯結(jié)束后,會(huì)再次patch node)

    • 如果是operator_excutor判斷還沒到backoff周期,就會(huì)返回backoffError,直接跳過DetachVolume

    1. 首先,確保該被detach的volume被detach掉

    2. 之后,確保該被attach的volume被attach成功

    3. 最后,UpdateNodeStatuses去更新node status

案例詳細(xì)分析
  • 前提

    • volume detach失敗

    • sts+cbs(pvc),pod recreate前后調(diào)度到相同的node

  • 涉及k8s組件

    • ad controller

    • kubelet(volume namager)

  • ad controller和kubelet(volume namager)通過字段node.status.VolumesAttached交互。

    • ad controller為node.status.VolumesAttached新增或刪除volume,新增表明已掛載,刪除表明已刪除

    • kubelet(volume manager)需要驗(yàn)證新建pod中的(pvc的)volume是否掛載成功,存在于node.status.VolumesAttached中,則表明驗(yàn)證volume已掛載成功;不存在,則表明還未掛載成功。

  • 以下是整個(gè)過程:

  1. 首先,刪除pod時(shí),由于某種原因cbs detach失敗,失敗后就會(huì)backoff重試。

    1. 由于detach失敗,該volume也不會(huì)從asw的attachedVolumes中刪除

  2. 由于detach時(shí),

    1. 先從node.status.VolumesAttached中刪除volume,之后才去執(zhí)行detach

    2. detach時(shí)返回backoffError不會(huì)把該volumeadd back node.status.VolumesAttached

  3. 之后,我們?cè)赽ackoff周期中(假如就為第一個(gè)周期的500ms中間)再次創(chuàng)建sts,pod被調(diào)度到之前的node

  4. 而pod一旦被創(chuàng)建,就會(huì)被添加到dsw的nodesManaged(nodeName和volumeName都沒變)

  5. reconcile()中的第2步,會(huì)去判斷volume是否被attach,此時(shí)發(fā)現(xiàn)該volume同時(shí)存在于asw和dws中,并且由于detach失敗,也會(huì)在檢測(cè)時(shí)發(fā)現(xiàn)還是attach,從而設(shè)置attachedConfirmed為true

  6. ad controller就認(rèn)為該volume被attach成功了

  7. reconcile()中第1步的detach邏輯進(jìn)行判斷時(shí),發(fā)現(xiàn)要detach的volume已經(jīng)存在于dsw.nodesManaged了(由于nodeName和volumeName都沒變),這樣volume同時(shí)存在于asw和dsw中了,實(shí)際狀態(tài)和期望狀態(tài)一致,被認(rèn)為就不需要進(jìn)行detach了。

  8. 這樣,該volume之后就再也不會(huì)被add back到node.status.VolumesAttached。所以就出現(xiàn)了現(xiàn)象中的node info中沒有該volume,而ad controller又認(rèn)為該volume被attach成功了

  9. 由于kubelet(volume manager)與controller manager是異步的,而它們之間交互是依據(jù)node.status.VolumesAttached ,所以volume manager在驗(yàn)證volume是否attach成功,發(fā)現(xiàn)node.status.VolumesAttached中沒有這個(gè)voume,也就認(rèn)為沒有attach成功,所以就有了現(xiàn)象中的報(bào)錯(cuò)Volume not attached according to node status

  10. 之后kubelet的syncPod在等待pod所有的volume attach和mount成功時(shí),就超時(shí)了(現(xiàn)象中的另一個(gè)報(bào)錯(cuò)timeout expired wating...)。

  11. 所以pod一直處于ContainerCreating

小結(jié)
  • 所以,該案例出現(xiàn)的原因是:

    • sts+cbs,pod recreate時(shí)間被調(diào)度到相同的node

    • 由于detach失敗,backoff期間創(chuàng)建sts/pod,致使ad controller中的dsw和asw數(shù)據(jù)一致(此時(shí)該volume由于沒有被detach成功而確實(shí)處于attach狀態(tài)),從而導(dǎo)致ad controller認(rèn)為不再需要去detach該volume。

    • 又由于detach時(shí),是先從node.status.VolumesAttached中刪除該volume,再去執(zhí)行真正的DetachVolume。backoff期間直接返回backoffError,跳過DetachVolume,不會(huì)add back

    • 之后,ad controller因volume已經(jīng)處于attach狀態(tài),認(rèn)為不再需要被attach,就不會(huì)再向node.status.VolumesAttached中添加該volume

    • 最后,kubelet與ad controller交互就通過node.status.VolumesAttached,所以kubelet認(rèn)為沒有attach成功,新創(chuàng)建的pod就一直處于ContianerCreating

  • 據(jù)此,我們可以發(fā)現(xiàn)關(guān)鍵點(diǎn)在于node.status.VolumesAttached和以下兩個(gè)邏輯:

    1. detach時(shí)backoffError,不會(huì)add back

    2. detach是先刪除,失敗再add back

  • 所以只要想辦法能在任何情況下add back就不會(huì)有問題了。根據(jù)以上兩個(gè)邏輯就對(duì)應(yīng)有以下2種解決方案,推薦使用方案2

    • 這個(gè)方案能避免方案1的問題,且會(huì)進(jìn)一步減少請(qǐng)求apiserver的次數(shù),且改動(dòng)也不多

    • pr #88572

    • 但這種方式有個(gè)缺點(diǎn):patch node的請(qǐng)求數(shù)增加了10+次/(s * volume)

    • pr #72914

    1. 一進(jìn)入detach邏輯就判斷是否backoffError(處于backoff周期中),是就跳過之后所有detach邏輯,不刪除就不需要add back了。

    1. backoffError時(shí),也add back

總結(jié)

  • AD Controller負(fù)責(zé)存儲(chǔ)的Attach、Detach。通過比較asw和dsw來判斷是否需要attach/detach。最終attach和detach結(jié)果會(huì)體現(xiàn)在node.status.VolumesAttached。

  • 以上現(xiàn)網(wǎng)案例出現(xiàn)的現(xiàn)象,是k8s ad controller的bug導(dǎo)致,目前社區(qū)并未修復(fù)。

    • 先刪除舊pod過程中detach失敗,而在detach失敗的backoff周期中創(chuàng)建新pod,此時(shí)由于ad controller邏輯bug,導(dǎo)致volume被從node.status.VolumesAttached中刪除,從而導(dǎo)致創(chuàng)建新pod時(shí),kubelet檢查時(shí)認(rèn)為該volume沒有attach成功,致使pod就一直處于ContianerCreating

    • 現(xiàn)象出現(xiàn)的原因主要是:

    • 而現(xiàn)象的解決方案,推薦使用pr #88572。目前TKE已經(jīng)有該方案的穩(wěn)定運(yùn)行版本,在灰度中。

上述就是小編為大家分享的Kubernetes attach/detach controller邏輯漏洞致使pod啟動(dòng)失敗該怎么辦了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI