本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。

Kubernetes 中的高階排程

編者注:本文是關於 Kubernetes 1.6 新功能的一系列深度文章的一部分

Kubernetes 排程器的預設行為在大多數情況下都表現良好——例如,它確保 Pod 只部署在擁有足夠空閒資源的節點上,它嘗試將來自同一組的 Pod(ReplicaSetStatefulSet 等)分散到不同的節點上,它嘗試平衡節點的資源利用率等等。

但有時您希望控制 Pod 的排程方式。例如,您可能希望確保某些 Pod 只調度到具有專用硬體的節點上,或者您希望將頻繁通訊的服務放在一起,或者您希望將一組節點專用於特定使用者。最終,您比 Kubernetes 更瞭解應用程式應如何排程和部署。因此,Kubernetes 1.6 提供了四種高階排程功能:節點親和性/反親和性、汙點和容忍度、Pod 親和性/反親和性以及自定義排程器。這些功能現在都在 Kubernetes 1.6 中處於 Beta 階段。

節點親和性/反親和性

節點親和性/反親和性 是排程器選擇節點的規則之一。此功能是自 Kubernetes 1.0 版本以來一直存在的 nodeSelector 功能的泛化。這些規則使用節點上的自定義標籤和 Pod 中指定的選擇器這些熟悉的概念來定義,並且它們可以是必需的或首選的,具體取決於您希望排程器強制執行的嚴格程度。

對於 Pod 在特定節點上排程,必須滿足必需規則。如果沒有節點符合條件(以及所有其他正常條件,例如為 Pod 的資源請求提供足夠的空閒資源),則 Pod 將不會被排程。必需規則在 nodeAffinity 的 requiredDuringSchedulingIgnoredDuringExecution 欄位中指定。

例如,如果我們希望在多區域 Kubernetes 叢集的 us-central1-a GCE 區域中的節點上強制排程,我們可以將以下親和性規則指定為 Pod 規範的一部分:

  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
            - key: "failure-domain.beta.kubernetes.io/zone"
              operator: In
              values: ["us-central1-a"]

“IgnoredDuringExecution”表示如果節點上的標籤發生變化且不再滿足親和性規則,Pod 仍將執行。未來計劃提供 requiredDuringSchedulingRequiredDuringExecution,它將在 Pod 不再滿足節點親和性規則時立即將其從節點中逐出。

首選規則意味著如果節點符合規則,它們將首先被選擇,並且只有在沒有首選節點可用時才會選擇非首選節點。您可以透過稍微更改 Pod 規範以使用 preferredDuringSchedulingIgnoredDuringExecution 來優先而不是強制將 Pod 部署到 us-central1-a

  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
            - key: "failure-domain.beta.kubernetes.io/zone"
              operator: In
              values: ["us-central1-a"]

可以透過使用否定運算子來實現節點反親和性。因此,例如,如果我們希望我們的 Pod 避免 us-central1-a,我們可以這樣做:

  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
            - key: "failure-domain.beta.kubernetes.io/zone"
              operator: NotIn
              values: ["us-central1-a"]

您可以使用的有效運算子包括 In、NotIn、Exists、DoesNotExist、Gt 和 Lt。

此功能的其他用例是根據節點的硬體架構、作業系統版本或專用硬體限制排程。節點親和性/反親和性在 Kubernetes 1.6 中處於 Beta 階段。

汙點和容忍度

一個相關的功能是“汙點和容忍度”,它允許您“汙點”一個節點,以便除非 Pod 明確“容忍”該汙點,否則任何 Pod 都無法排程到該節點上。標記節點而不是 Pod(如節點親和性/反親和性)對於叢集中大多數 Pod 應該避免排程到該節點的情況特別有用。例如,您可能希望將主節點標記為僅由 Kubernetes 系統元件排程,或將一組節點專用於特定使用者組,或使常規 Pod 遠離具有特殊硬體的節點,以便為需要特殊硬體的 Pod 留出空間。

kubectl 命令允許您在節點上設定汙點,例如:

kubectl taint nodes node1 key=value:NoSchedule

建立了一個汙點,將節點標記為無法被任何沒有容忍該汙點(key 為 key,value 為 value,效果為 NoSchedule)的 Pod 排程。(其他汙點效果包括 PreferNoSchedule,它是 NoSchedule 的首選版本,以及 NoExecute,這意味著當應用汙點時,任何正在節點上執行的 Pod 都將被驅逐,除非它們容忍該汙點。)您需要新增到 PodSpec 以使相應的 Pod 容忍此汙點的內容如下所示:

  tolerations:
  - key: "key"
    operator: "Equal"
    value: "value"
    effect: "NoSchedule"

除了在 Kubernetes 1.6 中將汙點和容忍度提升為 Beta 版之外,我們還引入了一項 Alpha 功能,它使用汙點和容忍度來允許您自定義當節點遇到網路分割槽等問題時,Pod 繫結到節點的時間長度,而不是使用預設的五分鐘。有關更多詳細資訊,請參閱文件的此部分

