天天看點

K8s 之 kube-scheduler 源碼學習1. 本地部署2. 簡介3. 整體架構設計4.源碼解析

主要分為四個子產品

  1. 本地部署
  2. 簡介
  3. 整體架構
  4. 源碼解析

1. 本地部署

  1. windows環境需要先安裝一個Docker Desktop 下載下傳位址 : https://hub.docker.com/search?type=edition&offering=community
  2. 下載下傳的版本要和自己本地的k8s源碼版本一緻.
  3. Docker Desktop安裝好了, 從阿裡雲鏡像服務下載下傳 Kubernetes 所需要的鏡像, 在Windows上,使用 PowerShell 執行:
    .\load_images.ps1
               

    在Windows上:

    如果在Kubernetes部署的過程中出現問題,可以在 C:\ProgramData\DockerDesktop下的service.txt 檢視Docker日志, 在 C:\Users\yourUserName\AppData\Local\Docker下的log.txt 檢視Kubernetes日志

  4. 驗證 Kubernetes 叢集狀态
    kubectl cluster-info
    kubectl get nodes
               
  5. 部署 Kubernetes dashboard
    $ kubectl apply -f kubernetes-dashboard.yaml
               
  6. 配置控制台令牌token
    $TOKEN=((kubectl -n kube-system describe secret default | Select-String "token:") -split " +")[1]
    kubectl config set-credentials docker-for-desktop --token="${TOKEN}"
    echo $TOKEN
               
    這是我的token
    eyJhbGciOiJSUzI1NiIsImtpZCI6ImNIZFFSbGl2OFB0Vkt6b20yVFFtOUhCZ1BnT2dQUnFXZkUtZUhSU0JPLVEifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkZWZhdWx0LXRva2VuLXZkeGZ6Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImRlZmF1bHQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI3ZGE1ZDM0Zi1kODAzLTQwYTEtODBjNy02ZDk4OWM3ZDIyY2YiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06ZGVmYXVsdCJ9.DflbMdlV5_Wio0CQQjfmnpNJ527tVd2sLVDuuaTXY-djnexKu6_qCfwCovt8CxQdcg5_yPyCxZE_Rk5ep41j9jqAhztwmXTPEJ0cP85Uqm0hEGQCSQUM4MxDbPLw2a5aDwQ8EnNaUNZT4JvFiQBGGFESenUFGx21jpiXtwcwuY7VGlOSSpdB2ia9usx2lJSryMYel0VZ3gyfSnJds7S3LoSULXlsg5ullgNvpxLNxKp6vwywFu90E6o61_8u3FSTFv5eGJGOGrxaI95lZbw2tdll-1Ri_mGkt9rH1X8jS_9U5QFf1HSOQjyKWUQof4q9KibDfpAxinA7Onjgyp_Blw
               

    通過如下 URL 通路 Kubernetes dashboard

    http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/

  7. 開啟 API Server 通路代理
    kubectl proxy
               
  8. 先啟動本地的kube-scheduler, 支援各種指令行參數, 具體可以參考 :

    https://kubernetes.io/zh/docs/reference/command-line-tools-reference/kube-scheduler/

    --leader-elect=true
    --master=http://127.0.0.1:8001
    --v=2
               
    啟動完列印這行日志代表啟動成功.
  9. 先殺掉docker容器裡的kube-scheduler, 可能需要嘗試多次. 列印下面這行日志代表選主成功
    I0628 20:22:36.591608    3480 leaderelection.go:258] successfully    acquired lease kube-system/kube-scheduler
               
  10. 建立一個POD, 本地列印如下日志表示pod配置設定node成功
  11. 如果申請一個超額的POD, 排程失敗列印的日志 :

2. 簡介

排程器通過 kubernetes 的監測(Watch)機制來發現叢集中新建立且尚未被排程到 Node 上的 Pod。 排程器會将發現的每一個未排程的 Pod 排程到一個合适的 Node 上來運作。 排程器會依據下文的排程原則來做出排程選擇。

