介紹 kube-scheduler-simulator

Kubernetes 排程器是控制平面中一個至關重要的元件,它決定了 Pod 將在哪個節點上執行。因此,任何使用 Kubernetes 的人都依賴於排程器。

kube-scheduler-simulator 是 Kubernetes 排程器的一個**模擬器**,它最初是我(Kensei Nakada)作為 Google Summer of Code 2021 專案開發的,後來收到了許多貢獻。這個工具允許使用者仔細檢查排程器的行為和決策。

它對於那些使用排程約束(例如,Pod 間親和性)的普通使用者以及用自定義外掛擴充套件排程器的專家都很有用。

動機

排程器通常像一個黑盒,由許多外掛組成,每個外掛都從其獨特的角度為排程決策過程做出貢獻。由於它考慮了眾多因素,理解其行為可能具有挑戰性。

即使在一個簡單的測試叢集中,Pod 看起來被正確排程了,它也可能是基於與預期不同的計算結果被排程的。當部署到大型生產環境時,這種差異可能導致意想不到的排程結果。

此外,測試排程器是一個複雜的挑戰。在一個真實的叢集中執行的操作模式有無數種,使得用有限數量的測試來預測每一種情況變得不可行。更多時候,只有在排程器部署到實際叢集中時才會發現錯誤。實際上,即使是上游的 kube-scheduler,許多錯誤也是在釋出後由使用者發現的。

擁有一個用於測試排程器(或者實際上是任何 Kubernetes 控制器)的開發或沙箱環境是一種常見的做法。然而,這種方法無法捕捉到生產叢集中可能出現的所有潛在場景,因為開發叢集通常要小得多,並且在工作負載大小和擴充套件動態方面存在顯著差異。它永遠不會經歷與生產環境完全相同的使用情況或表現出相同的行為。

kube-scheduler-simulator 旨在解決這些問題。它使使用者能夠測試他們的排程約束、排程器配置和自定義外掛,同時檢查排程決策的每個詳細部分。它還允許使用者建立一個模擬的叢集環境,在那裡他們可以使用與生產叢集相同的資源來測試他們的排程器,而不會影響實際的工作負載。

kube-scheduler-simulator 的特性

kube-scheduler-simulator 的核心特性是它能夠揭示排程器內部的決策過程。排程器基於排程框架執行,在不同的擴充套件點使用各種外掛,篩選節點(Filter 階段)、為節點打分(Score 階段),並最終確定 Pod 的最佳節點。

模擬器允許使用者建立 Kubernetes 資源,並觀察每個外掛如何影響 Pod 的排程決策。這種可見性幫助使用者理解排程器的工作原理,並定義適當的排程約束。

Screenshot of the simulator web frontend that shows the detailed scheduling results per node and per extension point

模擬器 Web 前端

在模擬器內部,執行的是一個可除錯的排程器,而不是普通的排程器。這個可除錯的排程器會將每個排程器外掛在每個擴充套件點的結果輸出到 Pod 的註解中,如下面的清單所示,然後 Web 前端會根據這些註解格式化和視覺化排程結果。

