點擊上方藍字● 關注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使用。