kube-scheduler 給一個 pod 做排程選擇包含兩個步驟:

  • 過濾
  • 打分

過濾階段會将所有滿足 Pod 排程需求的 Node 選出來。 例如,PodFitsResources 過濾函數會檢查候選 Node 的可用資源能否滿足 Pod 的資源請求。 在過濾之後,得出一個 Node 清單,裡面包含了所有可排程節點;通常情況下, 這個 Node 清單包含不止一個 Node。如果這個清單是空的,代表這個 Pod 不可排程。

在打分階段,排程器會為 Pod 從所有可排程節點中選取一個最合适的 Node。 根據目前啟用的打分規則,排程器會給每一個可排程節點進行打分。

最後,kube-scheduler 會将 Pod 排程到得分最高的 Node 上。 如果存在多個得分最高的 Node,kube-scheduler 會從中随機選取一個。

例子

K8s 之 kube-scheduler 源碼學習1. 本地部署2. 簡介3. 整體架構設計4.源碼解析

3. 整體架構設計

K8s 之 kube-scheduler 源碼學習1. 本地部署2. 簡介3. 整體架構設計4.源碼解析
scheduler工作流程:
  • scheduler維護待排程的

    podQueue

    并監聽APIServer。
  • 建立Pod時,我們首先通過APIServer将Pod中繼資料寫入etcd。
  • scheduler通過Informer監聽Pod狀态。添加新的Pod時,會将Pod添加到

    podQueue

  • 主程式不斷從

    podQueue

    中提取Pods并按照一定的算法将節點配置設定給Pods。
  • 節點上的kubelet也偵聽ApiServer。如果發現有新的Pod已排程到該節點,則将通過CRI調用進階容器運作時來運作容器。
  • 如果scheduler無法排程Pod,則如果啟用了優先級和搶占功能,則首先進行搶占嘗試,删除節點上具有低優先級的Pod,然後将要排程的Pod排程到該節點。如果未啟用搶占或搶占嘗試失敗,則相關資訊将記錄在日志中,并且Pod将添加到

    podQueue

    的末尾。
K8s 之 kube-scheduler 源碼學習1. 本地部署2. 簡介3. 整體架構設計4.源碼解析
  • Framework的排程流程是分為兩個階段scheduling cycle和binding cycle. scheduling cycle是同步執行的.
  • 同一個時間隻有一個scheduling cycle,是線程安全的。
  • binding cycle是異步執行的,同一個時間中可能會有多個binding cycle在運作,是線程不安全的。
設計要點
  • 無鎖化
  • 緩存加速排程
  • 可配置化
  • 可擴充

4.源碼解析

本地運作排程

kubernetes 是一個叢集管理平台, kubernetes需要統計整體平台的資源使用情況, 合理的将資源配置設定給容器使用, 并保證容器生命周期内有足夠的資源來保證其運作. 同時, 如果資源發放是獨占的, 對于空閑的容器來說占用這沒有使用的資源是非常浪費的, 比如CPU。k8s需要考慮如何在優先度和公平性的前提下提供資源的使用率。

limit/request介紹

  • 容器使用的最小資源需求, 作為容器排程時資源配置設定的判斷依賴。
  • 隻有目前節點上可配置設定的資源量 >= request 時才允許将容器排程到該節點。
  • request參數不限制容器的最大可使用資源

limit

  • 容器能使用資源的最大值
  • 設定為0表示對使用的資源不做限制, 可無限的使用

request 和 limit 關系

request能保證pod有足夠的資源來運作, 而limit則是防止某個pod無限制的使用資源, 導緻其他pod崩潰. 兩者的關系必須滿足:

0 <= request <= limit
           

如果limit=0表示不對資源進行限制, 這時可以小于request。