kind: Pod
apiVersion: v1
metadata:
  # The JSONs within these annotations are manually formatted for clarity in the blog post. 
  annotations:
    kube-scheduler-simulator.sigs.k8s.io/bind-result: '{"DefaultBinder":"success"}'
    kube-scheduler-simulator.sigs.k8s.io/filter-result: >-
      {
        "node-jjfg5":{
            "NodeName":"passed",
            "NodeResourcesFit":"passed",
            "NodeUnschedulable":"passed",
            "TaintToleration":"passed"
        },
        "node-mtb5x":{
            "NodeName":"passed",
            "NodeResourcesFit":"passed",
            "NodeUnschedulable":"passed",
            "TaintToleration":"passed"
        }
      }      
    kube-scheduler-simulator.sigs.k8s.io/finalscore-result: >-
      {
        "node-jjfg5":{
            "ImageLocality":"0",
            "NodeAffinity":"0",
            "NodeResourcesBalancedAllocation":"52",
            "NodeResourcesFit":"47",
            "TaintToleration":"300",
            "VolumeBinding":"0"
        },
        "node-mtb5x":{
            "ImageLocality":"0",
            "NodeAffinity":"0",
            "NodeResourcesBalancedAllocation":"76",
            "NodeResourcesFit":"73",
            "TaintToleration":"300",
            "VolumeBinding":"0"
        }
      }       
    kube-scheduler-simulator.sigs.k8s.io/permit-result: '{}'
    kube-scheduler-simulator.sigs.k8s.io/permit-result-timeout: '{}'
    kube-scheduler-simulator.sigs.k8s.io/postfilter-result: '{}'
    kube-scheduler-simulator.sigs.k8s.io/prebind-result: '{"VolumeBinding":"success"}'
    kube-scheduler-simulator.sigs.k8s.io/prefilter-result: '{}'
    kube-scheduler-simulator.sigs.k8s.io/prefilter-result-status: >-
      {
        "AzureDiskLimits":"",
        "EBSLimits":"",
        "GCEPDLimits":"",
        "InterPodAffinity":"",
        "NodeAffinity":"",
        "NodePorts":"",
        "NodeResourcesFit":"success",
        "NodeVolumeLimits":"",
        "PodTopologySpread":"",
        "VolumeBinding":"",
        "VolumeRestrictions":"",
        "VolumeZone":""
      }      
    kube-scheduler-simulator.sigs.k8s.io/prescore-result: >-
      {
        "InterPodAffinity":"",
        "NodeAffinity":"success",
        "NodeResourcesBalancedAllocation":"success",
        "NodeResourcesFit":"success",
        "PodTopologySpread":"",
        "TaintToleration":"success"
      }      
    kube-scheduler-simulator.sigs.k8s.io/reserve-result: '{"VolumeBinding":"success"}'
    kube-scheduler-simulator.sigs.k8s.io/result-history: >-
      [
        {
            "kube-scheduler-simulator.sigs.k8s.io/bind-result":"{\"DefaultBinder\":\"success\"}",
            "kube-scheduler-simulator.sigs.k8s.io/filter-result":"{\"node-jjfg5\":{\"NodeName\":\"passed\",\"NodeResourcesFit\":\"passed\",\"NodeUnschedulable\":\"passed\",\"TaintToleration\":\"passed\"},\"node-mtb5x\":{\"NodeName\":\"passed\",\"NodeResourcesFit\":\"passed\",\"NodeUnschedulable\":\"passed\",\"TaintToleration\":\"passed\"}}",
            "kube-scheduler-simulator.sigs.k8s.io/finalscore-result":"{\"node-jjfg5\":{\"ImageLocality\":\"0\",\"NodeAffinity\":\"0\",\"NodeResourcesBalancedAllocation\":\"52\",\"NodeResourcesFit\":\"47\",\"TaintToleration\":\"300\",\"VolumeBinding\":\"0\"},\"node-mtb5x\":{\"ImageLocality\":\"0\",\"NodeAffinity\":\"0\",\"NodeResourcesBalancedAllocation\":\"76\",\"NodeResourcesFit\":\"73\",\"TaintToleration\":\"300\",\"VolumeBinding\":\"0\"}}",
            "kube-scheduler-simulator.sigs.k8s.io/permit-result":"{}",
            "kube-scheduler-simulator.sigs.k8s.io/permit-result-timeout":"{}",
            "kube-scheduler-simulator.sigs.k8s.io/postfilter-result":"{}",
            "kube-scheduler-simulator.sigs.k8s.io/prebind-result":"{\"VolumeBinding\":\"success\"}",
            "kube-scheduler-simulator.sigs.k8s.io/prefilter-result":"{}",
            "kube-scheduler-simulator.sigs.k8s.io/prefilter-result-status":"{\"AzureDiskLimits\":\"\",\"EBSLimits\":\"\",\"GCEPDLimits\":\"\",\"InterPodAffinity\":\"\",\"NodeAffinity\":\"\",\"NodePorts\":\"\",\"NodeResourcesFit\":\"success\",\"NodeVolumeLimits\":\"\",\"PodTopologySpread\":\"\",\"VolumeBinding\":\"\",\"VolumeRestrictions\":\"\",\"VolumeZone\":\"\"}",
            "kube-scheduler-simulator.sigs.k8s.io/prescore-result":"{\"InterPodAffinity\":\"\",\"NodeAffinity\":\"success\",\"NodeResourcesBalancedAllocation\":\"success\",\"NodeResourcesFit\":\"success\",\"PodTopologySpread\":\"\",\"TaintToleration\":\"success\"}",
            "kube-scheduler-simulator.sigs.k8s.io/reserve-result":"{\"VolumeBinding\":\"success\"}",
            "kube-scheduler-simulator.sigs.k8s.io/score-result":"{\"node-jjfg5\":{\"ImageLocality\":\"0\",\"NodeAffinity\":\"0\",\"NodeResourcesBalancedAllocation\":\"52\",\"NodeResourcesFit\":\"47\",\"TaintToleration\":\"0\",\"VolumeBinding\":\"0\"},\"node-mtb5x\":{\"ImageLocality\":\"0\",\"NodeAffinity\":\"0\",\"NodeResourcesBalancedAllocation\":\"76\",\"NodeResourcesFit\":\"73\",\"TaintToleration\":\"0\",\"VolumeBinding\":\"0\"}}",
            "kube-scheduler-simulator.sigs.k8s.io/selected-node":"node-mtb5x"
        }
      ]      
    kube-scheduler-simulator.sigs.k8s.io/score-result: >-
      {
        "node-jjfg5":{
            "ImageLocality":"0",
            "NodeAffinity":"0",
            "NodeResourcesBalancedAllocation":"52",
            "NodeResourcesFit":"47",
            "TaintToleration":"0",
            "VolumeBinding":"0"
        },
        "node-mtb5x":{
            "ImageLocality":"0",
            "NodeAffinity":"0",
            "NodeResourcesBalancedAllocation":"76",
            "NodeResourcesFit":"73",
            "TaintToleration":"0",
            "VolumeBinding":"0"
        }
      }      
    kube-scheduler-simulator.sigs.k8s.io/selected-node: node-mtb5x

