Pod掛載Volume失敗問題分析

沃趣科技發表於2018-10-23

Kubernetes環境偶爾出現StatefulSet中的Pod被刪除,新啟動的Pod(還是排程到原有節點)掛載volume失敗的問題,如下圖,經過一番定位分析,也讓我們對於Kubernetes系統複雜程度有了新的認知。

在分析此問題之前,作為相關背景知識,先簡單介紹對於Kubernetes儲存系統的理解。


儲存系統簡析

儲存也是Kubernetes中比較重要而複雜的系統,功能比較龐大,涉及到不同元件中,不同控制器的協作,如下圖。


從三個維度分析:


1、從卷的生命週期來講,卷被Pod使用或者卷被回收,會依賴順序嚴格的幾個階段

卷被Pod使用:

  • provision,卷分配成功

  • attach,卷掛載在對應worker node

  • mount,卷掛載為檔案系統並且對映給對應Pod


卷要成功被Pod使用,需要遵循以上順序。

卷被回收:

  • umount,卷已經和對應worker node解除對映,且已經從檔案系統umount

  • detach,卷已經從worker node解除安裝

  • recycle,卷被回收


卷要成功回收,需要遵循以上順序。

2、從Kubernetes儲存系統來講,卷生命週期管理的職責,又分散於不同的控制器中

  • pv controller,負責建立和回收卷

  • attach detach controller,負責掛載和解除安裝卷

  • volume manager,負責mount和umount卷


3、controller模式,每個controller負責特定功能,並且不斷進行reconcile(對比期望狀態與實際狀態),然後進行調整

比如attach detach controller和volume manager中,各自都會有desiredStateOfWorld(快取期望狀態)和actualStateOfWorld(快取實際狀態)快取,並且由各自的syncLoopFunc不斷對比兩個快取的差異,並進行調整,下文中會介紹。

for {
    desired := getDesiredState();
    current := getCurrentState();
    makeChanges(desired, current);
}

結合以上三個維度,Kubernetes需要保證卷的管理功能分佈在不同控制器的前提下保證卷生命週期順序的正確性。以Pod使用卷為例,看Kubernetes是如何做到這一點?


Pod啟動流程

假設scheduler已經完成worker node選擇,確定排程的節點,此時啟動Pod前,需要先完成卷對映到Pod路徑中,結合前面的分析,整個過程如下:


1、卷分配,pvc繫結pv,由pv controller負責完成,結合相關程式碼1[1]。


此時pvc繫結pv。


2、attach detach controlle,如果pod分配到worker node,並且對應卷已經建立,則將卷掛載到對應worker node,並且給worker node資源增加volume已經掛載對應卷的狀態資訊,結合相關程式碼2[2]和程式碼3[3]。


此時對應node資源狀態中增加volume資訊。

[root@10-10-88-152 ~]# kubectl get nodes 10-10-88-113 -o yaml 
apiVersion: v1
kind: Node
....
volumesAttached:
- devicePath: csi-add9fc778d9593d01818d65ccde7013e87327d9f675b47df42a34b860c581711
name: kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-4faa18f5bbbd11e8-1365
- devicePath: csi-5dd249387138238e8e2209eb471450a072dd6543adde7a6769c8461943c789ca
name: kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-4fa9b764bbbd11e8-1366
- devicePath: csi-bc9b81e32d84e8890d17568964c1e01af97b0c175e0b73d4bf30bba54e3f1a1e
name: kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-4fa94533bbbd11e8-1364
volumesInUse:
- kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-4fa94533bbbd11e8-1364
- kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-4fa9b764bbbd11e8-1366
- kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-4faa18f5bbbd11e8-1365


3、volume manager在worker node中負責將卷掛載到對應路徑。


Pod分配到本worker node後,獲取Pod需要的volume,通過對比node狀態中的volumesAttached,確認volume是否已經attach到node節點,如果attach到node節點則將自身actualStateOfWorld中的volume狀態設定成attached,對應程式碼4[4]、程式碼5[5]。


