您好,登錄后才能下訂單哦!
這期內(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)象;然后去深入理解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
。
kubectl describe pod
kubelet log
kubectl get node xxx -oyaml
的volumesAttached
和volumesInUse
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中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策略
控制器模式是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。
對(duì)于ad controller來說,理解了其內(nèi)部的數(shù)據(jù)結(jié)構(gòu),再去理解邏輯就事半功倍。ad controller在內(nèi)存中維護(hù)2個(gè)數(shù)據(jù)結(jié)構(gòu):
actualStateOfWorld
—— 表征實(shí)際狀態(tài)(后面簡(jiǎn)稱asw)
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
包含2個(gè)map:
attachedVolumes
: 包含了那些ad controller認(rèn)為被成功attach到nodes上的volumes
nodesToUpdateStatusFor
: 包含要更新node.Status.VolumesAttached
的nodes
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)用MarkVolumeAsAttached
(GenerateAttachVolumeFunc
中)來填充到attachedVolumes中
。
1、只有在volume被detach成功后,才會(huì)把相關(guān)的volume從attachedVolumes
中刪掉。(GenerateDetachVolumeFunc
中調(diào)用MarkVolumeDetached
)
1、detach volume失敗后,將volume add back到nodesToUpdateStatusFor
- GenerateDetachVolumeFunc
中調(diào)用AddVolumeToReportAsAttached
1、在detach volume之前會(huì)先調(diào)用RemoveVolumeFromReportAsAttached
從nodesToUpdateStatusFor
中先刪除該volume相關(guān)信息
desiredStateOfWorld
中維護(hù)了一個(gè)map:
nodesManaged
:包含被ad controller管理的nodes,以及期望attach到這些node上的volumes。
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
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)單:
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中刪除
接下來結(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è)問題的定位首先要明確:
volume manager為什么認(rèn)為volume沒有按照node狀態(tài)掛載,ad controller卻認(rèn)為volume attch成功了?
volumesAttached
和volumesInUse
在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(volumesToMount
的podsToMount
)
然后遍歷,驗(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
。
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
首先,確保該被detach的volume被detach掉
之后,確保該被attach的volume被attach成功
最后,UpdateNodeStatuses
去更新node status
前提
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è)過程:
首先,刪除pod時(shí),由于某種原因cbs detach失敗,失敗后就會(huì)backoff重試。
由于detach失敗,該volume也不會(huì)從asw的attachedVolumes
中刪除
由于detach時(shí),
先從node.status.VolumesAttached
中刪除volume,之后才去執(zhí)行detach
detach時(shí)返回backoffError
不會(huì)把該volumeadd back node.status.VolumesAttached
之后,我們?cè)赽ackoff周期中(假如就為第一個(gè)周期的500ms中間)再次創(chuàng)建sts,pod被調(diào)度到之前的node
而pod一旦被創(chuàng)建,就會(huì)被添加到dsw的nodesManaged
(nodeName和volumeName都沒變)
reconcile()中的第2步,會(huì)去判斷volume是否被attach,此時(shí)發(fā)現(xiàn)該volume同時(shí)存在于asw和dws中,并且由于detach失敗,也會(huì)在檢測(cè)時(shí)發(fā)現(xiàn)還是attach,從而設(shè)置attachedConfirmed
為true
ad controller就認(rèn)為該volume被attach成功了
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了。
這樣,該volume之后就再也不會(huì)被add back到node.status.VolumesAttached
。所以就出現(xiàn)了現(xiàn)象中的node info中沒有該volume,而ad controller又認(rèn)為該volume被attach成功了
由于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
之后kubelet的syncPod
在等待pod所有的volume attach和mount成功時(shí),就超時(shí)了(現(xiàn)象中的另一個(gè)報(bào)錯(cuò)timeout expired wating...
)。
所以pod一直處于ContainerCreating
所以,該案例出現(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è)邏輯:
detach時(shí)backoffError,不會(huì)add back
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
一進(jìn)入detach邏輯就判斷是否backoffError(處于backoff周期中),是就跳過之后所有detach邏輯,不刪除就不需要add back了。
backoffError時(shí),也add back
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è)資訊頻道。
免責(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)容。