目前CPU支援設定request和limit,memory隻支援設定request, limit必須強制等于request, 這樣確定容器不會因為記憶體的使用量超過request但是沒有超過limit的情況下被意外kill掉。

通過指令行建立:
kubectl run nginx --image=nginx --port=80

通過yaml檔案建立
kubectl apply -f test-nginx.yaml

檢視pod
kubectl get pods

删除pod
kubectl delete pod nginx
           

檢視Pod狀态 :

Value Description
Pending 系統已經接受pod執行個體的建立,但其中所包含容器的一個或者多個image還沒有建立成功
Running Pod已經被排程到某個node上,pod包含的所有容器已經建立完成,至少有一個容器正常運作或者處于啟動與重新開機動過程。
Succeeded Pod中的所有容器正常終止,并且不會再次啟動。
Failed Pod中所有容器已終止運作,至少有一個容器非正常結束,比如退出碼非零,被系統強制殺死等。
Unknow 無法取得pod狀态,一般是網絡問題引起。
NAME READY STATUS RESTARTS AGE
nginx-deployment-77ccb5b6dd-dzvq5 0/1 Pending 3m38s

kube-scheduler 啟動流程

  • 指令行參數解析 : NewSchedulerCommand
  • 構造scheduler對象 : Setup(ctx, opts, registryOptions…) createFromConfig
  • 加載預設的内置排程算法 : GetDefaultConfig
  • 選舉 : leaderelection.go
  • 隻有leader才會運作排程 : scheduleOne

kube-scheduler 選舉機制

  • etcd是整個叢集所有狀态資訊的存儲, 而apiserver作為叢集入口,本身是無狀态的web伺服器,多個apiserver服務之間直接負載請求并不需要做選主。
  • Controller-Manager和Scheduler作為控制類型的元件,多個kube-scheduler啟動時先選舉leader,隻有 leader 的 schuduler 才會 調用run方法進入排程邏輯,而無需考慮它們之間的資料一緻和同步。
  • 基本原理其實就是利用通過調用apiServer 更新 endpoints 資源實作一個分布式鎖,搶(acqure)到鎖的節點成為leader,并且定期更新(renew)。其他程序也在不斷的嘗試進行搶占,搶占不到則繼續等待下次循環。當leader節點挂掉之後,租約到期,其他節點就成為新的leader。
leaderelection.go
           

擴充點

(1) Queue sort
// QueueSortPlugin is an interface that must be implemented by "QueueSort" plugins.
// These plugins are used to sort pods in the scheduling queue. Only one queue sort
// plugin may be enabled at a time.
type QueueSortPlugin interface {
    Plugin
    // Less are used to sort pods in the scheduling queue.
    Less(*PodInfo, *PodInfo) bool
}
           

Scheduler中的優先級隊列是通過heap實作的,我們可以在QueueSortPlugin中定義heap的比較函數來決定的排序結構。但是需要注意的是heap的比較函數在同一時刻隻有一個,是以QueueSort插件隻能Enable一個,如果使用者Enable了2個則排程器啟動時會報錯退出。下面是預設的比較函數,可供參考。

// Less is the function used by the activeQ heap algorithm to sort pods.
// It sorts pods based on their priority. When priorities are equal, it uses
// PodQueueInfo.timestamp.
func (pl *PrioritySort) Less(pInfo1, pInfo2 *framework.QueuedPodInfo) bool {
    p1 := pod.GetPodPriority(pInfo1.Pod)
    p2 := pod.GetPodPriority(pInfo2.Pod)
    return (p1 > p2) || (p1 == p2 && pInfo1.Timestamp.Before(pInfo2.Timestamp))
}
           
(2)PreFilter

PreFilter在scheduling cycle開始時就被調用,隻有當所有的PreFilter插件都傳回success時,才能進入下一個階段,否則Pod将會被拒絕掉,辨別此次排程流程失敗。PreFilter類似于排程流程啟動之前的預處理,可以對Pod的資訊進行加工。同時PreFilter也可以進行一些預置條件的檢查,去檢查一些叢集次元的條件,判斷否滿足pod的要求。