使用者還可以將自己的自定義外掛擴充套件器整合到可除錯排程器中,並可視化其結果。

這個可除錯的排程器也可以獨立執行,例如,在任何 Kubernetes 叢集或整合測試中。這對於希望測試其外掛或在真實叢集中以更好的可除錯性檢查其自定義排程器的自定義外掛開發者來說非常有用。

作為更好的開發叢集的模擬器

如前所述,透過有限的測試集,不可能預測真實叢集中的所有可能場景。通常,使用者會在將排程器部署到生產環境之前,在一個小型的開發叢集中進行測試,並希望不會出現問題。

模擬器的匯入功能提供了一個解決方案,它允許使用者在類似生產的環境中模擬部署新版本的排程器,而不會影響他們的即時工作負載。

透過在生產叢集和模擬器之間持續同步,使用者可以安全地使用其生產叢集處理的相同資源來測試新版本的排程器。一旦對其效能有信心,他們就可以進行生產部署,從而降低意外問題的風險。

有哪些使用場景?

  1. 叢集使用者:檢查排程約束(例如 PodAffinity、PodTopologySpread)是否按預期工作。
  2. 叢集管理員:評估更改排程器配置後集群的行為。
  3. 排程器外掛開發者:測試自定義排程器外掛或擴充套件器,在整合測試或開發叢集中使用可除錯的排程器,或使用同步功能在類似生產的環境中進行測試。

開始使用

模擬器只需要在機器上安裝 Docker;不需要 Kubernetes 叢集。

git clone git@github.com:kubernetes-sigs/kube-scheduler-simulator.git
cd kube-scheduler-simulator
make docker_up

然後你可以在 `https://:3000` 訪問模擬器的 Web UI。

請訪問 kube-scheduler-simulator 倉庫瞭解更多詳情!

參與進來

排程器模擬器由 Kubernetes SIG Scheduling 開發。歡迎你的反饋和貢獻!

kube-scheduler-simulator 倉庫中提出問題或 PR。在 #sig-scheduling Slack 頻道上加入討論。

致謝

模擬器一直由敬業的志願工程師維護,克服了許多挑戰才達到現在的形態。

非常感謝所有了不起的貢獻者