K8s 容器的定向排程與親和性

kubesphere發表於2024-10-30

K8s 容器的定向排程與親和性

K8s 叢集節點 CPU 使用率高!記憶體溢位(OOM)!當機!導致大量微服務癱瘓怎麼辦?可能是排程策略沒做好,看完這篇文章掌握提高叢集穩定性的管理訣竅。

Kubernetes(K8s)是一個開源的容器編排工具,而容器排程是其非常重要的特性,所謂的排程是指將容器(Pod)分配到叢集中的節點上執行的過程。為了更好地控制容器的排程,K8s 提供了多種排程策略,其中包括定向排程和親和性策略。在實際的 K8s 叢集維護場景中,合理使用這些排程策略,對叢集的穩定性至關重要。本文將透過分享實踐案例,幫助你更好地理解和使用這些功能。

定向排程

定向排程透過 nodeNamenodeSelector 來宣告 Pod 期望排程的目標節點,這種方式的排程是強制性的,不管節點是否存在,是否當機,都會往宣告的節點上去排程,當目標不存在或不可排程時,將會導致 Pod 無法執行。

  • nodeName

強制將 Pod 排程到指定主機名的節點上,這種方式簡單粗暴,沒有經過 Scheduler 的排程邏輯。

示例:我有一個機器學習的應用,需要排程到叢集中唯一的 GPU 節點上,可以這樣做。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: athena
spec:
  replicas: 1
  selector:
    matchLabels:
      app: athena
  template:
    metadata:
      labels:
        app: athena
    spec:
      containers:
      - name: athena
        image: athena:2.0.0
      nodeName: k8s-node-gpu-1
  • NodeSelector

強制將 Pod 排程到指定標籤的節點上,這種方式透過 Label-selector 機制實現,在 Pod 建立之前,會由 Schedule 的 MatchNodeSelector 排程策略根據 Label 匹配節點,再將 Pod 排程到目標節點上。

示例:我有一個機器學習的應用,需要排程到叢集中帶有 hardware-type:gpu 標籤的節點上,帶有該標籤的節點有多臺,可以這樣做。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: athena
spec:
  replicas: 1
  selector:
    matchLabels:
      app: athena
  template:
    metadata:
      labels:
        app: athena
    spec:
      containers:
      - name: athena
        image: athena:2.0.0
      nodeSelector:
        hardware-type: gpu
        # gpu-type: T4 (允許有多label匹配)

定向排程比較簡單粗暴,那有沒有相對溫和、靈活點的排程策略呢?當然是有的,接下來讓我們來看看親和性排程策略。

親和性排程

親和性排程(Affinity)在定向排程的基礎上,透過靈活的節點親和性(nodeAffinity)、Pod 親和性(podAffinity)、Pod 反親和性(podAntiAffinity)規則,滿足更多樣化的排程場景。

  • nodeAffinity

nodeSelector 更加強大和靈活,可以讓 Pod 滿足更多樣化的條件排程到指定的節點上,支援“軟性排程”(PreferredDuringSchedulingIgnoreDuringExecution)和“硬性排程”(RequiredDuringSchedulingIgnoredDuringExecution)”,硬性排程比較強硬,不滿足條件則排程不成功,而軟性排程相對溫和,屬於傾向性優先選擇滿足條件的節點,並不強求。

讓我們來看兩個示例,加深理解:

示例 1:我有一個機器學習的應用,必須排程到叢集中帶有 hardware-type: gpu,且區域 kubernetes.io/zone 的值為 cn-shenzhen-1 或 cn-shenzhen-2 標籤的節點上。我們可以透過親和性的硬性排程實現,具體如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: athena
spec:
  replicas: 2
  selector:
    matchLabels:
      app: athena
  template:
    metadata:
      labels:
        app: athena
    spec:
      containers:
      - name: athena
        image: athena:2.0.0
      affinity:
        nodeAffinity:
          # 硬性排程,節點必須滿足所有條件才可以排程
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: hardware-type
                # 運算
                operator: In
                values:
                - gpu
              - key: kubernetes.io/zone
                operator: In
                values:
                - cn-shenzhen-1
                - cn-shenzhen-2

Operator 支援的運算子還有:

Exists(key必須存在,value可以是任意的)
DoesNotExist(key不能存在)
In(key的value必須在提供的值列表中)
NotIn(key的value不能在提供的值列表中)
Gt(key的value必須大於提供的值,僅支援整數)
Lt(key的value必須小於提供的值)

示例 2:我有一個機器學習的應用,傾向於排程到叢集中帶有 hardware-type: gpu,且區域 kubernetes.io/zone 的值為 cn-shenzhen-1 或 cn-shenzhen-2 標籤的節點上。我們可以透過親和性的軟性排程實現,如果不能滿足條件,他也會嘗試去排程其他節點,具體如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: athena
spec:
  replicas: 2
  selector:
    matchLabels:
      app: athena
  template:
    metadata:
      labels:
        app: athena
    spec:
      containers:
      - name: athena
        image: athena:2.0.0
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          # 滿足條件的節點會加分,值支援(1-100),分數越高,優先順序越高
          # 不加的話,滿足條件的節點權重也為0,不能保證其優先順序。
          - weight: 1
            preference:
              matchExpressions:
              - key: hardware-type
                # 運算,支援的運算子跟硬性排程一致
                operator: In
                values:
                - gpu
              - key: kubernetes.io/zone
                operator: In
                values:
                - cn-shenzhen-1
                - cn-shenzhen-2
  • Pod 親和性(podAffinity)和反親和性(podAntiAffinity)

顧名思義,Pod 親和性用來指定哪些 Pod 應該跟哪些 Pod 更加靠近,而 Pod 反親和性通常用來打散 Pod,讓某些 Pod 不在同一節點或區域,同樣也有“軟性排程”(PreferredDuringSchedulingIgnoreDuringExecution)”和“硬性排程”
(RequiredDuringSchedulingIgnoredDuringExecution),接下來我將用一個示例,加深對 Pod 親和性和反親和性的理解:

示例:有兩個微服務 zeusathena 相互呼叫比較頻繁,他們都有兩個副本,出於提升效率和可用性考慮,我想將 zeus 和 athena 的副本打散到兩個不同的可用區(zone),並讓他們的副本必須部署到同一個節點上,假設 zeus 已經部署好了,那 athena 的部署可以這樣實現。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: athena
spec:
  replicas: 2
  selector:
    matchLabels:
      app: athena
  template:
    metadata:
      labels:
        app: athena
    spec:
      containers:
      - name: athena
        image: athena:2.0.0
      affinity:
        # Pod親和性
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: zeus
            # 拓撲鍵,表示在相同主機上排程
            topologyKey: kubernetes.io/hostname
        # Pod反親和性
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: athena
            # 拓撲鍵,表示在不同區域上排程
            topologyKey: topology.kubernetes.io/zone

結 語

在文章開頭我們提到如何藉助排程策略來提升 K8s 叢集的可用性,相信看完全文的小夥伴都可以悟出其中奧妙,我們可以將高計算、高記憶體的 Pod 排程到指定的節點,避免影響關鍵服務執行,另外為了保障微服務的高可用性,我們通常會打散副本到不同的節點或者可用區。

本文首發:SRE運維手記,作者:亦零一

相關文章