K8s 通過 Scheduler Extender 實現自定義調度邏輯

1. 為什么需要自定義調度邏輯

什么是所謂的調度?

  • 所謂調度就是指給 Pod 對象的 spec.nodeName 賦值

  • 待調度對象則是所有 spec.nodeName 為空的 Pod

  • 調度過程則是從集群現有的 Node 中為當前 Pod 選擇一個最合適的

實際上 Pod 上還有一個平時比較少關注的屬性:?spec.schedulerName,用于指定該 Pod 要交給哪個調度器進行調度。

那么問題來了,平時用的時候也沒給 spec.schedulerName 賦值過,怎么也能調度呢?

因為默認的 kube-scheduler 可以兼容 spec.schedulerName 為空或者為?default?的 Pod。

為什么需要自定義調度邏輯

自定義調度邏輯可以解決特定應用場景和需求,使集群資源使用更高效,適應特殊的調度策略。

比如:

  • 不同的工作負載可能有特定的資源需求,比如 GPU 或 NPU,需要確保 Pod 只能調度到滿足這些資源條件的節點上。
  • 某些集群可能需要均衡資源消耗,避免將多個負載集中到某些節點上。
  • 為了降低延遲,可能需要將Pod調度到特定地理位置的節點上。自定義調度器可以根據節點的地理位置標簽進行調度決策。
  • 某些應用需要與其他應用隔離運行,以避免資源爭搶。通過自定義調度器,可以將特定類型的任務或工作負載隔離到專用的節點上。
  • ...

總之就是業務上有各種特殊的調度需求,因此我們需要通過實現自定義調度器來滿足這些需求。

通過實現自定義調度器,可以根據具體的業務需求和集群環境,實現更靈活、更高效的資源管理和調度策略。

2.如何增加自定義調度邏輯

自定義調度器的幾種方法

要增加自定義調度邏輯也并不復雜,K8s 整個調度流程都已經插件化了,我們并不需要重頭開始實現一個調度器,而只需要實現一個調度插件,通過在調度過程中各個階段加入我們的自定義邏輯,來控制最終的調度結果。

總體來說可以分為以下幾個方向:

1)新增一個調度器

  • 直接修改 kube-scheduler 源碼,編譯替換
  • 使用 調度框架(Scheduling Framework),我們可以使用?scheduler-plugins?作為模板,簡化自定義調度器的開發流程。
    • Kubernetes v1.15 版本中引入了可插拔架構的調度框架,使得定制調度器這個任務變得更加的容易。調庫框架向現有的調度器中添加了一組插件化的 API,該 API 在保持調度程序“核心”簡單且易于維護的同時,使得大部分的調度功能以插件的形式存在。

2)擴展原有調度器

  • 通過 Scheduler Extender 可以實現對已有調度器進行擴展。單獨創建一個 HTTP 服務并實現對應接口,后續就可以將該服務作為外置調度器使用。通過配置?KubeSchedulerConfiguration原 Scheduler 會以 HTTP 調用方式和外置調度器交互,實現在不改動原有調度器基礎上增加自定義邏輯。

3)其他非主流方案

  • 自定義 Webhook 直接修改未調度 Pod 的 spec.nodeName 字段,有點離譜但理論可行哈哈

二者都有自己的優缺點

優點缺點
新增調度器性能好:由于不依賴外部插件或 HTTP 調用,調度流程的延遲相對較低,適合對性能要求較高的場景復用性高:可復用現有的調度插件,如?scheduler-plugins,大大降低了開發難度,提升了開發效率。多個調度器可能會沖突:比如多個調度器同時調度了一個 Pod 到節點上,先啟動的 Pod 把資源占用了,后續的 Pod 無法啟動。
擴展調度器實現簡單:無需重新編譯調度器,通過配置?KubeSchedulerConfiguration?創建一個外部 HTTP 服務來實現自定義邏輯。零侵入性:不需要修改或重構調度器的核心代碼,可快速上線新的調度邏輯。靈活性較高:原有調度器和自定義邏輯相對獨立,方便維護與測試。性能差:調度請求需要經過 HTTP 調用,增加了調用延遲,對性能可能有影響。

一般在我們要改動的邏輯不多時,直接使用 Scheduler Extender 是比較簡單的。

Scheduler 配置

調度器的配置有一個單獨的對象:KubeSchedulerConfiguration,不過并沒有以 CRD 形式存在,而是存放到 Configmap 中的。

以下是一個完整 KubeSchedulerConfiguration 的 yaml:

apiVersion: v1
data:config.yaml: |apiVersion: kubescheduler.config.k8s.io/v1beta2kind: KubeSchedulerConfigurationleaderElection:leaderElect: falseprofiles:- schedulerName: hami-schedulerextenders:- urlPrefix: "https://127.0.0.1:443"filterVerb: filterbindVerb: bindnodeCacheCapable: trueweight: 1httpTimeout: 30senableHTTPS: truetlsConfig:insecure: truemanagedResources:- name: nvidia.com/gpuignoredByScheduler: true- name: nvidia.com/gpumemignoredByScheduler: true- name: nvidia.com/gpucoresignoredByScheduler: true