(3)Filter

Filter插件是scheduler v1版本中的Predicate的邏輯,用來過濾掉不滿足Pod排程要求的節點。為了提升效率,Filter的執行順序可以被配置,這樣使用者就可以将可以過濾掉大量節點的Filter政策放到前邊執行,進而減少後邊Filter政策執行的次數,例如我們可以把NodeSelector的Filter放到第一個,進而過濾掉大量的節點。Node節點執行Filter政策是并發執行的,是以在同一排程周期中多次調用過濾器。

Filter插件的功能如下:

  • NodePorts: 檢查Pod請求的端口在Node是否可用; (NodePorts.go)
  • NodeLabel: 根據配置的标簽過濾Node; (node_label.go)
  • NodeAffinity: 實作了Node選擇器和節點親和性
  • InterPodAffinity: 實作Pod之間的親和性和反親和性;
  • NodeName: 檢查Pod指定的Node名稱與目前Node是否比對;
  • NodeResourcesFit: 檢查Node是否擁有Pod請求的所有資源;
  • NodeUnscheduleable: 過濾Node.Spec.Unschedulable值為true的Node;
  • NodeVolumeLimits: 檢查Node是否滿足CSI卷限制;
  • PodTopologySpread: 實作Pod拓撲分布;
  • ServiceAffinity: 檢查屬于某個服務(Service)的Pod與配置的标簽所定義的Node集合是否适配,這個插件還支援将屬于某個服務的Pod分散到各個Node;
  • TaintToleration: 實作了污點和容忍度;
  • VolumeBinding: 檢查Node是否有請求的卷,是否可以綁定請求的卷;
  • VolumeRestrictions: 檢查挂載到Node上的卷是否滿足卷Provider的限制;
  • VolumeZone: 檢查請求的卷是否在任何區域都滿足;
(4) PostFilter

新的PostFilter的接口定義在1.19的版本才釋出,主要是用于處理當Pod在Filter階段失敗後的操作,例如搶占邏輯就是postFilter的一個實作.

PostFilterPlugin插件在過濾後調用,但僅在Pod沒有滿足需求的Node時調用。

搶占機制基本流程如下:
  1. 判斷是否有關閉搶占機制,如果關閉搶占機制則直接傳回。
  2. 擷取排程失敗pod的最新對象資料。
  3. 執行搶占算法

    Algorithm.Preempt

    ,傳回預排程節點和需要被剔除的pod清單。
  4. 将搶占算法傳回的node添加到pod的

    Status.NominatedNodeName

    中,并删除需要被剔除的pod。
  5. 當搶占算法傳回的node是nil的時候,清除pod的

    Status.NominatedNodeName

    資訊。

整個搶占流程的最終結果實際上是更新

Pod.Status.NominatedNodeName

屬性的資訊。如果搶占算法傳回的節點不為空,則将該node更新到

Pod.Status.NominatedNodeName

中,否則就将

Pod.Status.NominatedNodeName

設定為空。

(5) PreScore

PreScore在之前版本稱為PostFilter,現在修改為PreScore,主要用于在Score之前進行一些資訊生成。此處會擷取到通過Filter階段的節點清單,我們也可以在此處進行一些資訊預處理或者生成一些日志或者監控資訊。

(6) Score

Scoring擴充點是scheduler v1版本中Priority的邏輯,目的是為了基于Filter過濾後的剩餘節點,根據Scoring擴充點定義的政策挑選出最優的節點。

Scoring擴充點分為兩個階段:

  1. 打分:打分階段會對Filter後的節點進行打分,scheduler會調用所配置的打分政策
  2. 歸一化: 不同的分數有不同的權重, 進行權重計算得出一個最終得分