如果已經attached成功,則進入到檔案系統掛載流程,相關程式碼6[6]。


  • 先掛載到node中全域性路徑,比如/var/lib/kubelet/plugins/kubernetes.io/csi/pv/pvc-3ecd68c7b7d211e8/globalmount。

  • 對映到Pod對應路徑,比如/var/lib/kubelet/pods/49a5fede-b811-11e8-844f-fa7378845e00/volumes/kubernetes.io~csi/pvc-3ecd68c7b7d211e8/mount。

  • actualStateOfWorld中設定volume為掛載成功狀態。

4、pod controller確認卷已經對映成功,啟動Pod,此處不詳細展開。


| Pod被刪除的過程

pod controller watch到pod處於被刪除狀態,執行killPod操作,刪除Pod,此處不詳細展開。


volume manager獲取到Pod被刪除的資訊,會執行如下幾步,相關程式碼5[5]。

  • 將Pod從desiredStateOfWorld的快取資訊中清除。


  • actualStateOfWorld中已經掛載的卷和desiredStateOfWorld發現Pod不應該掛載,執行UmountVolume操作,將Pod和卷對映關係解除,並將Pod從actualStateOfWorld的卷資訊中剔除。


  • 此時如果實際狀態中卷沒有關聯任何Pod,則說明卷需要可以完全與節點分離,則先執行UnmountDevice將卷的globalpath umount掉,等到下次reconcile時執行MarkVolumeAsDetached將卷完全從實際狀態中刪除掉。



attach detach controller發現掛載到node節點的volume沒有被Pod使用,執行detach操作,將卷從node節點detach,此時卷完全處於叢集中未被使用的狀態,此處不詳細展開。


總結為Kubernetes儲存系統的特點:

  • 不同元件通過資源狀態協作,attach detach controller需要PVC繫結PV的狀態,volume manager需要node status中volume attached狀態。

  • 元件通過reconcile方式達到期望狀態,並且狀態可能需要多次reconcile中完成,如Pod清除掉後,volume最終和node分離。


問題

理解了儲存系統的整體過程之後,回到問題,StatefulSet中Pod被刪除會發生什麼?

首先,對於StatefulSet的瞭解,Pod被刪除,StatefulSet controller應該會很快建立Pod,在我們的場景中,Pod還是排程到先前節點中啟動。結合對儲存的理解,可能的場景:

場景一:delete Pod,感知順序為volume manager(umount)->statefulset->scheduler->volume manager(mount)。

  1. volume manager發現Pod被刪除,執行umount

  2. StatefulSet發現Pod被刪除,馬上建立Pod

  3. scheduler發現Pod進行排程

  4. volume manager發現原有volume需要繫結Pod,執行mount


場景二:delete Pod,感知順序為volume manager(umount)->volume manager(unmountDevice)->volume manager(MarkVolumeAsDelete)->attach detach controller(Detach)->statefulset->scheduler->attach detach controller(Attach)->volume manager。

  1. volume manager發現Pod被刪除,執行umount/unmountDevice/MarkVolumeAsDelete(通過幾次reconcile)

  2. attach detach controller發現volume在node節點未被使用,執行detach

  3. scheduler發現Pod進行排程

  4. attach detach controller發現volume需要attach,執行attach

  5. volume manager掛載


場景三:delete Pod,感知順序為statefulset->volume manager(umount/deviceUmount)->scheduler->volume manager(mount)。

  1. StatefulSet發現Pod被刪除,馬上建立Pod

  2. volume manager發現Pod被刪除,執行umount/deviceUmount(通過幾次reconcile),注意此時devicePath和deviceMountPath都為空

  3. scheduler發現Pod進行排程

  4. volume manager發現原有volume需要繫結Pod,執行mount而此時devicePath和deviceMountPath都為空,問題出現


再結合問題出現日誌分析:


Sep 

14

 

19

:

28

:

33

 

10


-10


-40


-16

 kubelet: I0914 

19

:

