Kubernetes做爲一個容器編排調度引擎,資源調度是它的最基本也是最重要的功能。當開發者部署一個應用時它運行在哪一個節點?這個節點滿不知足開發的運行要求?Kubernetes又是如何進行資源調度的呢?node
▌經過本文可瞭解到如下信息:nginx
在Kubernetes中有一個kube-scheduler組件,該組件運行在master節點上,它主要負責pod的調度。Kube-scheduler監聽kube-apiserver中是否有還未調度到node上的pod(即Spec.NodeName爲空的Pod),再經過特定的算法爲pod指定分派node運行。若是分配失敗,則將該pod放置調度隊列尾部以從新調度。調度主要分爲幾個部分:首先是預選過程,過濾不知足Pod要求的節點。而後是優選過程,對經過要求的節點進行優先級排序,最後選擇優先級最高的節點分配,其中涉及到的兩個關鍵點是過濾和優先級評定的算法。調度器使用一組規則過濾不符合要求的節點,其中包括設置了資源的request和指定了Nodename或者其餘親和性設置等等。優先級評定將過濾獲得的節點列表進行打分,調度器考慮一些總體的優化策略,好比將Deployment控制的多個副本集分配到不一樣節點上等。git
在部署應用時,開發者會考慮到使這個應用運行起來須要多少的內存和CPU資源的使用量,這樣才能判斷應將他運行在哪一個節點上。在部署文件resource屬性中添加requests字段用於說明運行該容器所需的最少資源,當調度器開始調度該Pod時,調度程序確保對於每種資源類型,計劃容器的資源請求總和必須小於節點的容量才能分配該節點運行Pod,resource屬性中添加limits字段用於限制容器運行時所得到的最大資源。若是該容器超出其內存限制,則可能被終止。 若是該容器能夠從新啓動,kubelet會將它從新啓動。若是調度器找不到合適的節點運行Pod時,就會產生調度失敗事件,調度器會將Pod放置調度隊列以循環調度,直到調度完成。github
在下面例子中,運行一個nginx Pod,資源請求了256Mi的內存和100m的CPU,調度器將判斷哪一個節點還剩餘這麼多的資源,尋找到了以後就會將這個Pod調度上去。同時也設置了512Mi的內存和300m的CPU的使用限制,若是該Pod運行以後超出了這一限制就將被重啓甚至被驅逐。web
apiVersion: v1 kind: Pod metadata: name: nginx spec: containers: - name: nginx image: nginx resources: requests: memory: "256Mi" cpu: "100m" limits: memory: "512Mi" cpu: "300m"
參考文檔:redis
在部署應用後,可使用 kubectl describe 命令進行查看Pod的調度事件,下面是一個coredns被成功調度到node3運行的事件記錄。算法
$ kubectl describe po coredns-5679d9cd77-d6jp6 -n kube-system ... Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 29s default-scheduler Successfully assigned kube-system/coredns-5679d9cd77-d6jp6 to node3 Normal Pulled 28s kubelet, node3 Container image "grc.io/kubernetes/coredns:1.2.2" already present on machine Normal Created 28s kubelet, node3 Created container Normal Started 28s kubelet, node3 Started container
下面是一個coredns被調度失敗的事件記錄,根據記錄顯示不可調度的緣由是沒有節點知足該Pod的內存請求。docker
$ kubectl describe po coredns-8447874846-5hpmz -n kube-system ... Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 22s (x3 over 24s) default-scheduler 0/3 nodes are available: 3 Insufficient memory.
例如開發者須要部署一個ES集羣,因爲ES對磁盤有較高的要求,而集羣中只有一部分節點有SSD磁盤,那麼就須要將標記一下帶有SSD磁盤的節點即給這些節點打上Lable,讓ES的pod只能運行在帶這些標記的節點上。api
Lable是附着在K8S對象(如Pod、Service等)上的鍵值對。它能夠在建立對象的時候指定,也能夠在對象建立後隨時指定。Kubernetes最終將對labels最終索引和反向索引用來優化查詢和watch,在UI和命令行中會對它們排序。通俗的說,就是爲K8S對象打上各類標籤,方便選擇和調度。app
查看節點信息。
$ kubectl get nodes NAME STATUS ROLES AGE VERSION node1 Ready etcd,master 128m v1.12.4 node2 Ready etcd,lb,master 126m v1.12.4 node3 Ready etcd,lb,worker 126m v1.12.4
選擇出有SSD磁盤的節點,並給這個節點打上標記(label)。
$ kubectl label nodes <your-node-name> disktype=ssd node/<your-node-name> labeled
驗證節點上是否有成功打上對應label。
$ kubectl get nodes --show-labels NAME STATUS ROLES AGE VERSION LABELS node1 Ready etcd,master 139m v1.12.4 ...disktype=ssd,kubernetes.io/hostname=node1... node2 Ready etcd,lb,master 137m v1.12.4 ...kubernetes.io/hostname=node2... node3 Ready etcd,lb,worker 137m v1.12.4 ...kubernetes.io/hostname=node3...
建立一個ES的pod, 調度到有SSD磁盤標記的節點上。在pod的配置裏, 要指定nodeSelector屬性值爲disktype:ssd。這意味着pod啓動後會調度到打上了disktype=ssd標籤的node上。
apiVersion: v1 kind: Pod metadata: name: es spec: containers: - name: es
nodeSelector: disktype: ssd ```
驗證pod啓動後是否調度到指定節點上。
$ kubectl get pods -o wide NAMESPACE NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE default es-5679d9cd77-sbmcx 1/1 Running 0 134m 10.244.2.3 node1 <none>
參考文檔:
上小節講述的nodeSelector提供了一種很是簡單的方法,能夠將pod限制爲具備特定標籤的節點。而更爲強大的表達約束類型則能夠由Affinity和Anti-affinity來配置。即親和性與反親和性的設置。親和性和反親和性包括兩種類型:節點(反)親和性與Pod(反)親和性。
Node affinity與NodeSelector很類似,它容許你根據節點上的標籤限制你的pod能夠在哪些節點上進行調度。目前有兩種類型的節點關聯,稱爲required During Scheduling Ignored During Execution和 preferred During Scheduling Ignored During Execution。能夠將它們分別視爲「硬規則」和「軟規則」,前者指定了要將 pod調度到節點上必須知足的規則,然後者指定調度程序將嘗試強制但不保證的首選項。名稱中的「Ignored During Execution」部分意味着,相似於nodeSelector工做方式,若是節點上的標籤在運行時更改,再也不知足pod上的關聯性規則,pod仍將繼續在該節點上運行。Pod affinity強調的是同一個節點中Pod之間的親和力。能夠根據已在節點上運行的pod上的標籤來約束pod能夠調度哪些節點上。好比但願運行該Pod到某個已經運行了Pod標籤爲app=webserver的節點上,就可使用Pod affinity來表達這一需求。
目前有兩種類型Pod親和力和反親和力,稱爲required During Scheduling Ignored During Execution以及 preferred During Scheduling Ignored During Execution,其中表示「硬規則」與「軟規則」的要求。相似於Node affinity,IgnoredDuringExecution部分表示若是在Pod運行期間改變了Pod標籤致使親和性不知足以上規則,則pod仍將繼續在該節點上運行。不管是Selector仍是Affinity,都是基於Pod或者Node的標籤來表達約束類型。從而讓調度器按照約束規則來調度Pod運行在合理的節點上。
節點親和性以下所示,其中親和性定義爲:該pod只能放置在一個含有鍵爲kubernetes.io/hostname而且值爲node1或者node2標籤的節點上。此外,在知足該標準的節點中,具備其鍵爲app且值爲webserver的標籤的節點應該是優選的。
apiVersion: v1 kind: Pod metadata: name: with-node-affinity spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - node1 - node2 preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 preference: matchExpressions: - key: app operator: In values: - webserver containers: - name: with-node-affinity image: k8s.gcr.io/pause:2.0
Pod反親和性以下所示,其中反親和性定義爲:在此拓撲域(至關於以topologyKey的值進行的節點分組)中,命名空間爲default下有標籤鍵爲app,標籤值爲redis的Pod時不在此Node上運行。
apiVersion: v1 kind: Pod metadata: name: with-node-affinity spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - redis namespaces: - default topologyKey: kubernetes.io/hostname containers: - name: with-node-affinity image: k8s.gcr.io/pause:2.0
Scheduling過程的本質其實就是給Pod賦予nodeName屬性合適的值。那麼在開發者進行Pod部署時就直接指定這個值是否可行呢?答案是確定的。以下配置,將nginx直接分配到node1上運行。
apiVersion: v1 kind: Pod metadata: name: nginx spec: containers: - image: nginx name: nginx nodeName: node1
還有一種指定節點的部署方式——static pod,就像它名稱同樣,他是一個「靜態」的Pod,它不經過apiserver,直接由kubelet進行託管。在kubelet的啓動參數中--pod-manifest-path=DIR,這裏的DIR就是放置static pod的編排文件的目錄。把static pod的編排文件放到此目錄下,kubelet就能夠監聽到變化,並根據編排文件建立pod。還有一個啓動參數--manifest-url=URL,kubelet會從這個URL下載編排文件,並建立pod。static pod有一個特性是咱們使用docker或kubectl刪除static pod後, static pod還能被kubelet進程拉起。經過這種方式保證了應用的可用性。有點至關於systemd的功能, 但比systemd好的一點是, static pod的鏡像信息會在apiserver中註冊。 這樣的話, 咱們就能夠統一對部署信息進行可視化管理。 此外static pod是容器, 無需拷貝二進制文件到主機上, 應用封裝在鏡像裏也保證了環境的一致性, 不管是應用的編排文件仍是應用的鏡像都方便進行版本管理和分發。
在使用kubeadm部署kubernetes集羣時,static pod獲得了大量的應用,好比 etcd、kube-scheduler、kube-controller-manager、kube-apiserver 等都是使用 static pod的方式運行的。
使用static pod部署出來的pod名稱與其餘pod有很大的不一樣點,名稱中沒有「亂碼」,只是簡單的將pod的name屬性值與它運行在的node的name屬性值相鏈接而成。以下所示,coredns是經過Deployment部署出來的名稱中就有部分「亂碼」,而etcd,kube-apiserver這種Pod就是static pod。
$ kubectl get po --all-namespaces NAMESPACE NAME READY STATUS RESTARTS AGE kube-system coredns-5679d9cd77-d6jp6 1/1 Running 0 6m59s kube-system etcd-node1 1/1 Running 0 6m58s kube-system etcd-node2 1/1 Running 0 6m58s kube-system etcd-node3 1/1 Running 0 6m54s kube-system kube-proxy-nxj5d 1/1 Running 0 6m52s kube-system kube-proxy-tz264 1/1 Running 0 6m56s kube-system kube-proxy-zxgxc 1/1 Running 0 6m57s
DaemonSet是一種控制器,它確保在一些或所有Node上都運行一個指定的Pod。這些Pod就至關於守護進程同樣不指望被終止。當有Node加入集羣時,也會爲他們新增一個Pod。當有Node從集羣移除時,對應的Pod也會被回收。當刪除DaemonSet時將會刪除它建立的全部Pod。通常狀況下,Pod運行在哪一個節點上是由Kubernates調度器選擇的。可是在Kubernates 1.11版本以前由DaemonSet Controller建立的Pod在建立時已經肯定了在哪一個節點上運行(pod在建立的時候.spec.nodeName字段就指定了, 所以會被scheduler忽略),因此即便調度器沒有啓動DaemonSet Controller建立的Pod仍然也能夠被分配node。直到Kubernates 1.11版本,DaemonSet的pod由scheduler調度才做爲alpha特性引入。在上小節中kube-proxy就是以DaemonSet的方式進行運行的。
若是須要配置一些高級的調度策略以知足咱們的須要,能夠修改默認調度程序的配置文件。kube-scheduler在啓動的時候能夠經過--policy-config-file參數來指定調度策略文件,開發者能夠根據本身的須要來組裝Predicates和Priority函數。選擇不一樣的過濾函數和優先級函數。調整控制優先級函數的權重和過濾函數的順序都會影響調度結果。
官方的Policy文件以下:
kind: Policy apiVersion: v1 predicates: - {name: PodFitsHostPorts} - {name: PodFitsResources} - {name: NoDiskConflict} - {name: NoVolumeZoneConflict} - {name: MatchNodeSelector} - {name: HostName} priorities: - {name: LeastRequestedPriority, weight: 1} - {name: BalancedResourceAllocation, weight: 1} - {name: ServiceSpreadingPriority, weight: 1} - {name: EqualPriority, weight: 1}
其中predicates區域是調度的預選階段所須要的過濾算法。priorities區域是優選階段的評分算法。
再來回顧一下調度的主要構成部分:首先是預選過程,過濾掉不知足Pod要求的節點,而後是優選過程,對經過要求的節點進行優先級排序,最後選擇優先級最高的節點進行分配。當調度器不工做時或有臨時需求能夠手動指定nodeName屬性的值,讓其不經過調度器進行調度直接運行在指定的節點上。
Choerodon開源多雲應用敏捷全鏈路技術平臺,是基於開源技術Kubernetes,Istio,knative,Gitlab,Spring Cloud來實現本地和雲端環境的集成,實現企業多雲/混合雲應用環境的一致性。平臺經過提供精益敏捷、持續交付、容器環境、微服務、DevOps等能力來幫助組織團隊來完成軟件的生命週期管理,從而更快、更頻繁地交付更穩定的軟件。
你們也能夠經過如下社區途徑瞭解豬齒魚的最新動態、產品特性,以及參與社區貢獻:
本篇文章出自Choerodon豬齒魚社區鍾梓凌&黃顯東。