Pod 親和性/反親和性

節點親和性/反親和性允許您根據節點的標籤限制 Pod 可以執行的節點。但是,如果您想指定 Pod 之間如何相對放置的規則,例如在服務內部或相對於其他服務中的 Pod 分散或打包 Pod 呢?為此,您可以使用 Pod 親和性/反親和性,它也在 Kubernetes 1.6 中處於 Beta 階段。

讓我們看一個例子。假設服務 S1 中有前端,它們與服務 S2 中的後端頻繁通訊(“南北”通訊模式)。因此,您希望這兩個服務位於同一個雲提供商區域,但您不想手動選擇區域——如果區域出現故障,您希望 Pod 被重新排程到另一個(單個)區域。您可以使用如下所示的 Pod 親和性規則來指定這一點(假設您給此服務的 Pod 貼上“service=S2”標籤,給另一個服務的 Pod 貼上“service=S1”標籤):

affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: service
            operator: In
            values: [“S1”]
        topologyKey: failure-domain.beta.kubernetes.io/zone

與節點親和性/反親和性一樣,也有一個 preferredDuringSchedulingIgnoredDuringExecution 變體。

Pod 親和性/反親和性非常靈活。想象一下,您已經分析了服務的效能,發現服務 S1 的容器與服務 S2 的容器在共享同一節點時會相互干擾,這可能是由於快取干擾效應或網路鏈路飽和。或者,出於安全考慮,您可能不希望 S1 和 S2 的容器共享一個節點。要實現這些規則,只需對上面的程式碼片段進行兩處更改——將 podAffinity 更改為 podAntiAffinity,並將 topologyKey 更改為 kubernetes.io/hostname。

自定義排程器

如果 Kubernetes 排程器的各種功能無法讓您充分控制工作負載的排程,您可以將排程任意 Pod 子集的責任委託給您自己的自定義排程器,這些排程器與預設的 Kubernetes 排程器並行執行或取代它。在 Kubernetes 1.6 中,多排程器 處於 Beta 階段。

每個新 Pod 通常由預設排程器排程。但是,如果您提供自己的自定義排程器的名稱,預設排程器將忽略該 Pod,並允許您的排程器將該 Pod 排程到節點上。讓我們看一個例子。

這裡我們有一個 Pod,其中我們指定了 schedulerName 欄位

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  schedulerName: my-scheduler
  containers:
  - name: nginx
    image: nginx:1.10

如果我們建立這個 Pod 但不部署自定義排程器,預設排程器將忽略它,它將保持在 Pending 狀態。因此,我們需要一個自定義排程器,它會查詢並排程 schedulerName 欄位為 my-scheduler 的 Pod。

自定義排程器可以用任何語言編寫,並且可以根據您的需要簡單或複雜。這是一個用 Bash 編寫的自定義排程器非常簡單的示例,它隨機分配節點。請注意,您需要將其與 kubectl proxy 一起執行才能使其工作。

#!/bin/bash

SERVER='localhost:8001'

while true;

do

    for PODNAME in $(kubectl --server $SERVER get pods -o json | jq '.items[] | select(.spec.schedulerName == "my-scheduler") | select(.spec.nodeName == null) | .metadata.name' | tr -d '"')

;

    do

        NODES=($(kubectl --server $SERVER get nodes -o json | jq '.items[].metadata.name' | tr -d '"'))


        NUMNODES=${#NODES[@]}

        CHOSEN=${NODES[$[$RANDOM % $NUMNODES]]}

        curl --header "Content-Type:application/json" --request POST --data '{"apiVersion":"v1", "kind": "Binding", "metadata": {"name": "'$PODNAME'"}, "target": {"apiVersion": "v1", "kind"

: "Node", "name": "'$CHOSEN'"}}' http://$SERVER/api/v1/namespaces/default/pods/$PODNAME/binding/

        echo "Assigned $PODNAME to $CHOSEN"

    done

    sleep 1

done

瞭解更多

Kubernetes 1.6 發行說明 提供了有關這些功能的更多資訊,包括如果您已經在使用這些功能中的一個或多個 Alpha 版本,如何更改配置的詳細資訊(這是必需的,因為從 Alpha 到 Beta 的轉變對這些功能來說是破壞性更改)。

致謝

這裡描述的功能,無論是以 Alpha 還是 Beta 形式,都是真正的社群努力,涉及來自 Google、華為、IBM、Red Hat 等公司的工程師。

參與其中

在我們的每週社群會議上分享您的聲音

  • Stack Overflow 上提問(或回答問題)
  • 在 Twitter 上關注我們 @Kubernetesio 獲取最新更新
  • Slack 上與社群聯絡(房間 #sig-scheduling)

非常感謝您的貢獻。