28

:

33.174310

    

1953

 operation_generator.

go

:

1168

] Controller attach succeeded 

for

 volume 

"pvc-3ecd68c7b7d211e8"

 (UniqueName: 

"kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-3ecd68c7b7d211e8-338"

) pod 

"yoooo-416ea0-0"

 (UID: 

"49a5fede-b811-11e8-844f-fa7378845e00"

) device path: 

"csi-eb93736e654600786d95eaffa7cd5d616f11a90bdc109e0df575e8646c250eb2"


Sep  14   19 : 28 : 33   10 -10 -40 -16  kubelet: I0914  19 : 28 : 33.273344      1953  operation_generator. go : 486 ] MountVolume.WaitForAttach entering  for  volume  "pvc-3ecd68c7b7d211e8"  (UniqueName:  "kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-3ecd68c7b7d211e8-338" ) pod  "yoooo-416ea0-0"  (UID:  "49a5fede-b811-11e8-844f-fa7378845e00" ) DevicePath  "csi-eb93736e654600786d95eaffa7cd5d616f11a90bdc109e0df575e8646c250eb2"
Sep  14   19 : 28 : 33   10 -10 -40 -16  kubelet: I0914  19 : 28 : 33.318275      1953  operation_generator. go : 495 ] MountVolume.WaitForAttach succeeded  for  volume  "pvc-3ecd68c7b7d211e8"  (UniqueName:  "kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-3ecd68c7b7d211e8-338" ) pod  "yoooo-416ea0-0"  (UID:  "49a5fede-b811-11e8-844f-fa7378845e00" ) DevicePath  "csi-eb93736e654600786d95eaffa7cd5d616f11a90bdc109e0df575e8646c250eb2"
Sep  14   19 : 28 : 33   10 -10 -40 -16  kubelet: I0914  19 : 28 : 33.319345      1953  operation_generator. go : 514 ] MountVolume.MountDevice succeeded  for  volume  "pvc-3ecd68c7b7d211e8"  (UniqueName:  "kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-3ecd68c7b7d211e8-338" ) pod  "yoooo-416ea0-0"  (UID:  "49a5fede-b811-11e8-844f-fa7378845e00" ) device mount path  "/var/lib/kubelet/plugins/kubernetes.io/csi/pv/pvc-3ecd68c7b7d211e8/globalmount"
Sep  14   19 : 29 : 12   10 -10 -40 -16  kubelet: I0914  19 : 29 : 12.826916      1953  operation_generator. go : 486 ] MountVolume.WaitForAttach entering  for  volume  "pvc-3ecd68c7b7d211e8"  (UniqueName:  "kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-3ecd68c7b7d211e8-338" ) pod  "yoooo-416ea0-0"  (UID:  "67f223dc-b811-11e8-844f-fa7378845e00" ) DevicePath  "csi-eb93736e654600786d95eaffa7cd5d616f11a90bdc109e0df575e8646c250eb2"
Sep  14   19 : 29 : 14   10 -10 -40 -16  kubelet: I0914  19 : 29 : 14.465225      1953  operation_generator. go : 495 ] MountVolume.WaitForAttach succeeded  for  volume  "pvc-3ecd68c7b7d211e8"  (UniqueName:  "kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-3ecd68c7b7d211e8-338" ) pod  "yoooo-416ea0-0"  (UID:  "67f223dc-b811-11e8-844f-fa7378845e00" ) DevicePath  "csi-eb93736e654600786d95eaffa7cd5d616f11a90bdc109e0df575e8646c250eb2"
Sep  14   19 : 29 : 14   10 -10 -40 -16  kubelet: I0914  19 : 29 : 14.466483      1953  operation_generator. go : 514 ] MountVolume.MountDevice succeeded  for  volume  "pvc-3ecd68c7b7d211e8"  (UniqueName:  "kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-3ecd68c7b7d211e8-338" ) pod  "yoooo-416ea0-0"  (UID:  "67f223dc-b811-11e8-844f-fa7378845e00" ) device mount path  "/var/lib/kubelet/plugins/kubernetes.io/csi/pv/pvc-3ecd68c7b7d211e8/globalmount"
Sep  14   19 : 29 : 15   10 -10 -40 -16  kubelet: W0914  19 : 29 : 15.491424      1953  csi_mounter. go : 354 ] kubernetes.io/csi: skipping mount dir removal, path does not exist [/ var /lib/kubelet/pods/ 49 a5fede-b811 -11e8 -844f -fa7378845e00/volumes/kubernetes.io~csi/pvc -3 ecd68c7b7d211e8/mount]
Sep  14   19 : 29 : 15   10 -10 -40 -16  kubelet: I0914  19 : 29 : 15.491450      1953  operation_generator. go : 686 ] UnmountVolume.TearDown succeeded  for  volume  "kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-3ecd68c7b7d211e8-338"  (OuterVolumeSpecName:  "data" ) pod  "49a5fede-b811-11e8-844f-fa7378845e00"  (UID:  "49a5fede-b811-11e8-844f-fa7378845e00" ). InnerVolumeSpecName  "pvc-3ecd68c7b7d211e8" . PluginName  "kubernetes.io/csi" , VolumeGidValue  ""
Sep  14   19 : 29 : 44   10 -10 -40 -16  kubelet: W0914  19 : 29 : 44.896387      1953  csi_mounter. go : 354 ] kubernetes.io/csi: skipping mount dir removal, path does not exist [/ var /lib/kubelet/pods/ 67f 223dc-b811 -11e8 -844f -fa7378845e00/volumes/kubernetes.io~csi/pvc -3 ecd68c7b7d211e8/mount]
Sep  14   19 : 29 : 44   10 -10 -40 -16  kubelet: I0914  19 : 29 : 44.896403      1953  operation_generator. go : 686 ] UnmountVolume.TearDown succeeded  for  volume  "kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-3ecd68c7b7d211e8-338"  (OuterVolumeSpecName:  "data" ) pod  "67f223dc-b811-11e8-844f-fa7378845e00"  (UID:  "67f223dc-b811-11e8-844f-fa7378845e00" ). InnerVolumeSpecName  "pvc-3ecd68c7b7d211e8" . PluginName  "kubernetes.io/csi" , VolumeGidValue  ""
Sep  14   19 : 29 : 44   10 -10 -40 -16  kubelet: I0914  19 : 29 : 44.917540      1953  reconciler. go : 278 ] operationExecutor.UnmountDevice started  for  volume  "pvc-3ecd68c7b7d211e8"  (UniqueName:  "kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-3ecd68c7b7d211e8-338" ) on node  "10-10-40-16"
Sep  14   19 : 29 : 44   10 -10 -40 -16  kubelet: W0914  19 : 29 : 44.919231      1953  mount_linux. go : 179 ] could not determine device  for  path:  "/var/lib/kubelet/plugins/kubernetes.io/csi/pv/pvc-3ecd68c7b7d211e8/globalmount"
Sep  14   19 : 29 : 45   10 -10 -40 -16  kubelet: I0914  19 : 29 : 45.609605      1953  operation_generator. go : 760 ] UnmountDevice succeeded  for  volume  "pvc-3ecd68c7b7d211e8"  %!(EXTRA  string =UnmountDevice succeeded  for  volume  "pvc-3ecd68c7b7d211e8"  (UniqueName:  "kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-3ecd68c7b7d211e8-338" ) on node  "10-10-40-16"  )
Sep  14   19 : 29 : 45   10 -10 -40 -16  kubelet: I0914  19 : 29 : 45.624963      1953  operation_generator. go : 486 ] MountVolume.WaitForAttach entering  for  volume  "pvc-3ecd68c7b7d211e8"  (UniqueName:  "kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-3ecd68c7b7d211e8-338" ) pod  "yoooo-416ea0-0"  (UID:  "77b8caf7-b811-11e8-844f-fa7378845e00" ) DevicePath  ""
Sep  14   19 : 29 : 46   10 -10 -40 -16  kubelet: E0914  19 : 29 : 46.006612      1953  nestedpendingoperations. go : 267 ] Operation  for   "\"kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-3ecd68c7b7d211e8-338\""  failed. No retries permitted until  2018 -09 -14   19 : 29 : 46.506583596  + 0800  CST m=+ 105572.978439381  (durationBeforeRetry  500 ms). Error:  "MountVolume.WaitForAttach failed for volume \"pvc-3ecd68c7b7d211e8\" (UniqueName: \"kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-3ecd68c7b7d211e8-338\") pod \"yoooo-416ea0-0\" (UID: \"77b8caf7-b811-11e8-844f-fa7378845e00\") : resource name may not be empty"
Sep  14   19 : 29 : 46   10 -10 -40 -16  kubelet: I0914  19 : 29 : 46.533962      1953  operation_generator. go : 486 ] MountVolume.WaitForAttach entering  for  volume  "pvc-3ecd68c7b7d211e8"  (UniqueName:  "kubernetes.io/csi/csi-qcfsplugin^csi-qcfs-volume-3ecd68c7b7d211e8-338" ) pod  "yoooo-416ea0-0"  (UID:  "77b8caf7-b811-11e8-844f-fa7378845e00" ) DevicePath  ""


