目錄前端
API Server在接受客戶端提交Pod對象建立請求後,而後是經過調度器(kube-schedule)從集羣中選擇一個可用的最佳節點來建立並運行Pod。而這一個建立Pod對象,在調度的過程中有3個階段:節點預選、節點優選、節點選定,從而篩選出最佳的節點。如圖:node
當咱們有需求要將某些Pod資源運行在特定的節點上時,咱們能夠經過組合節點標籤,以及Pod標籤或標籤選擇器來匹配特定的預選策略並完成調度,如MatchInterPodAfinity、MatchNodeSelector、PodToleratesNodeTaints
等預選策略,這些策略經常使用於爲用戶提供自定義Pod親和性或反親和性、節點親和性以及基於污點及容忍度的調度機制。數據庫
預選策略實際上就是節點過濾器,例如節點標籤必須可以匹配到Pod資源的標籤選擇器(MatchNodeSelector實現的規則),以及Pod容器的資源請求量不能大於節點上剩餘的可分配資源(PodFitsResource規則)等等。執行預選操做,調度器會逐一根據規則進行篩選,若是預選沒能選定一個合適的節點,此時Pod會一直處於Pending狀態,直到有一個可用節點完成調度。其經常使用的預選策略以下:vim
在上面的這些預選策略裏面,CheckNodeLabelPressure和CheckServiceAffinity能夠在預選過程當中結合用戶自定義調度邏輯,這些策略叫作可配置策略。其餘不接受參數進行自定義配置的稱爲靜態策略。後端
預選策略篩選出一個節點列表就會進入優選階段,在這個過程調度器會向每一個經過預選的節點傳遞一系列的優選函數來計算其優先級分值,優先級分值介於0-10之間,其中0表示不適用,10表示最適合託管該Pod對象。api
另外,調度器還支持給每一個優選函數指定一個簡單的值,表示權重,進行節點優先級分值計算時,它首先將每一個優選函數的計算得分乘以權重,而後再將全部優選函數的得分相加,從而得出節點的最終優先級分值。權重可讓管理員定義優選函數傾向性的能力,其計算優先級的得分公式以下:tomcat
finalScoreNode = (weight1 * priorityFunc1) + (weight2 * priorityFunc2) + ......
下圖是關於優選函數的列表圖:安全
節點親和性是用來肯定Pod對象調度到哪個節點的規則,這些規則基於節點上的自定義標籤和Pod對象上指定的標籤選擇器進行定義。網絡
定義節點親和性規則有2種:硬親和性(require)和軟親和性(preferred)app
定義節點親和規則的兩個要點:一是節點配置是否合乎需求的標籤,而是Pod對象定義合理的標籤選擇器,這樣纔可以基於標籤選擇出指望的目標節點。
須要注意的是preferredDuringSchedulingIgnoredDuringExecution
和requiredDuringSchedulingIgnoredDuringExecution
名字中後半段字符串IgnoredDuringExecution
表示的是,在Pod資源基於節點親和性規則調度到某個節點以後,若是節點的標籤發生了改變,調度器不會講Pod對象從該節點上移除,由於該規則僅對新建的Pod對象有效。
下面的配置清單中定義的Pod對象,使用節點硬親和性和規則定義將當前Pod調度到標籤爲zone=foo的節點上:
apiVersion: v1 kind: Pod metadata: name: with-require-nodeaffinity spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - {key: zone,operator: In,values: ["foo"]} containers: - name: myapp image: ikubernetes/myapp:v1 #建立Pod對象 [root@k8s-master ~]# kubectl apply -f require-nodeAffinity-pod.yaml pod/with-require-nodeaffinity created #因爲集羣中並無節點含有節點標籤爲zone=foo,因此建立的Pod一直處於Pending狀態 [root@k8s-master ~]# kubectl get pods with-require-nodeaffinity NAME READY STATUS RESTARTS AGE with-require-nodeaffinity 0/1 Pending 0 35s #查看Pending具體的緣由 [root@k8s-master ~]# kubectl describe pods with-require-nodeaffinity ...... Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 3s (x21 over 1m) default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector. #給node01節點打上zone=foo的標籤,能夠看到成功調度到node01節點上 [root@k8s-master ~]# kubectl label node k8s-node01 zone=foo node/k8s-node01 labeled [root@k8s-master ~]# kubectl describe pods with-require-nodeaffinity Events: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 58s (x25 over 2m) default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector. Normal Pulled 4s kubelet, k8s-node01 Container image "ikubernetes/myapp:v1" already present on machine Normal Created 4s kubelet, k8s-node01 Created container Normal Started 4s kubelet, k8s-node01 Started container [root@k8s-master ~]# kubectl get pods with-require-nodeaffinity -o wide NAME READY STATUS RESTARTS AGE IP NODE with-require-nodeaffinity 1/1 Running 0 6m 10.244.1.12 k8s-node01
在定義節點親和性時,requiredDuringSchedulingIgnoredDuringExecution
字段的值是一個對象列表,用於定義節點硬親和性,它能夠由一個或多個nodeSelectorTerms
定義的對象組成,此時值須要知足其中一個nodeSelectorTerms
便可。
而nodeSelectorTerms
用來定義節點選擇器的條目,它的值也是一個對象列表,由1個或多個matchExpressions
對象定義的匹配規則組成,多個規則是邏輯與的關係,這就表示某個節點的標籤必需要知足同一個nodeSelectorTerms
下全部的matchExpression
對象定義的規則纔可以成功調度。以下:
#以下配置清單,必須存在知足標籤zone=foo和ssd=true的節點纔可以調度成功 apiVersion: v1 kind: Pod metadata: name: with-require-nodeaffinity spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - {key: zone, operator: In, values: ["foo"]} - {key: ssd, operator: Exists, values: []} #增長一個規則 containers: - name: myapp image: ikubernetes/myapp:v1 [root@k8s-master ~]# kubectl apply -f require-nodeAffinity-pod.yaml pod/with-require-nodeaffinity created [root@k8s-master ~]# kubectl get pods with-require-nodeaffinity NAME READY STATUS RESTARTS AGE with-require-nodeaffinity 0/1 Pending 0 16s [root@k8s-master ~]# kubectl label node k8s-node01 ssd=true node/k8s-node01 labeled [root@k8s-master ~]# kubectl get pods with-require-nodeaffinity NAME READY STATUS RESTARTS AGE with-require-nodeaffinity 1/1 Running 0 2m
在預選策略中,還能夠經過節點資源的可用性去限制可以成功調度,以下配置清單:要求的資源爲6核心CPU和20G內存,節點是沒法知足該容器的資源需求,所以也會調度失敗,Pod資源會處於Pending狀態。
apiVersion: v1 kind: Pod metadata: name: with-require-nodeaffinity spec: containers: - name: myapp image: ikubernetes/myapp:v1 resources: requests: cpu: 6 memory: 20Gi
看下面一個配置清單:
apiVersion: apps/v1 kind: Deployment metadata: name: myapp-deploy-with-node-affinity spec: replicas: 5 selector: matchLabels: app: myapp template: metadata: name: myapp-pod labels: app: myapp spec: affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 60 preference: matchExpressions: - {key: zone, operator: In, values: ["foo"]} - weight: 30 preference: matchExpressions: - {key: ssd, operator: Exists, values: []} containers: - name: myapp image: ikubernetes/myapp:v1 #首先先給node02和master都打上標籤,master標籤爲zone=foo,node02標籤爲ssd=true,這裏node01是沒有對應標籤的 [root@k8s-master ~]# kubectl label node k8s-node02 ssd=true node/k8s-node02 labeled [root@k8s-master ~]# kubectl label node k8s-master zone=foo node/k8s-master labeled #進行建立 [root@k8s-master ~]# kubectl apply -f deploy-with-preferred-nodeAffinity.yaml deployment.apps/myapp-deploy-with-node-affinity2 created #能夠看到5個Pod分別分佈在不一樣的節點上,node01上沒有對應的標籤也會調度上進行建立Pod,體現軟親和性 [root@k8s-master ~]# kubectl get pods -o wide |grep deploy-with myapp-deploy-with-node-affinity2-75b8f65f87-2gqjv 1/1 Running 0 11s 10.244.2.4 k8s-node02 myapp-deploy-with-node-affinity2-75b8f65f87-7l2sg 1/1 Running 0 11s 10.244.0.4 k8s-master myapp-deploy-with-node-affinity2-75b8f65f87-cdrxx 1/1 Running 0 11s 10.244.2.3 k8s-node02 myapp-deploy-with-node-affinity2-75b8f65f87-j77f6 1/1 Running 0 11s 10.244.1.36 k8s-node01 myapp-deploy-with-node-affinity2-75b8f65f87-wt6tq 1/1 Running 0 11s 10.244.0.3 k8s-master #若是咱們給node01打上zone=foo,ssd=true的標籤,再去建立時,你會發現全部的Pod都調度在這個節點上。由於節點的軟親和性,會盡力知足Pod中定義的規則,以下: [root@k8s-master ~]# kubectl label node k8s-node01 zone=foo [root@k8s-master ~]# kubectl label node k8s-node01 ssd=true [root@k8s-master ~]# kubectl get pods -o wide |grep deploy-with myapp-deploy-with-node-affinity2-75b8f65f87-4lwsw 0/1 ContainerCreating 0 3s <none> k8s-node01 myapp-deploy-with-node-affinity2-75b8f65f87-dxbxf 1/1 Running 0 3s 10.244.1.31 k8s-node01 myapp-deploy-with-node-affinity2-75b8f65f87-lnhgm 0/1 ContainerCreating 0 3s <none> k8s-node01 myapp-deploy-with-node-affinity2-75b8f65f87-snxbc 0/1 ContainerCreating 0 3s <none> k8s-node01 myapp-deploy-with-node-affinity2-75b8f65f87-zx8ck 1/1 Running 0 3s 10.244.1.33 k8s-node01
上面的實驗結果顯示,當2個標籤沒有都存在一個node節點上時,Pod對象會被分散在集羣中的三個節點上進行建立並運行,之因此如此,是由於使用了 節點軟親和性的預選方式,全部的節點都可以經過MatchNodeSelector
預選策略的篩選。當咱們將2個標籤都集合在node01上時,全部Pod對象都會運行在node01之上。
在出於高效通訊的需求,有時須要將一些Pod調度到相近甚至是同一區域位置(好比同一節點、機房、區域)等等,好比業務的前端Pod和後端Pod,此時這些Pod對象之間的關係能夠叫作親和性。
同時出於安全性的考慮,也會把一些Pod之間進行隔離,此時這些Pod對象之間的關係叫作反親和性(anti-affinity)。
調度器把第一個Pod放到任意位置,而後和該Pod有親和或反親和關係的Pod根據該動態完成位置編排,這就是Pod親和性和反親和性調度的做用。Pod的親和性定義也存在硬親和性和軟親和性的區別,其約束的意義和節點親和性相似。
Pod的親和性調度要求各相關的Pod對象運行在同一位置,而反親和性則要求它們不能運行在同一位置。這裏的位置實際上取決於節點的位置拓撲,拓撲的方式不一樣,Pod是否在同一位置的斷定結果也會有所不一樣。
若是基於各個節點的kubernetes.io/hostname
標籤做爲評判標準,那麼會根據節點的hostname
去斷定是否在同一位置區域。
Pod強制約束的親和性調度也是使用requiredDuringSchedulingIgnoredDuringExecution
進行定義的。Pod親和性是用來描述一個Pod對象和現有的Pod對象運行的位置存在某種依賴關係,因此若是要測試Pod親和性約束,須要存在一個被依賴的Pod對象,下面建立一個帶有app=tomcat
的Deployment資源部署一個Pod對象:
[root@k8s-master ~]# kubectl run tomcat -l app=tomcat --image=tomcat:alpine deployment.apps/tomcat created [root@k8s-master ~]# kubectl get pods -l app=tomcat -o wide NAME READY STATUS RESTARTS AGE IP NODE tomcat-75fd5cc757-w9qdb 1/1 Running 0 5m 10.244.1.37 k8s-node01
從上面咱們能夠看到新建立的tomcat
pod對象被調度在k8s-node01上,再寫一個配置清單定義一個Pod對象,經過labelSelector
定義的標籤選擇器挑選對應的Pod對象。
[root@k8s-master ~]# vim required-podAffinity-pod1.yaml apiVersion: v1 kind: Pod metadata: name: with-pod-affinity-1 spec: affinity: podAffninity: requiredDuringSchedulingIngnoreDuringExecution: - labelSelector: matchExpression: - {key: app , operator: In , values: ["tomcat"]} topologyKey: kubernetes.io/hostname containers: - name: myapp image: ikubernetes/myapp:v1
kubernetes.io/hostname
標籤是Kubernetes集羣節點的內建標籤,它的值爲當前節點的主機名,對於各個節點來講都是不一樣的。因此新建的Pod對象要被部署到和tomcat
所在的同一個節點上。
[root@k8s-master ~]# kubectl apply -f required-podAffinity-pod1.yaml pod/with-pod-affinity-1 created [root@k8s-master ~]# kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE with-pod-affinity-1 1/1 Running 0 31s 10.244.1.38 k8s-node01 tomcat-75fd5cc757-w9qdb 1/1 Running 0 5m 10.244.1.37 k8s-node01
基於單一節點的Pod親和性相對來講使用的狀況會比較少,一般使用的是基於同一地區、區域、機架等拓撲位置約束。好比部署應用程序(myapp)和數據庫(db)服務相關的Pod時,這兩種Pod應該部署在同一區域上,能夠加速通訊的速度。
同理,有硬親和度即有軟親和度,Pod也支持使用preferredDuringSchedulingIgnoredDuringExecuttion
屬性進行定義Pod的軟親和性,調度器會盡力知足親和約束的調度,在知足不了約束條件時,也容許將該Pod調度到其餘節點上運行。好比下面這一配置清單:
apiVersion: apps/v1 kind: Deployment metadata: name: myapp-with-preferred-pod-affinity spec: replicas: 3 selector: matchLabels: app: myapp template: metadata: name: myapp labels: app: myapp spec: affinity: podAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 80 podAffinityTerm: labelSelector: matchExpressions: - {key: app, operator: In , values: ["cache"]} topologyKey: zone - weight: 20 podAffinityTerm: labelSelector: matchExpressions: - {key: app, operator: In, values: ["db"]} topologyKey: zone containers: - name: myapp image: ikubernetes/mapp:v1
上述的清單配置當中,pod的軟親和調度須要將Pod調度到標籤爲app=cache
並在區域zone當中,或者調度到app=db
標籤節點上的,可是咱們的節點上並無相似的標籤,因此調度器會根據軟親和調度進行隨機調度到k8s-node01
節點之上。以下:
[root@k8s-master ~]# kubectl apply -f deploy-with-preferred-podAffinity.yaml deployment.apps/myapp-with-preferred-pod-affinity created [root@k8s-master ~]# kubectl get pods -o wide |grep myapp-with-preferred-pod-affinity myapp-with-preferred-pod-affinity-5c44649f58-cwgcd 1/1 Running 0 1m 10.244.1.40 k8s-node01 myapp-with-preferred-pod-affinity-5c44649f58-hdk8q 1/1 Running 0 1m 10.244.1.42 k8s-node01 myapp-with-preferred-pod-affinity-5c44649f58-kg7cx 1/1 Running 0 1m 10.244.1.41 k8s-node01
podAffinity
定義了Pod對象的親和約束,而Pod對象的反親和調度則是用podAntiAffinty
屬性進行定義,下面的配置清單中定義了由同一Deployment建立可是彼此基於節點位置互斥的Pod對象:
[root@k8s-master ~]# cat deploy-with-required-podAntiAffinity.yaml apiVersion: apps/v1 kind: Deployment metadata: name: myapp-with-pod-anti-affinity spec: replicas: 4 selector: matchLabels: app: myapp template: metadata: name: myapp labels: app: myapp spec: affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - {key: app,operator: In,values: ["myapp"]} topologyKey: kubernetes.io/hostname containers: - name: myapp image: ikubernetes/myapp:v1 [root@k8s-master ~]# kubectl apply -f deploy-with-required-podAntiAffinity.yaml deployment.apps/myapp-with-pod-anti-affinity created [root@k8s-master ~]# kubectl get pods -l app=myapp NAME READY STATUS RESTARTS AGE myapp-with-pod-anti-affinity-79c7b6c596-77hrz 1/1 Running 0 8s myapp-with-pod-anti-affinity-79c7b6c596-fhxmv 0/1 Pending 0 8s myapp-with-pod-anti-affinity-79c7b6c596-l9ckr 1/1 Running 0 8s myapp-with-pod-anti-affinity-79c7b6c596-vfv2s 1/1 Running 0 8s
因爲在配置清單中定義了強制性反親和性,因此建立的4個Pod副本必須 運行在不一樣的節點當中呢,可是集羣中只存在3個節點,所以,確定會有一個Pod對象處於Pending的狀態。
污點(taints)是定義在節點上的一組鍵值型屬性數據,用來讓節點拒絕將Pod調度到該節點上,除非該Pod對象具備容納節點污點的容忍度。而容忍度(tolerations)是定義在Pod對象上的鍵值型數據,用來配置讓Pod對象能夠容忍節點的污點。
前面的節點選擇器和節點親和性的調度方式都是經過在Pod對象上添加標籤選擇器來完成對特定類型節點標籤的匹配,實現的是Pod選擇節點的方式。而污點和容忍度則是經過對節點添加污點信息來控制Pod對象的調度結果,讓節點擁有了控制哪一種Pod對象能夠調度到該節點上的 一種方式。
Kubernetes使用PodToleratesNodeTaints預選策略和TaintTolerationPriority優選函數來完成這種調度方式。
污點的定義是在節點的nodeSpec,而容忍度的定義是在Pod中的podSpec,都屬於鍵值型數據,兩種方式都支持一個effect
標記,語法格式爲key=value: effect
,其中key和value的用戶和格式和資源註解相似,而effect
是用來定義對Pod對象的排斥等級,主要包含如下3種類型:
在Pod對象上定義容忍度時,其支持2中操做符:Equal
和Exists
在使用kubeadm部署的集羣中,master節點上將會自動添加污點信息,阻止不能容忍該污點的Pod對象調度到該節點上,以下:
[root@k8s-master ~]# kubectl describe node k8s-master Name: k8s-master Roles: master ...... Taints: node- role. kubernetes. io/ master: NoSchedule ......
而一些系統級別的應用在建立時,就會添加相應的容忍度來確保被建立時能夠調度到master節點上,如flannel插件:
[root@k8s-master ~]# kubectl describe pods kube-flannel-ds-amd64-2p8wm -n kube-system ...... Tolerations: node-role.kubernetes.io/master:NoSchedule node.kubernetes.io/disk-pressure:NoSchedule node.kubernetes.io/memory-pressure:NoSchedule node.kubernetes.io/not-ready:NoExecute node.kubernetes.io/unreachable:NoExecute ......
使用命令行向節點添加污點
語法:kubectl taint nodes <nodename> <key>=<value>:<effect>...... #定義k8s-node01上的污點 [root@k8s-master ~]# kubectl taint nodes k8s-node01 node-type=production:NoSchedule node/k8s-node01 tainted #查看節點污點信息 [root@k8s-master ~]# kubectl get nodes k8s-node01 -o go-template={{.spec.taints}} [map[effect:NoSchedule key:node-type value:production]]
此時,node01節點上已經存在的Pod對象不受影響,僅對新建Pod對象有影響,須要注意的是,若是是同一個鍵值數據,可是最後的標識不一樣,也是屬於不一樣的污點信息,好比再給node01上添加一個污點的標識爲:PreferNoSchedule
[root@k8s-master ~]# kubectl taint nodes k8s-node01 node-type=production:PreferNoSchedule node/k8s-node01 tainted [root@k8s-master ~]# kubectl get nodes k8s-node01 -o go-template={{.spec.taints}} [map[value:production effect:PreferNoSchedule key:node-type] map[key:node-type value:production effect:NoSchedule]]
刪除污點
語法:kubectl taint nodes <node-name> <key>[: <effect>]- #刪除node01上的node-type標識爲NoSchedule的污點 [root@k8s-master ~]# kubectl taint nodes k8s-node01 node-type:NoSchedule- node/k8s-node01 untainted #刪除指定鍵名的全部污點 [root@k8s-master ~]# kubectl taint nodes k8s-node01 node-type- node/k8s-node01 untainted #補丁方式刪除節點上的所有污點信息 [root@k8s-master ~]# kubectl patch nodes k8s-node01 -p '{"spec":{"taints":[]}}'
Pod對象的容忍度能夠經過spec.tolerations
字段進行添加,同一的也有兩種操做符:Equal
和Exists
方式。Equal等值方式以下:
tolerations: - key: "key1" operator: "Equal" value: "value1" effect: "Noexecute" tolerationSeconds: 3600
Exists方式以下:
tolerations: - key: "key1" operator: "Exists" effect: "NoExecute" tolerationSeconds: 3600