摘要:的詳細內容如下當然,也可以不使用,而是手動創建。前是將作為記錄到中,起將其獨立為一個字段接下來可以創建各種,記得要在的模板中聲明。注意到這里是,即我們一開始創建的實例的名字。為表示中申明的未的,在集群內的中找不到可以匹配的。
什么是Local Persistent Volumes
在kubernetes 1.14版本中, Local Persistent Volumes(以下簡稱LPV)已變為正式版本(GA),LPV的概念在1.7中被首次提出(alpha),并在1.10版本中升級到beat版本。現在用戶終于可以在生產環境中使用LPV的功能和API了。
首先:Local Persistent Volumes代表了直接綁定在計算節點上的一塊本地磁盤。
kubernetes提供了一套卷插件(volume plugin)標準,使得k8s集群的工作負載可以使用多種塊存儲和文件存儲。大部分磁盤插件都使用了遠程存儲,這是為了讓持久化的數據與計算節點彼此獨立,但遠程存儲通常無法提供本地存儲那么強的讀寫性能。有了LPV 插件,kubernetes負載現在可以用同樣的volume api,在容器中使用本地磁盤。
這跟hostPath有什么區別hostPath是一種volume,可以讓pod掛載宿主機上的一個文件或目錄(如果掛載路徑不存在,則創建為目錄或文件并掛載)。
最大的不同在于調度器是否能理解磁盤和node的對應關系,一個使用hostPath的pod,當他被重新調度時,很有可能被調度到與原先不同的node上,這就導致pod內數據丟失了。而使用LPV的pod,總會被調度到同一個node上(否則就調度失敗)。
如何使用LPV首先 需要創建StorageClass
kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: local-storage provisioner: kubernetes.io/no-provisioner volumeBindingMode: WaitForFirstConsumer
注意到這里volumeBindingMode字段的值是WaitForFirstConsumer。這種bindingmode意味著:
kubernetes的pv控制器會將這類pv的binding延遲,直到有一個使用了對應pvc的pod被創建出來且該pod被調度完畢。這時候才會將pv和pvc進行binding,并且這時候pv的選擇會結合調度的node和pv的nodeaffinity。
接下來,提前準備好的provisioner會動態創建PV。
$ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE local-pv-27c0f084 368Gi RWO Delete Available local-storage 8s local-pv-3796b049 368Gi RWO Delete Available local-storage 7s local-pv-3ddecaea 368Gi RWO Delete Available local-storage 7s
LPV的詳細內容如下:
$ kubectl describe pv local-pv-ce05be60 Name: local-pv-ce05be60 Labels:Annotations: pv.kubernetes.io/provisioned-by=local-volume-provisioner-minikube-18f57fb2-a186-11e7-b543-080027d51893 StorageClass: local-fast Status: Available Claim: Reclaim Policy: Delete Access Modes: RWO Capacity: 1024220Ki NodeAffinity: Required Terms: Term 0: kubernetes.io/hostname in [my-node] Message: Source: Type: LocalVolume (a persistent volume backed by local storage on a node) Path: /mnt/disks/vol1 Events:
當然,也可以不使用provisioner,而是手動創建PV。但是必須要注意的是,LPV必須要填寫nodeAffinity。 (1.10前k8s是將nodeAffinity作為annotation記錄到PV中,1.10起將其獨立為一個字段)
apiVersion: v1 kind: PersistentVolume metadata: name: example-pv spec: capacity: storage: 100Gi # volumeMode field requires BlockVolume Alpha feature gate to be enabled. volumeMode: Filesystem accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Delete storageClassName: local-storage local: path: /mnt/disks/ssd1 nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - example-node
接下來可以創建各種workload,記得要在workload的模板中聲明volumeClaimTemplates。
apiVersion: apps/v1 kind: StatefulSet metadata: name: local-test spec: serviceName: "local-service" replicas: 3 selector: matchLabels: app: local-test template: metadata: labels: app: local-test spec: containers: - name: test-container image: k8s.gcr.io/busybox command: - "/bin/sh" args: - "-c" - "sleep 100000" volumeMounts: - name: local-vol mountPath: /usr/test-pod volumeClaimTemplates: - metadata: name: local-vol spec: accessModes: [ "ReadWriteOnce" ] storageClassName: "local-storage" resources: requests: storage: 368Gi
注意到這里volumeClaimTemplates.spec.storageClassName是local-storage,即我們一開始創建的storageclass實例的名字。
使用LPV的pod的調度流程上面這個statefulset創建后,控制器會為其創建對應的PVC,并且會為PVC查找符合條件的PV,但是由于我們在local-storage中配置了WaitForFirstConsumer,所以控制器不會處理pvc和pv的bind;
同時,調度器在調度該pod時,predicate算法中也會根據PVC的要求去找到可用的PV,并且會過濾掉“與LPV的affinity”不匹配的node。最終,調度器發現:
pv:example-pv滿足了pvc的要求;
node:example-node滿足了pv:example-pv的nodeAffinity要求。
于是乎調度器嘗試將pv和pvc bind起來,并且對pod進行重新調度。
重新調度pod時調度器發現pod的pvc資源得到了滿足(都bound了pv),且bound的pv的nodeAffinity與node:example-node匹配。于是將pod調度到node:example-node上。完成調度。
如何創建LPV在機器上創建目錄: mkdir -p /mnt/disks/ssd1
在機器上執行命令,將某個卷掛載到該目錄:mount -t /dev/vdc /mnt/disks/ssd1
在集群中創建對應的storageClass. 參見上文。
手動創建本地卷的PV,或者通過provisioner去自動創建。手動創建的模板見上文。
如何刪除LPV對于已經被bind并被pod使用的LPV,刪除一定要按照流程來 , 要不然會刪除失敗:
刪除使用這個pv的pod
從node上移除這個磁盤(按照一個pv一塊盤)
刪除pvc
刪除pv
LPV延遲綁定部分的代碼解讀所有的關鍵在于volumeBinder這個結構,它繼承了SchedulerVolumeBinder接口,包括:
type SchedulerVolumeBinder interface { FindPodVolumes(pod *v1.Pod, node *v1.Node) AssumePodVolumes(assumedPod *v1.Pod, nodeName string) BindPodVolumes(assumedPod *v1.Pod) error GetBindingsCache() PodBindingCache }FindPodVolumes
了解調度器原理的應該知道,調度器的predicate算法,在調度pod時,會逐個node的去進行predicate,以確認這個node是否可以調度。我們稱之為預選階段。
VolumeBindingChecker 是一個檢查器,在調度器的算法工廠初始化的最后一步,會向工廠中注冊檢查算法,這樣調度器在進行predicate時,最后一步會執行對volumeBinding的檢查。我們看func (c *VolumeBindingChecker) predicate 方法就能看到,這里面執行了FindPodVolumes,并且判斷返回的幾個值是否為true,或err是否為空:
unboundSatisfied, boundSatisfied, err := c.binder.Binder.FindPodVolumes(pod, node)
boundSatisfied 為false表示pod綁定的pv 與當前計算的node親和性不過關。
unboundSatisfied 為false表示pod中申明的未bound的pvc,在集群內的pv中找不到可以匹配的。
就這樣,調度器會反復去重試調度,反復執行FindPodVolumes,直到我們(或者provisoner)創建出了PV,比如這時新建的PV,其nodeAffinity對應到了node A。這次調度,在對node A進行predicate計算時,發現pod中申明的、未bound的pvc,在集群中有合適的pv,且該pv的nodeAffinity就是node A,于是返回的unboundSatisfied為 true, 調度器最終找到了一個合適的node。
那么,調度器接下來要對pod執行assume,在對pod assume之前,調度器要先對pod中bind的volume進行assume。見func (sched *Scheduler) assumeAndBindVolumes(assumed *v1.Pod, host string) error 。這個函數里,我們調用了volumeBinder的AssumePodVolumes方法。
AssumePodVolumesassume是假設的意思,顧名思義,這個方法會先在調度器的緩存中,假定pod已經調度到node A上,對緩存中的pv、pvc、binding等資源進行更新,看是否能成功,它會返回一些訊息:
allBound, bindingRequired, err := sched.config.VolumeBinder.Binder.AssumePodVolumes(assumed, host)
allBound 為true表示所有的pv、pvc,在緩存中已經是bind。如果為false,會最終導致本次調度失敗。
bindingRequired 為true表示有一些pv需要和pvc bind起來。如果為true,調度器會向volumeBinder的BindQueue中寫入一個用例。這個隊列會被一個worker輪詢,并進行對應的工作。
什么工作呢? BindPodVolumes
BindPodVolumes調度器在Run起來的時候,會啟動一個協程,反復執行bindVolumesWorker。在這個worker中我們可以看到,他嘗試從volumeBinder的BindQueue中取出任務,進行BindPodVolumes,成功則該任務Done,失敗則報錯重試。
閱讀BindPodVolumes這個方法,很簡單,從緩存中找到對應的pod、pv、pvc等內容,更新到APIserver中。
由于我們在AssumePodVolumes中已經更新了緩存,所以這里更新到apiserver的操作,會真正地將pv和pvc bind起來。
之后呢?
在worker中我們看到,如果BindPodVolumes成功,依然會構造一個pod調度失敗的事件,并更新pod的狀態為PodScheduled,這么做是為了將pod放回調度隊列,讓調度器再去調度一次。
我們假設pod中只申明了一個LPV,在剛剛描述的這次BindPodVolumes操作中已經在apiserver中對這個LPV,和pod中的pvc進行了bind。那么,下一次調度器調度pod時,在AssumePodVolumes時會發現已經allBound ,調度器會繼續后續的操作,最終pod被成功地調度(創建出Binding資源,apiserver將pod的nodeName更新)。
pv控制器不管嗎?創建PVC后,pv控制器會有一個worker:syncUnboundClaim去管理未bind的pvc。這個worker中,對于spec.VolumeName不為空的pvc,會去進行bind操作,確保pv和pvc綁定起來;對于spec.VolumeName為空的pvc,會去檢查是否延遲綁定,并查找集群中適合該pvc的pv(這里沒有node的概念,所以在查找時更多地是根據selector和AccessModes去過濾)。可以在
func findMatchingVolume( claim *v1.PersistentVolumeClaim, volumes []*v1.PersistentVolume, node *v1.Node, excludedVolumes map[string]*v1.PersistentVolume, delayBinding bool) (*v1.PersistentVolume, error)
中找到過濾的邏輯。這里我們只要知道:對于延遲綁定的pvc,我們會過濾掉所有的pv,并最后發出一個WaitForFirstConsumer的event結束worker。
可見,pv控制器對于延遲調度的pvc放任自流了。我們在findMatchingVolume方法中也可以看到官方的一段注釋:
if node == nil && delayBinding { // PV controller does not bind this claim. // Scheduler will handle binding unbound volumes // Scheduler path will have node != nil continue }總結本地盤的使用流程 待補充: pv控制器、CSI的工作機制
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/33183.html
摘要:這與不同,因為將繼續存在于系統中,直到用戶刪除它。和持久卷聲明之間很容易弄混淆。該在直接和使用前必須已經存在。這對于需要持久化儲存但只有本地卷可用的工作負載,這非常有用。此外,由于缺少,會失去使用自動伸縮的能力。 showImg(https://segmentfault.com/img/remote/1460000017071596?w=1920&h=1080); 回 顧 在本系列...
摘要:此次新版的最重大更新無疑為對節點的生產級支持。持久化本地存儲的最主要用例是分布式文件系統和數據庫,主要是由于性能和成本的原因。在裸機上,除了性能之外,本地存儲通常也更便宜,并且使用它是配置分布式文件系統的必要條件。 Kubernetes 1.14現已正式發布,這是Kubernetes在2019年的首次更新! Kubernetes 1.14由31個增強功能組成:10個功能現進入Stabl...
摘要:的本身是無狀態的生命周期通常比較短,只要出現了異常,就會自動創建一個新的來代替它。為了實現內數據的存儲管理,引入了兩個資源持久卷,以下簡稱和持久卷申請,以下簡稱。跟里的卷類似,不過會有獨立于的生命周期。 Kubernetes的pod本身是無狀態的(stateless),生命周期通常比較短,只要出現了異常,Kubernetes就會自動創建一個新的Pod來代替它。 而容器產生的數據,會隨著...
摘要:的本身是無狀態的生命周期通常比較短,只要出現了異常,就會自動創建一個新的來代替它。為了實現內數據的存儲管理,引入了兩個資源持久卷,以下簡稱和持久卷申請,以下簡稱。跟里的卷類似,不過會有獨立于的生命周期。 Kubernetes的pod本身是無狀態的(stateless),生命周期通常比較短,只要出現了異常,Kubernetes就會自動創建一個新的Pod來代替它。 而容器產生的數據,會隨著...
摘要:的本身是無狀態的生命周期通常比較短,只要出現了異常,就會自動創建一個新的來代替它。為了實現內數據的存儲管理,引入了兩個資源持久卷,以下簡稱和持久卷申請,以下簡稱。跟里的卷類似,不過會有獨立于的生命周期。 Kubernetes的pod本身是無狀態的(stateless),生命周期通常比較短,只要出現了異常,Kubernetes就會自動創建一個新的Pod來代替它。 而容器產生的數據,會隨著...
閱讀 2980·2021-11-16 11:45
閱讀 5124·2021-09-22 10:57
閱讀 1763·2021-09-08 09:36
閱讀 1584·2021-09-02 15:40
閱讀 2508·2021-07-26 23:38
閱讀 1184·2019-08-30 15:55
閱讀 923·2019-08-30 15:54
閱讀 1213·2019-08-29 14:06