一般分為基礎配置和 extenders 配置兩部分。

基礎配置

基礎配置一般就是配置調度器的名稱,Demo 如下:

apiVersion: v1
kind: ConfigMap
metadata:name: my-scheduler-confignamespace: kube-system
data:my-scheduler-config.yaml: |apiVersion: kubescheduler.config.k8s.io/v1beta2kind: KubeSchedulerConfigurationprofiles:- schedulerName: my-schedulerleaderElection:leaderElect: false   

通過 schedulerName 來指定該調度器的名稱,比如這里就是?my-scheduler?。

創建 Pod 時除非手動指定 spec.schedulerName 為 my-scheduler,否則不會由該調度器進行調度。

擴展調度器:extenders 配置

extenders 部分通過額外指定一個 http 服務器來實現外置的自定義的調度邏輯。

一個簡單的 Scheduler Extender 配置如下:

apiVersion: v1
kind: ConfigMap
metadata:name: i-scheduler-extendernamespace: kube-system
data:i-scheduler-extender.yaml: |apiVersion: kubescheduler.config.k8s.io/v1kind: KubeSchedulerConfigurationprofiles:- schedulerName: i-scheduler-extenderleaderElection:leaderElect: falseextenders:- urlPrefix: "http://localhost:8080"enableHTTPS: falsefilterVerb: "filter"prioritizeVerb: "prioritize"bindVerb: "bind"weight: 1nodeCacheCapable: true

核心部分為

    extenders:- urlPrefix: "http://localhost:8080"enableHTTPS: falsefilterVerb: "filter"prioritizeVerb: "prioritize"bindVerb: "bind"weight: 1nodeCacheCapable: true

幾個核心參數含義如下:

  • urlPrefix: http://127.0.0.1:8080?用于指定外置的調度服務訪問地址

  • filterVerb: "filter":表示 Filter 接口在外置服務中的訪問地址為 filter,即完整地址為?http://127.0.0.1:8080/filter

  • prioritizeVerb: "prioritize":同上,Prioritize(Score) 接口的地址為 prioritize

  • bindVerb: "bind":同上,Bind 接口的地址為 bind

這樣該調度器在執行 Filter 接口邏輯時,除了內置的調度器插件之外,還會通過 HTTP 方式調用外置的調度器。

這樣我們只需要創建一個 HTTP 服務,實現對應接口即可實現自定義的調度邏輯,而不需要重頭實現一個調度器。

ManagedResources 配置

在之前的配置中是所有 Pod 都會走 Extender 的調度邏輯,實際上 Extender 還有一個 ManagedResources 配置,用于限制只有申請使用指定資源的 Pod 才會走 Extender 調度邏輯,這樣可以減少無意義的調度。

一個帶 managedResources 的 KubeSchedulerConfiguration 內容如下

apiVersion: v1
data:config.yaml: |apiVersion: kubescheduler.config.k8s.io/v1kind: KubeSchedulerConfigurationleaderElection:leaderElect: falseprofiles:- schedulerName: hami-schedulerextenders:- urlPrefix: "https://127.0.0.1:443"filterVerb: filterbindVerb: bindnodeCacheCapable: falseenableHTTPS: falsemanagedResources:- name: nvidia.com/gpuignoredByScheduler: true- name: nvidia.com/gpumemignoredByScheduler: true- name: nvidia.com/gpucoresignoredByScheduler: true- name: nvidia.com/gpumem-percentageignoredByScheduler: true- name: nvidia.com/priorityignoredByScheduler: true

在原來的基礎上增加了 managedResources 部分的配置

      managedResources:- name: nvidia.com/gpuignoredByScheduler: true- name: nvidia.com/gpumemignoredByScheduler: true- name: nvidia.com/gpucoresignoredByScheduler: true- name: nvidia.com/gpumem-percentageignoredByScheduler: true

只有 Pod 申請這些特殊資源時才走 Extender 調度邏輯,否則使用原生的 Scheduler 調度即可。

ignoredByScheduler: true?的作用是告訴調度器忽略指定資源,避免將它們作為調度決策的依據。也就是說,雖然這些資源(如 GPU 或其他加速硬件)會被 Pod 請求,但調度器不會在選擇節點時基于這些資源的可用性做出決定。

ps:因為這些資源可能是虛擬的,并不會真正的出現在 Node 上,因此調度時需要忽略掉,否則就沒有任何節點滿足條件了,但是這些虛擬資源則是我們的自定義調度邏輯需要考慮的事情。

Scheduler 中的判斷邏輯如下,和之前說的一樣,只有當 Pod 申請了這些指定的資源時,Scheduler 才會調用 Extender。

