歡迎光臨
每天分享高質量文章

Pod掛載Volume失敗問題分析

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/49a5fede-b811-11e8-844f-fa7378845e00/volumes/kubernetes.io~csi/pvc-3ecd68c7b7d211e8/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/67f223dc-b811-11e8-844f-fa7378845e00/volumes/kubernetes.io~csi/pvc-3ecd68c7b7d211e8/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 500ms). 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

赞(0)

分享創造快樂