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負責完成,結合相關 ofollow,noindex" target="_blank">程式碼1 。
此時pvc繫結pv。
2、attach detach controlle,如果pod分配到worker node,並且對應卷已經建立,則將卷掛載到對應worker node,並且給worker node資源增加volume已經掛載對應卷的狀態資訊,結合相關 程式碼2 和 程式碼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 、 程式碼5 。
如果已經attached成功,則進入到檔案系統掛載流程,相關 程式碼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 。
- 將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)。
- volume manager發現Pod被刪除,執行umount
- StatefulSet發現Pod被刪除,馬上建立Pod
- scheduler發現Pod進行排程
- 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。
- volume manager發現Pod被刪除,執行umount/unmountDevice/MarkVolumeAsDelete(通過幾次reconcile)
- attach detach controller發現volume在node節點未被使用,執行detach
- scheduler發現Pod進行排程
- attach detach controller發現volume需要attach,執行attach
- volume manager掛載
場景三:delete Pod,感知順序為statefulset->volume manager(umount/deviceUmount)->scheduler->volume manager(mount)。
- StatefulSet發現Pod被刪除,馬上建立Pod
- volume manager發現Pod被刪除,執行umount/deviceUmount(通過幾次reconcile),注意此時devicePath和deviceMountPath都為空
- scheduler發現Pod進行排程
- volume manager發現原有volume需要繫結Pod,執行mount而此時devicePath和deviceMountPath都為空,問題出現
再結合問題出現日誌分析:
Sep 14 19:28:33 10-10-40-16 kubelet: I0914 19:28:33.1743101953 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.2733441953 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.3182751953 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.3193451953 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.8269161953 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.4652251953 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.4664831953 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.4914241953 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.4914501953 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.8963871953 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.8964031953 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.9175401953 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.9192311953 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.6096051953 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.6249631953 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.0066121953 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.5339621953 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的重要原因,當然在其解決的問題規模下複雜程度也不言而喻,總之,一句話,沒有銀彈。