點擊上方藍字● 關注Linux公社
來自:https://cvvz.github.io/post/k8s-volume容器運行時掛載卷的過程如果CRI是通過dockershim實現的話,kubelet通過CRI接口去拉起一個容器,就好比是通過docker-daemon執行docker run命令。而如果想要在容器中掛載宿主機目錄的話,就要帶上-v參數,以下面這條命令為例:1docker run -v /home:/test ...1int pid = clone(main_function, stack_size, CLONE_NEWNS | SIGCHLD, NULL);1mount("/home", "/test", "", MS_BIND, NULL)此時雖然開啟了mount namespace,只代表主機和容器之間mount點隔離開了,容器仍然可以看到主機的文件系統目錄。調用pivot_root或chroot,改變容器進程的根目錄。至此,容器再也看不到宿主機的文件系統目錄了。kubelet掛載卷的過程當一個Pod被調度到一個節點上之後,kubelet首先為這個Pod在宿主機上創建一個Volume目錄:/var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume類型>/<Volume名字>。在kubernetes中,卷volumes是Pod的一個屬性,而不是容器的。kubelet先以Pod為單位,在宿主機這個Volume目錄中準備好Pod需要的卷。接着啟動容器,容器啟動時,根據volumeMounts的定義將主機的這個目錄下的部分卷資源掛載進來。掛載的過程如前所述,相當於為每個容器執行了命令:1docker run -v /var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume類型>/<Volume名字>:/<容器內的目標目錄> 我的鏡像 ...而kubelet是怎麼把卷掛載到主機的volumes目錄下的呢?這取決於Volume的類型。遠程塊存儲Attach:將遠程磁盤掛載到本地,成為一個主機上的一個塊設備,通過lsblk命令可以查看到。Attach 這一步,由kube-controller-manager中的Volume Controller負責Mount:本地有了新的塊設備後,先將其格式化為某種文件系統格式後,就可以進行mount操作了。Mount 這一步,由kubelet中的VolumeManagerReconciler這個控制循環負責,它是一個獨立於kubelet主循環的goroutine。NFSNFS本身已經是一個遠程的文件系統了,所以可以直接進行mount,相當於執行:1mount -t nfs <NFS服務器地址>:/ /var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume類型>/<Volume名字>hostPathhostPath類型的掛載方式,和宿主機上的Volume目錄沒啥關係,就是容器直接掛載指定的宿主機目錄。emptyDir、downwardAPI、configMap、secret這幾種掛載方式,數據都會隨着Pod的消亡而被刪除。原因是kubelet在創建Pod的Volume資源時,其實是在主機的Volume目錄下創建了一些子目錄供容器進行掛載。Pod被刪除時,kubelet也會把這個Volume目錄刪掉,從而這個Volume目錄中的子目錄也都被刪除,這幾種類型的數據就被刪掉了。遠程塊存儲、NFS存儲等持久化的存儲,和hostPath、emptyDir、downwardAPI、configMap、secret不一樣,不是在Pod或任何一種workload中的volume字段中直接定義的,而是在PV中定義的。PVC、PV和StorageClass在Pod中,如果想使用持久化的存儲,如上面提到的遠程塊存儲、NFS存儲,或是本地塊存儲(非hostPath),則在volumes字段中,定義persistentVolumeClaim,即PVC。PVC和PV進行綁定的過程,由Volume Controller中的PersistentVolumeController這個控制循環負責。所謂「綁定」,也就是填寫PVC中的spec.volumeName字段而已。PersistentVolumeController只會將StorageClass相同的PVC和PV綁定起來。StorageClass主要用來動態分配存儲(Dynamic Provisioning)。StorageClass中的provisioner字段用於指定使用哪種存儲插件進行動態分配,當然,前提是你要在kubernetes中裝好對應的存儲插件。parameters字段就是生成出來的PV的參數。PersistentVolumeController只是在找不到對應的PV資源和PVC進行綁定時,藉助StorageClass生成了一個PV這個API對象。具體這個PV是怎麼成為主機volume目錄下的一個子目錄的,則是靠前面所述的Attach + Mount兩階段處理後的結果。當然如果是NFS或本地持久化卷,就不需要Volume Controller進行Attach操作了。本地持久化卷對於本地持久化卷,通過在PV模版中定義spec.nodeAffinity來指定持久化卷位於哪個宿主機上定義spec.local.path來指定宿主機的持久化卷的路徑。此外,由於PersistentVolumeController只會將StorageClass相同的PVC和PV綁定起來,所以還需要創建一個StorageClass,並且使PVC和PV中的StorageClassName相同。在 StorageClass 里,進行了如下定義:volumeBindingMode: WaitForFirstConsumer,這個字段的作用是延遲綁定PV和PVC。定義了這個字段,PVC和PV的綁定就不會在PersistentVolumeController中進行,而是由調度器在調度Pod的時候,根據Pod中聲明的PVC,來決定和哪個PV進行綁定。本地持久化卷是沒辦法進行 Dynamic Provisioning的,所以StorageClass字段中的provisioner定義的是kubernetes.io/no-provisioner。但是它的Static Provisioning也並不需要純手工操作。運維人員可以使用local-static-provisioner對PV進行自動管理。它的原理是通過DaemonSet檢測節點的/mnt/disks目錄,這個目錄下如果存在掛載點,則根據這個路徑自動生成對應的PV。所以,運維人員只需要在node節點上,在/mnt/disks目錄下準備好掛載點即可。Q:hostPath可以是掛載在宿主機上的一塊磁盤,而不是宿主機的主目錄,這種情況使用hostPath作為持久化存儲不會導致宿主機宕機。那是不是可以使用hostPath代替PVC/PV作為本地持久化卷?A:不可以。這種玩法失去了PersistentVolumeController對PVC和PV進行自動綁定、解綁的靈活性。也失去了通過local-static-provisioner對PV進行自動管理的靈活性。最關鍵的是失去了延遲綁定的特性,調度器進行調度的時候,無法參考節點存儲的使用情況。Q:刪除一個被Pod使用中的PVC/PV時,kubectl會卡住,為什麼?A:PVC和PV中定義了kubernetes.io/pvc-protection、kubernetes.io/pv-protection這個finalizer字段,刪除時,資源不會被apiserver立即刪除,要等到volume controller進行pre-delete操作後,將finalizer字段刪掉,才會被實際刪除。而volume controller的pre-delete操作實際上就是檢查PVC/PV有沒有被Pod使用。 長按或掃描下面的二維碼關注 Linux公社
關注 Linux公社,添加「 星標 」
每天 獲取 技術乾貨,讓我們一起成長
合作聯繫: root@linuxidc.net