// IsInterested returns true if at least one extended resource requested by
// this pod is managed by this extender.
func (h *HTTPExtender) IsInterested(pod *v1.Pod) bool {if h.managedResources.Len() == 0 {return true}if h.hasManagedResources(pod.Spec.Containers) {return true}if h.hasManagedResources(pod.Spec.InitContainers) {return true}return false
}func (h *HTTPExtender) hasManagedResources(containers []v1.Container) bool {for i := range containers {container := &containers[i]for resourceName := range container.Resources.Requests {if h.managedResources.Has(string(resourceName)) {return true}}for resourceName := range container.Resources.Limits {if h.managedResources.Has(string(resourceName)) {return true}}}return false
}

3. Scheduler Extender 規范

Scheduler Extender 通過 HTTP 請求的方式,將調度框架階段中的調度決策委托給外部的調度器,然后將調度結果返回給調度框架。

我們只需要實現一個 HTTP 服務,然后通過配置文件將其注冊到調度器中,就可以實現自定義調度器。

通過 Scheduler Extender 擴展原有調度器一般分為以下兩步:

  • 1)創建一個 HTTP 服務,實現對應接口
  • 2)修改調度器配置 KubeSchedulerConfiguration,增加 extenders 相關配置

外置調度器可以影響到三個階段:

  • Filter:調度框架將調用 Filter 函數,過濾掉不適合被調度的節點。

  • Priority:調度框架將調用 Priority 函數,為每個節點計算一個優先級,優先級越高,節點越適合被調度。

  • Bind:調度框架將調用 Bind 函數,將 Pod 綁定到一個節點上。

Filter、Priority、Bind 三個階段分別對應三個 HTTP 接口,三個接口都接收 POST 請求,各自的請求、響應結構定義在這里:#kubernetes/kube-scheduler/extender/v1/types.go

在這個 HTTP 服務中,我們可以實現上述階段中的任意一個或多個階段的接口,來定制我們的調度需求。

每個接口的請求和響應值請求如下。

Filter

請求參數

// ExtenderArgs represents the arguments needed by the extender to filter/prioritize
// nodes for a pod.
type ExtenderArgs struct {// Pod being scheduledPod *v1.Pod// List of candidate nodes where the pod can be scheduled; to be populated// only if Extender.NodeCacheCapable == falseNodes *v1.NodeList// List of candidate node names where the pod can be scheduled; to be// populated only if Extender.NodeCacheCapable == trueNodeNames *[]string
}

響應結果

// ExtenderFilterResult represents the results of a filter call to an extender
type ExtenderFilterResult struct {// Filtered set of nodes where the pod can be scheduled; to be populated// only if Extender.NodeCacheCapable == falseNodes *v1.NodeList// Filtered set of nodes where the pod can be scheduled; to be populated// only if Extender.NodeCacheCapable == trueNodeNames *[]string// Filtered out nodes where the pod can't be scheduled and the failure messagesFailedNodes FailedNodesMap// Filtered out nodes where the pod can't be scheduled and preemption would// not change anything. The value is the failure message same as FailedNodes.// Nodes specified here takes precedence over FailedNodes.FailedAndUnresolvableNodes FailedNodesMap// Error message indicating failureError string
}

Prioritize

請求參數

// ExtenderArgs represents the arguments needed by the extender to filter/prioritize
// nodes for a pod.
type ExtenderArgs struct {// Pod being scheduledPod *v1.Pod// List of candidate nodes where the pod can be scheduled; to be populated// only if Extender.NodeCacheCapable == falseNodes *v1.NodeList// List of candidate node names where the pod can be scheduled; to be// populated only if Extender.NodeCacheCapable == trueNodeNames *[]string
}

響應結果

// HostPriority represents the priority of scheduling to a particular host, higher priority is better.
type HostPriority struct {// Name of the hostHost string// Score associated with the hostScore int64
}// HostPriorityList declares a []HostPriority type.
type HostPriorityList []HostPriority

Bind

請求參數

// ExtenderBindingArgs represents the arguments to an extender for binding a pod to a node.
type ExtenderBindingArgs struct {// PodName is the name of the pod being boundPodName string// PodNamespace is the namespace of the pod being boundPodNamespace string// PodUID is the UID of the pod being boundPodUID types.UID// Node selected by the schedulerNode string
}

響應結果

// ExtenderBindingResult represents the result of binding of a pod to a node from an extender.
type ExtenderBindingResult struct {// Error message indicating failureError string
}

4. Demo

這部分則是手把手實現一個簡單的擴展調度器,完整代碼見:lixd/i-scheduler-extender

功能如下:

  • 1)過濾階段:僅調度到帶有 Label?priority.lixueduan.com?的節點上
  • 2)打分階段:直接將節點上 Label?priority.lixueduan.com?的值作為得分
    • 比如某節點攜帶 Label?priority.lixueduan.com=50?則打分階段該節點則是 50 分

代碼實現

main.go

比較簡單,直接通過內置的 net.http 包啟動一個 http 服務即可。

var h *server.Handlerfunc init() {h = server.NewHandler(extender.NewExtender())
}func main() {http.HandleFunc("/filter", h.Filter)http.HandleFunc("/filter_onlyone", h.FilterOnlyOne) // Filter 接口的一個額外實現http.HandleFunc("/priority", h.Prioritize)http.HandleFunc("/bind", h.Bind)http.ListenAndServe(":8080", nil)
}