ScorePlugin插件功能如下:

  • NodeLabel: 根據配置的标簽過濾Node; (node_label.go)
  • NodeResourcesLeastAllocation: 排程Pod時,選擇資源配置設定較少的Node; (LeastAllocated.go)
  • NodeResourcesMostAllocation: 排程Pod時,選擇資源配置設定較多的Node; (MostAllocated.go)
  • NodeResourcesBalancedAllocation: 排程Pod時,選擇資源配置設定更為均勻的Node; (BalancedAllocation.go)
  • ImageLocality: 選擇已經存在Pod運作所需容器鏡像的Node,這樣可以省去下載下傳鏡像的過程,對于鏡像非常大的容器是一個非常有價值的特性,因為啟動時間可以節約幾秒甚至是幾十秒;
  • InterPodAffinity: 實作Pod之間的親和性和反親和性;
  • NodeAffinity: 實作了Node選擇器和節點親和性
  • NodePreferAvoidPods: 基于Node的注解 scheduler.alpha.kubernetes.io/preferAvoidPods打分;
  • RequestedToCapacityRatio: 根據已配置設定資源的配置函數選擇偏愛Node;
  • PodTopologySpread: 實作Pod拓撲分布;
  • SelectorSpread: 對于屬于Services、ReplicaSets和StatefulSets的Pod,偏好跨多節點部署;
  • ServiceAffinity: 檢查屬于某個服務(Service)的Pod與配置的标簽所定義的Node集合是否适配,這個插件還支援将屬于某個服務的Pod分散到各個Node;
  • TaintToleration: 實作了污點和容忍度;

其他擴充點可以見 : https://kubernetes.io/zh/docs/concepts/scheduling-eviction/scheduling-framework/

關鍵類

排程要做的事, 其實就是合理利用各種資源, 把Pod配置設定到合适的Node上

NodeInfo

type NodeInfo struct
           

NodeInfo是node的聚合資訊,主要包括:

  • node *v1.Node node的結構體
  • Pods []*PodInfo:目前node上pod的數量
  • UsedPorts HostPortInfo:已配置設定的端口
  • Requested *Resource : 已配置設定的所有資源的總和

Resource

type Resource struct {
   MilliCPU         int64
   Memory           int64
   EphemeralStorage int64
   AllowedPodNumber int
   ScalarResources map[v1.ResourceName]int64
}
           
排程的方法入口
schedule framework
// frameworkImpl is the component responsible for initializing and running scheduler
// plugins.
type frameworkImpl struct {
	registry             Registry
	snapshotSharedLister framework.SharedLister
	waitingPods          *waitingPodsMap
	scorePluginWeight    map[string]int
	queueSortPlugins     []framework.QueueSortPlugin
	preFilterPlugins     []framework.PreFilterPlugin
	filterPlugins        []framework.FilterPlugin
	postFilterPlugins    []framework.PostFilterPlugin
	preScorePlugins      []framework.PreScorePlugin
	scorePlugins         []framework.ScorePlugin
	reservePlugins       []framework.ReservePlugin
	preBindPlugins       []framework.PreBindPlugin
	bindPlugins          []framework.BindPlugin
	postBindPlugins      []framework.PostBindPlugin
	permitPlugins        []framework.PermitPlugin

	clientSet       clientset.Interface
	kubeConfig      *restclient.Config
	eventRecorder   events.EventRecorder
	informerFactory informers.SharedInformerFactory

	metricsRecorder *metricsRecorder
	profileName     string

	extenders []framework.Extender
	framework.PodNominator

	parallelizer parallelize.Parallelizer

	// Indicates that RunFilterPlugins should accumulate all failed statuses and not return
	// after the first failure.
	runAllFilters bool
}
           

generic_scheduler.go

過濾

打分

func prioritizeNodes(
   ctx context.Context,
   extenders []framework.Extender,
   fwk framework.Framework,
   state *framework.CycleState,
   pod *v1.Pod,
   nodes []*v1.Node,  // 對所有的node進行打分
) (framework.NodeScoreList, error)