前言
k8s 其中一個很大的優點在於很強大的 scheduler,在絕大多數時的情況下,使用者只要提交自己想要佈署的服務,k8s 就會自動的找到合適的 worker node 來運行,完全不用擔心 woker node 之間工作負載不平衡的狀況發生。
但有些時候,我們可能會希望可以人為介入 pod scheduling 的工作,例如:
希望讓 pod 運作在某些帶有某些特定硬體(例如:SSD)的 worker node 上
希望某些 pod 可以被固定放在一起
k8s 也提供了一些機制可以讓我們自己決定 job scheduling 是如何進行的,而這些機制基本上都是建立在 label select 的基礎上完成的,以下來逐一介紹。
nodeSelector
nodeSelector 是目前 k8s 提供的方式中最簡單的一個,只要在 pod spec 上指定所希望的 key/value pair 作為 nodeSelector,k8s 就會協助找到有相同 label 的 worker node 來接手工作。
但 nodeSelector 要怎麼用呢? 以下是簡單的流程說明:
為 Worker Node 設定 Label
上面提到可以指定 pod 到帶有特定 key/value pair 的 worker node 上,那當然 worker node 上一定要有特定的 key/value pair 才行,以下是為 worker node 指定 key/value pair 的方式
kubectl label nodes/<node-name> <label-key>=<label-value>
以下是實際示範過程:
1 | # 檢視目前每個 node 所帶的 label |
從上面的 label 資訊可以看出,其實 k8s 剛安裝好後,就會為每個 node 自動加上一些預設的 label,這些 label 可能會在未來作為識別 or scheduling 時的一個依據,通常 public cloud provider 上佈署的 k8s,也會根據需求為每個 node 加上預設的 label 資訊
為 Pod spec 設定 nodeSelector
接著以下就是新增一個帶有 nodeSelector 的 pod,範例如下:
1 | apiVersion: v1 |
套用以上設定後,檢視一下 pod 的內容:
1 | # 檢視 pod 運作細節 |
Built-in Node Labels
剛安裝好 k8s 後,如果有曾經檢查過 node status 的話,可能會發現其實每個 node 都會有一些內建的 label set,以下是剛安裝好 v1.12.1 後的結果:
1 | kubectl get nodes --show-labels |
可以看出內建的 label set 有以下幾項:
beta.kubernetes.io/arch
beta.kubernetes.io/os
kubernetes.io/hostname
node-role.kubernetes.io/master
node-role.kubernetes.io/node
以上是在本地端環境安裝 k8s 後的結果,若是在 public cloud 上,為了管理方便,可能會有用來表示 Zone or AZ 這一類資訊 label 存在。
而以上這些內建的 node label,也會跟下面提到 affinity 機制在運行的時候會有相關,而這些 node label 則會被作為 topologyKey
(下面會看到) 來使用。
為何這些 node label 要稱為
topologyKey
? 因為從人類的角度來看,這些 node 的規劃透過以 topology 的型式來呈現,會比較容易理解
Node Affinity & anti-affinity
上述的 nodeSelector 提供了一個簡單的方式讓使用者可以將 pod 分派到帶有特定 label 的 worker node 上,但 nodeSelector 有時候並無法滿足現實世界的複雜需求,因此 affinity/anti-affinity 的功能就應運而生了。(目前在 v1.12 中還是屬於 beta feature)
而 affinity/anti-affinity 主要加強了幾個地方:
可用更彈性的方法來指定多個 label 時的組合,而不再只能用 AND
以往只能設定是否完全符合條件,現在可以用
preference(希望可以有,但沒有也沒關係)
的方式來設定可以指定跟帶有某些 label 的
pod
放在一起(or 不要放在一起),而不是只能指定 worker node label:這樣有助於讓某些 pod 可以被放在同一個 worker node(或不被放在一起)
從上面幾個加強的地方可以看出,affinity/anti-affinity 這個 feature 共分為兩大類,分別是:
node affinity
inter-pod affinity/anti-affinity
nodeSelector 目前還是可以正常使用,但最終會被 node affinity 取代,因為 node affinity 可以完全取代 nodeSelector 的功能之外,還可以提供更多的其他功能,就像 RepplicationController & ReplicaSet 的關係是相同的。(ReplicaSet 取代了 RepplicationController)
Node affinity/anti-affinity
node affinity 在 v1.2 之後所提供的功能,類似 nodeSelector ,有以下兩種類型:
requiredDuringSchedulingIgnoredDuringExecution
preferredDuringSchedulingIgnoredDuringExecution
上面的設定可以拆開三個部份來看,分別是:
requiredDuringScheduling
:一定要 node 符合條件,scheduler 才會把 pod 分派到上面去跑preferredDuringScheduling
:會儘量嘗試找尋合適條件的 node,但不強制IgnoredDuringExecution
:表示當 pod 已經正在運作中了,即使 node 的 label 在之後遭到變更,也不會影響正在運作中的 pod
從上面的說明就可以知道,兩種組合的搭配所得到的結果會是如何。
之後還會推出
requiredDuringSchedulingRequiredDuringExecution
的功能,而且同樣的,看字面的意思就可以很清楚了解這個設定會有什麼樣的效果
以下是一個 node affinity 的範例:
1 | apiVersion: v1 |
除了上面範例中的 In
之外,k8s 還支援了其他的 operator 像是 NotIn
, Exists
, DoesNotExist
, Gt
, Lt
… 等等,可以根據實際需求使用。
此外,node affinity 在使用上還有一些其他規則的存在,例如:
若同時設定
nodeSelector
&nodeAffinity
,那 scheduler 就會尋找同時滿足兩個條件要求的 node如果在
nodeAffinity
中設定多個nodeSelectorTerms
,那就只要滿足任何一個 nodeSelectorTerms 的要求即可node affinity 目前只有在 pod scheduling 的時候會有用途,當 pod 已經在 node 上運行後,即使 node label 變更了也不會影響正在上面運行中的 pod,除非之後
requiredDuringSchedulingRequiredDuringExecution
的功能有推出在 prefer 的設定中,有個
weight
的權重值(1-100)可以設定,而 scheduler 在決定前,還會加上其他 node priority function 來進行綜合考量,最後 pod 會被分配到數值計算結果最高的 node 上去
Inter-pod affinity/anti-affinity
為什麼需要 pod affinity/anti-affinity?
這功能有時候挺有用的,特別是跟 ReplicaSets, StatefulSets 或是 Deployments 一起搭配的時候;例如:我希望把 workload 分派到特定的 topology 的運行(例如:同一個 node)
pod affinity 的功能是在 v1.4 的時候提出,其實這跟 node affinity 很類似,只是 scheduler 要尋找的是目前有哪些正在運行中的 pod 帶有符合條件的 label set,而不是 node。
規則的設定原則應該是幫忙尋找符合設定條件的 Pod & 所在的 node 在哪裡,並把 Pod 放到該 node 上面運行
而且 pod affinity 跟 node affinity 相同,也是有以下兩種設定類型可以使用:
requiredDuringSchedulingIgnoredDuringExecution
preferredDuringSchedulingIgnoredDuringExecution
而兩種設定的運作規則當然也跟 node affinity 相同,但通常 preferredDuringSchedulingIgnoredDuringExecution
常與 pod anti-affinity 搭配使用,而通常這兩者的搭配,大多是要達成儘量將 pod 分散配置的目的才會這樣使用。
當實際要設定 pod affinity/anti-affinity,有兩個條件可以用來進行設定:
pod label set:這個部份其實很清楚,跟 node label set 其實是一樣的東西
node topology key:這個部份通常就是 k8s 的 build-in node label (表示希望檢查 worker node 是否也符合條件)
topologyKey 的設定用意在於,如果有多個 pod 需要分配,並指定了 topologyKey,那 scheduler 在分配時就不可以(注意! 是不可以!)將多個 pod 放到帶有相同 value 的topologyKey(Label) 的 node 上
以下用一個實際的例子來說明:
假設 k8s cluster 中有 3 個 worker node(node03, node04, node05),且要在裡面安裝一個三份 replica 的 web application,而這個 web application 使用到 redis 作為 cache,為了平均的分散 work load,希望可以將 pod 佈署成:
node03
: redis + web-appnode04
: redis + web-appnode05
: redis + web-app
也就是每個 worker 都帶有一份 redis + web app,這要如何透過 pod affinity/anti-affinity 來完成呢? 可以參考以下步驟:
確認 topologyKey (node Label)
1 | # 從上面可以看出,每個 worker node 中的 "kubernetes.io/hostname" 都帶有不同的值,可以拿來作為 topologyKey |
佈署 redis,並確保分散在不同的 node
接著要來佈署 redis 作為 cache service,但由於要將 redis 分散到不同的 node,因此進行了以下設定:
1 | apiVersion: apps/v1 |
套用完以上設定後,可以看到 k8s 成功的將 redis cache 分散到不同的 worker node 上
1 | $ kubectl get pod -o wide |
佈署 web app,並跟 redis 放一起
為了提升效能,佈署 web application 的時候,每個 web application 就要搭配一個 redis cache,可以透過以下設定完成:
1 | apiVersion: apps/v1 |
套用完以上設定後,就可以看到 k8s 系統中已經將 web app 跟 redis 一對一對的分派到不同的 worker node 上:
1 | $ kubectl get pod -o wide |
因此從上面的例子可以看到,若不要讓 pod 分配到同一個 node 上,只要設定
podAntiAffinity
+topologyKey: "kubernetes.io/hostname"
即可 (因為kubernetes.io/hostname
是 built-in 的 label,並且都會帶上不同的 value)
使用 affinity 的注意事項
官網的文件中有提到,由於 inter-pod affinity/anti-affinity 需要消耗大量的計算資源來作比對,因此若是 cluster node 數大於 700 的情況下,不建議使用這個功能,會大大降低整個 cluster 的運作速度。
不過是說一般人要用到超過 700 個 node 的 k8s cluster 似乎也是沒什麼機會倒是真的….