由于 Priority 階段返回的得分 kube-Scheduler 會自行匯總后重新計算,因此擴展調度器的 priority 接口并不能安全控制最終調度結果,因此額外實現了一個 filter_onlyone 接口。

Filter 實現

filter 接口用于過濾掉不滿足條件的接口,這里直接過濾掉沒有指定 label 的節點即可。

// Filter 過濾掉不滿足條件的節點
func (ex *Extender) Filter(args extenderv1.ExtenderArgs) *extenderv1.ExtenderFilterResult {nodes := make([]v1.Node, 0)nodeNames := make([]string, 0)for _, node := range args.Nodes.Items {_, ok := node.Labels[Label]if !ok { // 排除掉不帶指定標簽的節點continue}nodes = append(nodes, node)nodeNames = append(nodeNames, node.Name)}// 沒有滿足條件的節點就報錯if len(nodes) == 0 {return &extenderv1.ExtenderFilterResult{Error: fmt.Errorf("all node do not have label %s", Label).Error()}}args.Nodes.Items = nodesreturn &extenderv1.ExtenderFilterResult{Nodes:     args.Nodes, // 當 NodeCacheCapable 設置為 false 時會使用這個值NodeNames: &nodeNames, // 當 NodeCacheCapable 設置為 true 時會使用這個值}
}

具體返回 Nodes 還是 NodeNames 決定了后續 Scheduler 部署的 NodeCacheCapable 參數的配置,二者對于即可。

Prioritize 實現

Prioritize 對應的就是 Score 階段,給 Filter 之后留下來的節點打分,選擇一個最適合的節點進行調度。

這里的邏輯就是之前說的:解析 Node 上的 label ,直接將其 value 作為節點分數。

// Prioritize 給 Pod 打分
func (ex *Extender) Prioritize(args extenderv1.ExtenderArgs) *extenderv1.HostPriorityList {var result extenderv1.HostPriorityListfor _, node := range args.Nodes.Items {// 獲取 Node 上的 Label 作為分數priorityStr, ok := node.Labels[Label]if !ok {klog.Errorf("node %q does not have label %s", node.Name, Label)continue}priority, err := strconv.Atoi(priorityStr)if err != nil {klog.Errorf("node %q has priority %s are invalid", node.Name, priorityStr)continue}result = append(result, extenderv1.HostPriority{Host:  node.Name,Score: int64(priority),})}return &result
}
Bind 實現

就是通過 clientset 創建一個 Binding 對象,指定 Pod 和 Node 信息。

// Bind 將 Pod 綁定到指定節點
func (ex *Extender) Bind(args extenderv1.ExtenderBindingArgs) *extenderv1.ExtenderBindingResult {log.Printf("bind pod: %s/%s to node:%s", args.PodNamespace, args.PodName, args.Node)// 創建綁定關系binding := &corev1.Binding{ObjectMeta: metav1.ObjectMeta{Name: args.PodName, Namespace: args.PodNamespace, UID: args.PodUID},Target:     corev1.ObjectReference{Kind: "Node", APIVersion: "v1", Name: args.Node},}result := new(extenderv1.ExtenderBindingResult)err := ex.ClientSet.CoreV1().Pods(args.PodNamespace).Bind(context.Background(), binding, metav1.CreateOptions{})if err != nil {klog.ErrorS(err, "Failed to bind pod", "pod", args.PodName, "namespace", args.PodNamespace, "podUID", args.PodUID, "node", args.Node)result.Error = err.Error()}return result
}
Filter OnlyOne 實現

Extender 僅作為一個額外的調度插件接入, 接口返回得分最終 Scheduler 會將其和其他插件打分合并之后再選出最終節點,因此 extender 中無法通過 prioritie 接口的分數完全控制調度結果。

不過也不是沒有辦法!

想要完全控制調度結果,我們可以在 Filter 接口中特殊處理。

Filter 接口先過濾掉不滿足條件的節點,然后對剩余節點進行打分,最終只返回得分最高的那個節點,這樣就一定會調度到該接口,從而實現完全控制調度結果。

具體實現如下:

// FilterOnlyOne 過濾掉不滿足條件的節點,并將其余節點打分排序,最終只返回得分最高的節點以實現完全控制調度結果
func (ex *Extender) FilterOnlyOne(args extenderv1.ExtenderArgs) *extenderv1.ExtenderFilterResult {// 過濾掉不滿足條件的節點nodeScores := &NodeScoreList{NodeList: make([]*NodeScore, 0)}for _, node := range args.Nodes.Items {_, ok := node.Labels[Label]if !ok { // 排除掉不帶指定標簽的節點continue}// 對剩余節點打分score := ComputeScore(node)nodeScores.NodeList = append(nodeScores.NodeList, &NodeScore{Node: node, Score: score})}// 沒有滿足條件的節點就報錯if len(nodeScores.NodeList) == 0 {return &extenderv1.ExtenderFilterResult{Error: fmt.Errorf("all node do not have label %s", Label).Error()}}// 排序sort.Sort(nodeScores)// 然后取最后一個,即得分最高的節點,這樣由于 Filter 只返回了一個節點,因此最終肯定會調度到該節點上m := (*nodeScores).NodeList[len((*nodeScores).NodeList)-1]// 組裝一下返回結果args.Nodes.Items = []v1.Node{m.Node}return &extenderv1.ExtenderFilterResult{Nodes:     args.Nodes,NodeNames: &[]string{m.Node.Name},}
}

部署

構建鏡像

Dockerfile 如下:

# syntax=docker/dockerfile:1# Build the manager binary
FROM golang:1.22.5 as builder
ARG TARGETOS
ARG TARGETARCHENV GOPROXY=https://goproxy.cnWORKDIR /workspace
# Copy the go source
COPY . /workspace
# cache deps before building and copying source so that we don't need to re-download as much
# and so that source changes don't invalidate our downloaded layer
RUN go mod download# Build
# the GOARCH has not a default value to allow the binary be built according to the host where the command
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o extender main.go# Use distroless as minimal base image to package the manager binary
# Refer to https://github.com/GoogleContainerTools/distroless for more details
#FROM gcr.io/distroless/static:nonroot
FROM busybox:1.36
WORKDIR /
COPY --from=builder /workspace/extender .
USER 65532:65532ENTRYPOINT ["/extender"]
部署到集群

部分也是分為兩步:

  • 1)部署 Extender:由于 Extender 只是一個 HTTP 服務器,只需要使用 Deployment 將其部署到集群即可。

  • 2)配置 Extender:修改調度器的 KubeSchedulerConfiguration 配置,在其中 extender 部分指定 url 以及對應的 path,進行接入。

這里為了不影響到默認的 kube-scheduler,我們使用 kube-scheduler 鏡像單獨啟動一個 Scheduler,然后為該調度器配置上 Extender,同時為了降低網絡請求的影響,直接將 kube-scheduler 和 Extender 直接運行在同一個 Pod 里,通過 localhost 進行訪問。

完整 yaml 如下:

apiVersion: v1
kind: ServiceAccount
metadata:name: i-scheduler-extendernamespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:name: i-scheduler-extender
subjects:- kind: ServiceAccountname: i-scheduler-extendernamespace: kube-system
roleRef:kind: ClusterRolename: cluster-adminapiGroup: rbac.authorization.k8s.io
---
apiVersion: v1
kind: ConfigMap
metadata:name: i-scheduler-extendernamespace: kube-system
data:i-scheduler-extender.yaml: |apiVersion: kubescheduler.config.k8s.io/v1kind: KubeSchedulerConfigurationprofiles:- schedulerName: i-scheduler-extenderleaderElection:leaderElect: falseextenders:- urlPrefix: "http://localhost:8080"enableHTTPS: falsefilterVerb: "filter"prioritizeVerb: "prioritize"bindVerb: "bind"weight: 1nodeCacheCapable: true
---
apiVersion: apps/v1
kind: Deployment
metadata:labels:component: i-scheduler-extendertier: control-planename: i-scheduler-extendernamespace: kube-system
spec:replicas: 1selector:matchLabels:component: i-scheduler-extendertier: control-planetemplate:metadata:labels:component: i-scheduler-extendertier: control-planespec:serviceAccountName: i-scheduler-extendercontainers:- name: kube-schedulerimage: registry.k8s.io/kube-scheduler:v1.29.0command:- kube-scheduler- --config=/etc/kubernetes/i-scheduler-extender.yamllivenessProbe:httpGet:path: /healthzport: 10259scheme: HTTPSinitialDelaySeconds: 15readinessProbe:httpGet:path: /healthzport: 10259scheme: HTTPSresources:requests:cpu: '0.1'volumeMounts:- name: config-volumemountPath: /etc/kubernetes- name: i-scheduler-extenderimage: lixd96/i-scheduler-extender:v1ports:- containerPort: 8080volumes:- name: config-volumeconfigMap:name: i-scheduler-extender
kubectl apply -f deploy

確認服務正常運行

[root@scheduler-1 ~]# kubectl -n kube-system get po|grep i-scheduler-extender
i-scheduler-extender-f9cff954c-dkwz2   2/2     Running   0          1m

接下來就可以開始測試了。

測試

創建 Pod

創建一個 Deployment 并指定使用上一步中部署的 Scheduler,然后測試會調度到哪個節點上。

apiVersion: apps/v1
kind: Deployment
metadata:name: test
spec:replicas: 1selector:matchLabels:app: testtemplate:metadata:labels:app: testspec:schedulerName: i-scheduler-extendercontainers:- image: busybox:1.36name: nginxcommand: ["sleep"]         args: ["99999"]

創建之后 Pod 會一直處于 Pending 狀態

[root@scheduler-1 lixd]# k get po
NAME                    READY   STATUS    RESTARTS   AGE
test-58794bff9f-ljxbs   0/1     Pending   0          17s

查看具體情況

[root@scheduler-1]# k describe po test-58794bff9f-ljxbs
Events:Type     Reason            Age                From                  Message----     ------            ----               ----                  -------Warning  FailedScheduling  99s                i-scheduler-extender  all node do not have label priority.lixueduan.comWarning  FailedScheduling  95s (x2 over 97s)  i-scheduler-extender  all node do not have label priority.lixueduan.com

可以看到,是因為 Node 上沒有我們定義的 Label,因此都不滿足條件,最終 Pod 就一直 Pending 了。

添加 Label

由于我們實現的 Filter 邏輯是需要 Node 上有priority.lixueduan.com?才會用來調度,否則直接會忽略。

理論上,只要給任意一個 Node 打上 Label 就可以了。

[root@scheduler-1 install]# k get node
NAME          STATUS   ROLES           AGE     VERSION
scheduler-1   Ready    control-plane   4h34m   v1.27.4
scheduler-2   Ready    <none>          4h33m   v1.27.4
[root@scheduler-1 install]# k label node scheduler-1 priority.lixueduan.com=10
node/scheduler-1 labeled

再次查看 Pod 狀態

[root@scheduler-1 lixd]# k get po -owide
NAME                    READY   STATUS    RESTARTS   AGE    IP               NODE          NOMINATED NODE   READINESS GATES
test-58794bff9f-ljxbs   1/1     Running   0          104s   172.25.123.201   scheduler-1   <none>           <none>

已經被調度到 node1 上了,查看詳細日志

[root@scheduler-1 install]# k describe po test-7f7bb8f449-w6wvv
Events:Type     Reason            Age                  From                  Message----     ------            ----                 ----                  -------Warning  FailedScheduling  116s                 i-scheduler-extender  0/2 nodes are available: preemption: 0/2 nodes are available: 2 No preemption victims found for incoming pod.Warning  FailedScheduling  112s (x2 over 115s)  i-scheduler-extender  0/2 nodes are available: preemption: 0/2 nodes are available: 2 No preemption victims found for incoming pod.Normal   Scheduled         26s                  i-scheduler-extender  Successfully assigned default/test-58794bff9f-ljxbs to scheduler-1

可以看到,確實是 i-scheduler-extender 這個調度器在處理,調度到了 node1.

多節點排序

我們實現的 Score 是根據 Node 上的?priority.lixueduan.com?對應的 Value 作為得分的,因此調度器會優先考慮調度到 Value 比較大的一個節點。

因為 Score 階段也有很多調度插件,Scheduler 會匯總所有得分,最終才會選出結果,因此這里的分數也是僅供參考,不能完全控制調度結果。

給 node2 也打上 label,value 設置為 20

[root@scheduler-1 install]# k get node
NAME          STATUS   ROLES           AGE     VERSION
scheduler-1   Ready    control-plane   4h34m   v1.27.4
scheduler-2   Ready    <none>          4h33m   v1.27.4
[root@scheduler-1 install]# k label node scheduler-2 priority.lixueduan.com=20
node/scheduler-2 labeled

然后更新 Deployment ,觸發創建新 Pod ,測試調度邏輯。

[root@scheduler-1 lixd]# kubectl rollout restart deploy test
deployment.apps/test restarted

因為 Node2 上的 priority 為 20,node1 上為 10,那么理論上會調度到 node2 上。

[root@scheduler-1 lixd]# k get po -owide
NAME                    READY   STATUS    RESTARTS   AGE   IP             NODE          NOMINATED NODE   READINESS GATES
test-84fdbbd8c7-47mtr   1/1     Running   0          38s   172.25.0.162   scheduler-1   <none>           <none>

結果還是調度到了 node1,為什么呢?

這就是前面提到的:因為 Extender 僅作為一個額外的調度插件接入,Prioritize 接口返回得分最終 Scheduler 會將其和其他插件打分合并之后再選出最終節點,因此 Extender 想要完全控制調度結果,只能在 Filter 接口中實現,過濾掉不滿足條件的節點,并對剩余節點進行打分,最終 Filter 接口只返回得分最高的那個節點,從而實現完全控制調度結果。

ps:即之前的 Filter OnlyOne 實現,可以在 KubeSchedulerConfiguration 中配置不同的 path 來調用不同接口進行測試。

修改 KubeSchedulerConfiguration 配置,

apiVersion: v1
kind: ConfigMap
metadata:name: i-scheduler-extendernamespace: kube-system
data:i-scheduler-extender.yaml: |apiVersion: kubescheduler.config.k8s.io/v1kind: KubeSchedulerConfigurationprofiles:- schedulerName: i-scheduler-extenderleaderElection:leaderElect: falseextenders:- urlPrefix: "http://localhost:8080"enableHTTPS: falsefilterVerb: "filter_onlyone"prioritizeVerb: "prioritize"bindVerb: "bind"weight: 1nodeCacheCapable: true

修改點:

filterVerb: "filter_onlyone"

Path 從 filter 修改成了 filter_onlyone,這里的 path 和前面注冊服務時的路徑對應:

    http.HandleFunc("/filter", h.Filter)http.HandleFunc("/filter_onlyone", h.FilterOnlyOne) // Filter 接口的一個額外實現

修改后重啟一下 Scheduler

kubectl -n kube-system rollout restart deploy i-scheduler-extender

再次更新 Deployment 觸發調度

[root@scheduler-1 install]# k rollout restart deploy test
deployment.apps/test restarted

這樣應該是調度到 node2 了,確認一下

[root@scheduler-1 lixd]# k get po -owide
NAME                    READY   STATUS    RESTARTS   AGE   IP             NODE          NOMINATED NODE   READINESS GATES
test-849f549d5b-pbrml   1/1     Running       0          12s   172.25.0.166     scheduler-2   <none>           <none>

現在我們更新 Node1 的 label,改成 30

k label node scheduler-1 priority.lixueduan.com=30 --overwrite

再次更新 Deployment 觸發調度

[root@scheduler-1 install]# k rollout restart deploy test
deployment.apps/test restarted

這樣應該是調度到 node1 了,確認一下

[root@scheduler-1 lixd]# k get po -owide
NAME                    READY   STATUS        RESTARTS   AGE   IP               NODE          NOMINATED NODE   READINESS GATES
test-69d9ccb877-9fb6t   1/1     Running       0          5s    172.25.123.203   scheduler-1   <none>           <none>

說明修改 Filter 方法實現之后,確實可以直接控制調度結果。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/91843.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/91843.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/91843.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

7.19 換根dp | vpp |滑窗

lcr147.最小棧通過兩個棧 維護實現class MinStack { public:stack<int> A, B;MinStack() {}void push(int x) {A.push(x);if(B.empty() || B.top() > x)B.push(x);}void pop() {if(A.top() B.top())B.pop();A.pop();}int top() {return A.top();}int getMin() {retur…

以太坊的心臟與大腦:詳解執行客戶端(EL)與共識客戶端(CL)

好的&#xff0c;各位技術同道&#xff0c;歡迎再次光臨我的博客。在上一篇文章中&#xff0c;我們聊了如何搭建一個以太坊測試節點&#xff0c;并提到了節點需要同時運行“執行客戶端”和“共識客戶端”。很多朋友對此表示了濃厚興趣&#xff0c;想深入了解這兩者究竟是什么&a…

Debian-10,用glibc二進制預編譯包,安裝Mysql-5.7.44 筆記250716

Debian-10,用glibc二進制預編譯包,安裝Mysql-5.7.44 筆記250716 &#x1f4e6; 一步腳本 #!/bin/bash### 安裝依賴 apt install -y libaio1 libnuma1 libncurses5### 下載MySQL-5.7.44 的 glib二進制包: mysql-5.7.44-linux-glibc2.12-x86_64.tar.gz ,(如果不存在) mkdir…

用邏輯回歸(Logistic Regression)處理鳶尾花(iris)數據集

# 導入必要的庫 import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from…

華大北斗TAU1201-1216A00高精度雙頻GNSS定位模塊 自動駕駛專用

在萬物互聯的時代&#xff0c;您還在為定位不準、信號丟失而煩惱嗎&#xff1f;TAU1201-1216A00華大北斗高精度定位模塊TAU1201是一款高性能的雙頻GNSS定位模塊&#xff0c;搭載了華大北斗的CYNOSURE III GNSS SoC 芯片&#xff0c;該模塊支持新一代北斗三號信號體制&#xff0…

堅持繼續布局32位MCU,進一步完善產品陣容,96Mhz主頻CW32L012新品發布!

在全球MCU市場競爭加劇、國產替代加速的背景下&#xff0c;嵌入式設備對核心控制芯片的性能、功耗、可靠性及性價比提出了前所未有的嚴苛需求。為適應市場競爭&#xff0c;2025年7月16日&#xff0c;武漢芯源半導體正式推出基于CW32L01x系列低功耗微控制器家族的全新成員&#…

用線性代數推導碼分多址(CDMA)

什么是碼分多址 碼分多址&#xff1a;CDMA允許多個用戶同時、在同一頻率上傳輸數據。它通過給每個用戶分配唯一的、相互正交的二進制序列來實現區分。用戶的數據比特被這個碼片序列擴展成一個高速率的信號&#xff0c;然后在接收端通過相同的碼片序列進行相關運算來回復原數據 …

mac 配置svn

1.查看brew的版本&#xff1a;brew install subversion2.安裝brew命令&#xff1a;bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"3.把路徑添加到path環境變量&#xff1a;echo export PATH"/opt/homebrew/b…

使用 .NET Core 的原始 WebSocket

在 Web 開發中&#xff0c;后端存在一些值得注意的通信協議&#xff0c;用于將更改通知給已連接的客戶端。所有這些協議都用于處理同一件事。但鮮為人知的協議很少&#xff0c;鮮為人知的協議也很少。今天&#xff0c;將討論 WebSocket&#xff0c;它在開發中使用最少&#xff…

編程實現Word自動排版:從理論到實踐的全面指南

在現代辦公環境中&#xff0c;文檔排版是一項常見但耗時的工作。特別是對于需要處理大量文檔的專業人士來說&#xff0c;手動排版不僅費時費力&#xff0c;還容易出現不一致的問題。本文將深入探討如何通過編程方式實現Word文檔的自動排版&#xff0c;從理論基礎到實際應用&…

力扣經典算法篇-25-刪除鏈表的倒數第 N 個結點(計算鏈表的長度,利用棧先進后出特性,雙指針法)

1、題干 給你一個鏈表&#xff0c;刪除鏈表的倒數第 n 個結點&#xff0c;并且返回鏈表的頭結點。 示例 1&#xff1a;輸入&#xff1a;head [1,2,3,4,5], n 2 輸出&#xff1a;[1,2,3,5] 示例 2&#xff1a; 輸入&#xff1a;head [1], n 1 輸出&#xff1a;[] 示例 3&…

VIT速覽

當我們取到一張圖片&#xff0c;我們會把它劃分為一個個patch&#xff0c;如上圖把一張圖片劃分為了9個patch&#xff0c;然后通過一個embedding把他們轉換成一個個token&#xff0c;每個patch對應一個token&#xff0c;然后在輸入到transformer encoder之前還要經過一個class …

【服務器與部署 14】消息隊列部署:RabbitMQ、Kafka生產環境搭建指南

【服務器與部署 14】消息隊列部署&#xff1a;RabbitMQ、Kafka生產環境搭建指南 關鍵詞&#xff1a;消息隊列、RabbitMQ集群、Kafka集群、消息中間件、異步通信、微服務架構、高可用部署、消息持久化、生產環境配置、分布式系統 摘要&#xff1a;本文從實際業務場景出發&#x…

LeetCode中等題--167.兩數之和II-輸入有序數組

1. 題目 給你一個下標從 1 開始的整數數組 numbers &#xff0c;該數組已按 非遞減順序排列 &#xff0c;請你從數組中找出滿足相加之和等于目標數 target 的兩個數。如果設這兩個數分別是 numbers[index1] 和 numbers[index2] &#xff0c;則 1 < index1 < index2 <…

【C# in .NET】19. 探秘抽象類:具體實現與抽象契約的橋梁

探秘抽象類:具體實現與抽象契約的橋梁 在.NET類型系統中,抽象類是連接具體實現與抽象契約的關鍵橋梁,它既具備普通類的狀態承載能力,又擁有類似接口的行為約束特性。本文將從 IL 代碼結構、CLR 類型加載機制、方法調度邏輯三個維度,全面揭示抽象類的底層工作原理,通過與…

Apache RocketMQ + “太乙” = 開源貢獻新體驗

Apache RocketMQ 是 Apache 基金會托管的頂級項目&#xff0c;自 2012 年誕生于阿里巴巴&#xff0c;服務于淘寶等核心交易系統&#xff0c;歷經多次雙十一萬億級數據洪峰穩定性驗證&#xff0c;至今已有十余年發展歷程。RocketMQ 致力于構建低延遲、高并發、高可用、高可靠的分…

永磁同步電機控制算法--弱磁控制(變交軸CCR-VQV)

一、原理介紹CCR-FQV弱磁控制不能較好的利用逆變器的直流側電壓&#xff0c;造成電機的調速范圍窄、效率低和帶載能力差。為了解決CCR-FQV弱磁控制存在的缺陷&#xff0c;可以在電機運行過程中根據工況的不同實時的改變交軸電壓給定uq?的值&#xff0c;實施 CCR-VQV弱磁控制。…

達夢數據守護集群搭建(1主1實時備庫1同步備庫1異步備庫)

目錄 1 環境信息 1.1 目錄信息 1.2 其他環境信息 2 環境準備 2.1 新建dmdba用戶 2.2 關閉防火墻 2.3 關閉Selinux 2.4 關閉numa和透明大頁 2.5 修改文件打開最大數 2.6 修改磁盤調度 2.7 修改cpufreq模式 2.8 信號量修改 2.9 修改sysctl.conf 2.10 修改 /etc/sy…

電感與電容充、放電極性判斷和電感選型

目錄 一、電感 二、電容 三、電感選型 一、電感 充電&#xff1a;左右-為例 放電&#xff1a;極性相反&#xff0c;左-右 二、電容 充電&#xff1a;左右-為例 放電&#xff1a;左右-&#xff08;與充電極性一致&#xff09; 三、電感選型 主要考慮額定電流和飽和電流。…

新建模范式Mamba——“Selectivity is All You Need?”

目錄 一、快速走進和理解Mamba建模架構 &#xff08;一&#xff09;從Transformer的統治地位談起 &#xff08;二&#xff09;另一條道路&#xff1a;結構化狀態空間模型&#xff08;SSM&#xff09; &#xff08;三&#xff09;Mamba 的核心創新&#xff1a;Selective SSM…