WaitForAttach有兩個階段:

  • Sep 14 19:29:14以及之前DevicePath非空

  • Sep 14 19:29:45以及之後DevicePath為空


那麼在這兩個時間點之間發生了什麼,懷疑這個時間點發生的問題造成卷無法掛載。

通過日誌發現Sep 14 19:29:14到Sep 14 19:29:45有一段日誌資訊比較關鍵,分析如下:



Sep

 14 19

:29


:14

 …… 

MountVolume


.MountDevice

 ……

Sep  14 19 :29 :15  …..  UnmountVolume .TearDown  ……
Sep  14 19 :29 :44  ……  UnmountVolume .TearDown  ……
Sep  14 19 :29 :44  ……  operationExecutor .UnmountDevice  ……
Sep  14 19 :29 :44  ……  could   not   determine   device   for   path  ….

在步驟4中,有設定相關函式的:


UnmountDevice->GenerateUnmountDeviceFunc->actualStateOfWorld.MarkDeviceAsUnmounted->asw.SetVolumeGloballyMounted


其中比較關鍵的函式SetVolumeGloballyMounted:


asw.SetVolumeGloballyMounted(volumeName, 

false

 

/* globallyMounted */

/* devicePath */


""

/*  deviceMountPath */


""

)


總結

Kubernetes之所以在當前成為容器編排領域的事實標準,原因很多,但是對我們來講基於宣告式API的程式設計正規化是我們依賴Kubernetes的重要原因,當然在其解決的問題規模下複雜程度也不言而喻,總之,一句話,沒有銀彈。

相關連結:

  1. https://github.com/kubernetes/kubernetes/blob/release-1.10/pkg/controller/volume/persistentvolume/pv_controller.go#L301

  2. https://github.com/kubernetes/kubernetes/blob/release-1.10/pkg/controller/volume/attachdetach/populator/desired_state_of_world_populator.go#L88

  3. https://github.com/kubernetes/kubernetes/blob/release-1.10/pkg/controller/volume/attachdetach/reconciler/reconciler.go#L251

  4. https://github.com/kubernetes/kubernetes/blob/release-1.10/pkg/kubelet/volumemanager/populator/desired_state_of_world_populator.go#L152

  5. https://github.com/kubernetes/kubernetes/blob/release-1.10/pkg/kubelet/volumemanager/reconciler/reconciler.go#L160

  6. https://github.com/kubernetes/kubernetes/blob/release-1.10/pkg/kubelet/volumemanager/reconciler/reconciler.go#L238

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/28218939/viewspace-2217186/,如需轉載,請註明出處,否則將追究法律責任。

相關文章