k8s調(diào)度原理以及自定義調(diào)度器方式
kube-scheduler 是 kubernetes 的核心組件之一,主要負(fù)責(zé)整個集群資源的調(diào)度功能,根據(jù)特定的調(diào)度算法和策略,將 Pod 調(diào)度到最優(yōu)的工作節(jié)點上面去,從而更加合理、更加充分的利用集群的資源,這也是我們選擇使用 kubernetes 一個非常重要的理由。如果一門新的技術(shù)不能幫助企業(yè)節(jié)約成本、提供效率,我相信是很難推進(jìn)的。
1. 調(diào)度流程
默認(rèn)情況下,kube-scheduler 提供的默認(rèn)調(diào)度器能夠滿足我們絕大多數(shù)的要求,我們前面和大家接觸的示例也基本上用的默認(rèn)的策略,都可以保證我們的 Pod 可以被分配到資源充足的節(jié)點上運行。但是在實際的線上項目中,可能我們自己會比 kubernetes 更加了解我們自己的應(yīng)用,比如我們希望一個 Pod 只能運行在特定的幾個節(jié)點上,或者這幾個節(jié)點只能用來運行特定類型的應(yīng)用,這就需要我們的調(diào)度器能夠可控。
kube-scheduler 的主要作用就是根據(jù)特定的調(diào)度算法和調(diào)度策略將 Pod 調(diào)度到合適的 Node 節(jié)點上去,是一個獨立的二進(jìn)制程序,啟動之后會一直監(jiān)聽 API Server,獲取到 PodSpec.NodeName 為空的 Pod,對每個 Pod 都會創(chuàng)建一個 binding。

這個過程在我們看來好像比較簡單,但在實際的生產(chǎn)環(huán)境中,需要考慮的問題就有很多了:
如何保證全部的節(jié)點調(diào)度的公平性?要知道并不是所有節(jié)點資源配置一定都是一樣的
如何保證每個節(jié)點都能被分配資源?
集群資源如何能夠被高效利用?
集群資源如何才能被最大化使用?
如何保證 Pod 調(diào)度的性能和效率?
用戶是否可以根據(jù)自己的實際需求定制自己的調(diào)度策略?
考慮到實際環(huán)境中的各種復(fù)雜情況,kubernetes 的調(diào)度器采用插件化的形式實現(xiàn),可以方便用戶進(jìn)行定制或者二次開發(fā),我們可以自定義一個調(diào)度器并以插件形式和 kubernetes 進(jìn)行集成。
kubernetes 調(diào)度器的源碼位于 kubernetes/pkg/scheduler 中,大體的代碼目錄結(jié)構(gòu)如下所示:(不同的版本目錄結(jié)構(gòu)可能不太一樣)
kubernetes/pkg/scheduler -- scheduler.go //調(diào)度相關(guān)的具體實現(xiàn) |-- algorithm | |-- predicates //節(jié)點篩選策略 | |-- priorities //節(jié)點打分策略 |-- algorithmprovider | |-- defaults //定義默認(rèn)的調(diào)度器
2. 自定義調(diào)度器說明
一般來說,我們有 4 種擴展 Kubernetes 調(diào)度器的方法。
一種方法就是直接 clone 官方的 kube-scheduler 源代碼,在合適的位置直接修改代碼,然后重新編譯運行修改后的程序,當(dāng)然這種方法是最不建議使用的,也不實用,因為需要花費大量額外的精力來和上游的調(diào)度程序更改保持一致。
第二種方法就是和默認(rèn)的調(diào)度程序一起運行獨立的調(diào)度程序,默認(rèn)的調(diào)度器和我們自定義的調(diào)度器可以通過 Pod 的
spec.schedulerName來覆蓋各自的 Pod,默認(rèn)是使用 default 默認(rèn)的調(diào)度器,但是多個調(diào)度程序共存的情況下也比較麻煩,比如當(dāng)多個調(diào)度器將 Pod 調(diào)度到同一個節(jié)點的時候,可能會遇到一些問題,因為很有可能兩個調(diào)度器都同時將兩個 Pod 調(diào)度到同一個節(jié)點上去,但是很有可能其中一個 Pod 運行后其實資源就消耗完了,并且維護(hù)一個高質(zhì)量的自定義調(diào)度程序也不是很容易的,因為我們需要全面了解默認(rèn)的調(diào)度程序,整體 Kubernetes 的架構(gòu)知識以及各種 Kubernetes API 對象的各種關(guān)系或限制。第三種方法是調(diào)度器擴展程序,這個方案目前是一個可行的方案,可以和上游調(diào)度程序兼容,所謂的調(diào)度器擴展程序其實就是一個可配置的 Webhook 而已,里面包含
過濾器和優(yōu)先級兩個端點,分別對應(yīng)調(diào)度周期中的兩個主要階段(過濾和打分)。第四種方法是通過調(diào)度框架(Scheduling Framework),Kubernetes v1.15 版本中引入了可插拔架構(gòu)的調(diào)度框架,使得定制調(diào)度器這個任務(wù)變得更加的容易。調(diào)庫框架向現(xiàn)有的調(diào)度器中添加了一組插件化的 API,該 API 在保持調(diào)度程序“核心”簡單且易于維護(hù)的同時,使得大部分的調(diào)度功能以插件的形式存在,而且在我們現(xiàn)在的 v1.16 版本中上面的
調(diào)度器擴展程序也已經(jīng)被廢棄了,所以以后調(diào)度框架才是自定義調(diào)度器的核心方式。
這里我們可以簡單介紹下后面兩種方式的實現(xiàn)。
3. 調(diào)度器擴展程序
在進(jìn)入調(diào)度器擴展程序之前,我們再來了解下 Kubernetes 調(diào)度程序是如何工作的:
- 默認(rèn)調(diào)度器根據(jù)指定的參數(shù)啟動(我們使用 kubeadm 搭建的集群,啟動配置文件位于
/etc/kubernetes/manifests/kube-schdueler.yaml) - watch apiserver,將
spec.nodeName為空的 Pod 放入調(diào)度器內(nèi)部的調(diào)度隊列中 - 從調(diào)度隊列中 Pop 出一個 Pod,開始一個標(biāo)準(zhǔn)的調(diào)度周期
- 從 Pod 屬性中檢索“硬性要求”(比如 CPU/內(nèi)存請求值,nodeSelector/nodeAffinity),然后過濾階段發(fā)生,在該階段計算出滿足要求的節(jié)點候選列表
- 從 Pod 屬性中檢索“軟需求”,并應(yīng)用一些默認(rèn)的“軟策略”(比如 Pod 傾向于在節(jié)點上更加聚攏或分散),最后,它為每個候選節(jié)點給出一個分?jǐn)?shù),并挑選出得分最高的最終獲勝者
- 和 apiserver 通信(發(fā)送綁定調(diào)用),然后設(shè)置 Pod 的
spec.nodeName屬性以表示將該 Pod 調(diào)度到的節(jié)點。
我們可以通過查看官方文檔,可以通過 --config 參數(shù)指定調(diào)度器將使用哪些參數(shù),該配置文件應(yīng)該包含一個 KubeSchedulerConfiguration 對象,如下所示格式:(/etc/kubernetes/scheduler-extender.yaml)
# 通過"--config" 傳遞文件內(nèi)容
apiVersion: kubescheduler.config.k8s.io/v1alpha1
kind: KubeSchedulerConfiguration
clientConnection:
kubeconfig: "/etc/kubernetes/scheduler.conf"
algorithmSource:
policy:
file:
path: "/etc/kubernetes/scheduler-extender-policy.yaml" # 指定自定義調(diào)度策略文件我們在這里應(yīng)該輸入的關(guān)鍵參數(shù)是 algorithmSource.policy,這個策略文件可以是本地文件也可以是 ConfigMap 資源對象,這取決于調(diào)度程序的部署方式,比如我們這里默認(rèn)的調(diào)度器是靜態(tài) Pod 方式啟動的,所以我們可以用本地文件的形式來配置。
該策略文件 /etc/kubernetes/scheduler-extender-policy.yaml 應(yīng)該遵循 kubernetes/pkg/scheduler/apis/config/legacy_types.go#L28 的要求,在我們這里的 v1.16.2 版本中已經(jīng)支持 JSON 和 YAML 兩種格式的策略文件,下面是我們定義的一個簡單的示例,可以查看 Extender 描述了解策略文件的定義規(guī)范:
apiVersion: v1
kind: Policy
extenders:
- urlPrefix: "http://127.0.0.1:8888/"
filterVerb: "filter"
prioritizeVerb: "prioritize"
weight: 1
enableHttps: false我們這里的 Policy 策略文件是通過定義 extenders 來擴展調(diào)度器的,有時候我們不需要去編寫代碼,可以直接在該配置文件中通過指定 predicates 和 priorities 來進(jìn)行自定義,如果沒有指定則會使用默認(rèn)的 DefaultProvier:
{
"kind": "Policy",
"apiVersion": "v1",
"predicates": [
{
"name": "MatchNodeSelector"
},
{
"name": "PodFitsResources"
},
{
"name": "PodFitsHostPorts"
},
{
"name": "HostName"
}
],
"priorities": [
{
"name": "EqualPriority",
"weight": 2
},
{
"name": "ImageLocalityPriority",
"weight": 4
},
{
"name": "LeastRequestedPriority",
"weight": 2
},
{
"name": "BalancedResourceAllocation",
"weight": 2
}
],
"extenders": [
{
"urlPrefix": "/prefix",
"filterVerb": "filter",
"prioritizeVerb": "prioritize",
"weight": 1,
"bindVerb": "bind",
"enableHttps": false
}
]
}
改策略文件定義了一個 HTTP 的擴展程序服務(wù),該服務(wù)運行在 127.0.0.1:8888 下面,并且已經(jīng)將該策略注冊到了默認(rèn)的調(diào)度器中,這樣在過濾和打分階段結(jié)束后,可以將結(jié)果分別傳遞給該擴展程序的端點 <urlPrefix>/<filterVerb> 和 <urlPrefix>/<prioritizeVerb>,在擴展程序中,我們可以進(jìn)一步過濾并確定優(yōu)先級,以適應(yīng)我們的特定業(yè)務(wù)需求。
示例
我們直接用 golang 來實現(xiàn)一個簡單的調(diào)度器擴展程序,當(dāng)然你可以使用其他任何編程語言,如下所示:
func main() {
router := httprouter.New()
router.GET("/", Index)
router.POST("/filter", Filter)
router.POST("/prioritize", Prioritize)
log.Fatal(http.ListenAndServe(":8888", router))
}
然后接下來我們需要實現(xiàn) /filter 和 /prioritize 兩個端點的處理程序。
其中 Filter 這個擴展函數(shù)接收一個輸入類型為 schedulerapi.ExtenderArgs 的參數(shù),然后返回一個類型為 *schedulerapi.ExtenderFilterResult 的值。在函數(shù)中,我們可以進(jìn)一步過濾輸入的節(jié)點:
// filter 根據(jù)擴展程序定義的預(yù)選規(guī)則來過濾節(jié)點
func filter(args schedulerapi.ExtenderArgs) *schedulerapi.ExtenderFilterResult {
var filteredNodes []v1.Node
failedNodes := make(schedulerapi.FailedNodesMap)
pod := args.Pod
for _, node := range args.Nodes.Items {
fits, failReasons, _ := podFitsOnNode(pod, node)
if fits {
filteredNodes = append(filteredNodes, node)
} else {
failedNodes[node.Name] = strings.Join(failReasons, ",")
}
}
result := schedulerapi.ExtenderFilterResult{
Nodes: &v1.NodeList{
Items: filteredNodes,
},
FailedNodes: failedNodes,
Error: "",
}
return &result
}
在過濾函數(shù)中,我們循環(huán)每個節(jié)點然后用我們自己實現(xiàn)的業(yè)務(wù)邏輯來判斷是否應(yīng)該批準(zhǔn)該節(jié)點,這里我們實現(xiàn)比較簡單,在 podFitsOnNode() 函數(shù)中我們只是簡單的檢查隨機數(shù)是否為偶數(shù)來判斷即可,如果是的話我們就認(rèn)為這是一個幸運的節(jié)點,否則拒絕批準(zhǔn)該節(jié)點。
var predicatesSorted = []string{LuckyPred}
var predicatesFuncs = map[string]FitPredicate{
LuckyPred: LuckyPredicate,
}
type FitPredicate func(pod *v1.Pod, node v1.Node) (bool, []string, error)
func podFitsOnNode(pod *v1.Pod, node v1.Node) (bool, []string, error) {
fits := true
var failReasons []string
for _, predicateKey := range predicatesSorted {
fit, failures, err := predicatesFuncs[predicateKey](pod, node)
if err != nil {
return false, nil, err
}
fits = fits && fit
failReasons = append(failReasons, failures...)
}
return fits, failReasons, nil
}
func LuckyPredicate(pod *v1.Pod, node v1.Node) (bool, []string, error) {
lucky := rand.Intn(2) == 0
if lucky {
log.Printf("pod %v/%v is lucky to fit on node %v\n", pod.Name, pod.Namespace, node.Name)
return true, nil, nil
}
log.Printf("pod %v/%v is unlucky to fit on node %v\n", pod.Name, pod.Namespace, node.Name)
return false, []string{LuckyPredFailMsg}, nil
}
同樣的打分功能用同樣的方式來實現(xiàn),我們在每個節(jié)點上隨機給出一個分?jǐn)?shù):
// it's webhooked to pkg/scheduler/core/generic_scheduler.go#PrioritizeNodes()
// 這個函數(shù)輸出的分?jǐn)?shù)會被添加會默認(rèn)的調(diào)度器
func prioritize(args schedulerapi.ExtenderArgs) *schedulerapi.HostPriorityList {
pod := args.Pod
nodes := args.Nodes.Items
hostPriorityList := make(schedulerapi.HostPriorityList, len(nodes))
for i, node := range nodes {
score := rand.Intn(schedulerapi.MaxPriority + 1) // 在最大優(yōu)先級內(nèi)隨機取一個值
log.Printf(luckyPrioMsg, pod.Name, pod.Namespace, score)
hostPriorityList[i] = schedulerapi.HostPriority{
Host: node.Name,
Score: score,
}
}
return &hostPriorityList
}
然后我們可以使用下面的命令來編譯打包我們的應(yīng)用:
$ GOOS=linux GOARCH=amd64 go build -o app
構(gòu)建完成后,將應(yīng)用 app 拷貝到 kube-scheduler 所在的節(jié)點直接運行即可?,F(xiàn)在我們就可以將上面的策略文件配置到 kube-scheduler 組件中去了,我們這里集群是 kubeadm 搭建的,所以直接修改文件 /etc/kubernetes/manifests/kube-schduler.yaml 文件即可,內(nèi)容如下所示:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
component: kube-scheduler
tier: control-plane
name: kube-scheduler
namespace: kube-system
spec:
containers:
- command:
- kube-scheduler
- --authentication-kubeconfig=/etc/kubernetes/scheduler.conf
- --authorization-kubeconfig=/etc/kubernetes/scheduler.conf
- --bind-address=127.0.0.1
- --kubeconfig=/etc/kubernetes/scheduler.conf
- --leader-elect=true
- --config=/etc/kubernetes/scheduler-extender.yaml
- --v=9
image: gcr.azk8s.cn/google_containers/kube-scheduler:v1.16.2
imagePullPolicy: IfNotPresent
livenessProbe:
failureThreshold: 8
httpGet:
host: 127.0.0.1
path: /healthz
port: 10251
scheme: HTTP
initialDelaySeconds: 15
timeoutSeconds: 15
name: kube-scheduler
resources:
requests:
cpu: 100m
volumeMounts:
- mountPath: /etc/kubernetes/scheduler.conf
name: kubeconfig
readOnly: true
- mountPath: /etc/kubernetes/scheduler-extender.yaml
name: extender
readOnly: true
- mountPath: /etc/kubernetes/scheduler-extender-policy.yaml
name: extender-policy
readOnly: true
hostNetwork: true
priorityClassName: system-cluster-critical
volumes:
- hostPath:
path: /etc/kubernetes/scheduler.conf
type: FileOrCreate
name: kubeconfig
- hostPath:
path: /etc/kubernetes/scheduler-extender.yaml
type: FileOrCreate
name: extender
- hostPath:
path: /etc/kubernetes/scheduler-extender-policy.yaml
type: FileOrCreate
name: extender-policy
status: {}當(dāng)然我們這個地方是直接在系統(tǒng)默認(rèn)的 kube-scheduler 上面配置的,我們也可以復(fù)制一個調(diào)度器的 YAML 文件然后更改下 schedulerName 來部署,這樣就不會影響默認(rèn)的調(diào)度器了,然后在需要使用這個測試的調(diào)度器的 Pod 上面指定 spec.schedulerName 即可。對于多調(diào)度器的使用可以查看官方文檔。
kube-scheduler 重新配置后可以查看日志來驗證是否重啟成功,需要注意的是一定需要將 /etc/kubernetes/scheduler-extender.yaml 和 /etc/kubernetes/scheduler-extender-policy.yaml 兩個文件掛載到 Pod 中去:
$ kubectl logs -f kube-scheduler-ydzs-master -n kube-system I0102 15:17:38.824657 1 serving.go:319] Generated self-signed cert in-memory I0102 15:17:39.472276 1 server.go:143] Version: v1.16.2 I0102 15:17:39.472674 1 defaults.go:91] TaintNodesByCondition is enabled, PodToleratesNodeTaints predicate is mandatory W0102 15:17:39.479704 1 authorization.go:47] Authorization is disabled W0102 15:17:39.479733 1 authentication.go:79] Authentication is disabled I0102 15:17:39.479777 1 deprecated_insecure_serving.go:51] Serving healthz insecurely on [::]:10251 I0102 15:17:39.480559 1 secure_serving.go:123] Serving securely on 127.0.0.1:10259 I0102 15:17:39.682180 1 leaderelection.go:241] attempting to acquire leader lease kube-system/kube-scheduler... I0102 15:17:56.500505 1 leaderelection.go:251] successfully acquired lease kube-system/kube-scheduler
到這里我們就創(chuàng)建并配置了一個非常簡單的調(diào)度擴展程序,現(xiàn)在我們來運行一個 Deployment 查看其工作原理,我們準(zhǔn)備一個包含 20 個副本的部署 Yaml:(test-scheduler.yaml)
apiVersion: apps/v1
kind: Deployment
metadata:
name: pause
spec:
replicas: 20
selector:
matchLabels:
app: pause
template:
metadata:
labels:
app: pause
spec:
containers:
- name: pause
image: gcr.azk8s.cn/google_containers/pause:3.1直接創(chuàng)建上面的資源對象:
$ kuectl apply -f test-scheduler.yaml deployment.apps/pause created
這個時候我們?nèi)ゲ榭聪挛覀兙帉懙恼{(diào)度器擴展程序日志:
$ ./app ...... 2020/01/03 12:27:29 pod pause-58584fbc95-bwn7t/default is unlucky to fit on node ydzs-node1 2020/01/03 12:27:29 pod pause-58584fbc95-bwn7t/default is lucky to get score 7 2020/01/03 12:27:29 pod pause-58584fbc95-bwn7t/default is lucky to get score 9 2020/01/03 12:27:29 pod pause-58584fbc95-86w92/default is unlucky to fit on node ydzs-node3 2020/01/03 12:27:29 pod pause-58584fbc95-86w92/default is unlucky to fit on node ydzs-node4 2020/01/03 12:27:29 pod pause-58584fbc95-86w92/default is lucky to fit on node ydzs-node1 2020/01/03 12:27:29 pod pause-58584fbc95-86w92/default is lucky to fit on node ydzs-node2 2020/01/03 12:27:29 pod pause-58584fbc95-86w92/default is lucky to get score 4 2020/01/03 12:27:29 pod pause-58584fbc95-86w92/default is lucky to get score 8 ......
我們可以看到 Pod 調(diào)度的過程,另外默認(rèn)調(diào)度程序會定期重試失敗的 Pod,因此它們將一次又一次地重新傳遞到我們的調(diào)度擴展程序上,我們的邏輯是檢查隨機數(shù)是否為偶數(shù),所以最終所有 Pod 都將處于運行狀態(tài)。
調(diào)度器擴展程序可能是在一些情況下可以滿足我們的需求,但是他仍然有一些限制和缺點:
- 通信成本:數(shù)據(jù)在默認(rèn)調(diào)度程序和調(diào)度器擴展程序之間以
http(s)傳輸,在執(zhí)行序列化和反序列化的時候有一定成本 - 有限的擴展點:擴展程序只能在某些階段的末尾參與,例如
“ Filter”和“ Prioritize”,它們不能在任何階段的開始或中間被調(diào)用 - 減法優(yōu)于加法:與默認(rèn)調(diào)度程序傳遞的節(jié)點候選列表相比,我們可能有一些需求需要添加新的候選節(jié)點列表,但這是比較冒險的操作,因為不能保證新節(jié)點可以通過其他要求,所以,調(diào)度器擴展程序最好執(zhí)行
“減法”(進(jìn)一步過濾),而不是“加法”(添加節(jié)點) - 緩存共享:上面只是一個簡單的測試示例,但在真實的項目中,我們是需要通過查看整個集群的狀態(tài)來做出調(diào)度決策的,默認(rèn)調(diào)度程序可以很好地調(diào)度決策,但是無法共享其緩存,這意味著我們必須構(gòu)建和維護(hù)自己的緩存
由于這些局限性,Kubernetes 調(diào)度小組就提出了上面第四種方法來進(jìn)行更好的擴展,也就是調(diào)度框架(Scheduler Framework),它基本上可以解決我們遇到的所有難題,現(xiàn)在也已經(jīng)成官方推薦的擴展方式,所以這將是以后擴展調(diào)度器的最主流的方式。
4. 調(diào)度框架
調(diào)度框架定義了一組擴展點,用戶可以實現(xiàn)擴展點定義的接口來定義自己的調(diào)度邏輯(我們稱之為擴展),并將擴展注冊到擴展點上,調(diào)度框架在執(zhí)行調(diào)度工作流時,遇到對應(yīng)的擴展點時,將調(diào)用用戶注冊的擴展。調(diào)度框架在預(yù)留擴展點時,都是有特定的目的,有些擴展點上的擴展可以改變調(diào)度程序的決策方法,有些擴展點上的擴展只是發(fā)送一個通知。
我們知道每當(dāng)調(diào)度一個 Pod 時,都會按照兩個過程來執(zhí)行:調(diào)度過程和綁定過程。
調(diào)度過程為 Pod 選擇一個合適的節(jié)點,綁定過程則將調(diào)度過程的決策應(yīng)用到集群中(也就是在被選定的節(jié)點上運行 Pod),將調(diào)度過程和綁定過程合在一起,稱之為調(diào)度上下文(scheduling context)。需要注意的是調(diào)度過程是同步運行的(同一時間點只為一個 Pod 進(jìn)行調(diào)度),綁定過程可異步運行(同一時間點可并發(fā)為多個 Pod 執(zhí)行綁定)。
調(diào)度過程和綁定過程遇到如下情況時會中途退出:
- 調(diào)度程序認(rèn)為當(dāng)前沒有該 Pod 的可選節(jié)點
- 內(nèi)部錯誤
這個時候,該 Pod 將被放回到 待調(diào)度隊列,并等待下次重試。
擴展點(Extension Points)
下圖展示了調(diào)度框架中的調(diào)度上下文及其中的擴展點,一個擴展可以注冊多個擴展點,以便可以執(zhí)行更復(fù)雜的有狀態(tài)的任務(wù)。

QueueSort 擴展用于對 Pod 的待調(diào)度隊列進(jìn)行排序,以決定先調(diào)度哪個 Pod,QueueSort 擴展本質(zhì)上只需要實現(xiàn)一個方法 Less(Pod1, Pod2) 用于比較兩個 Pod 誰更優(yōu)先獲得調(diào)度即可,同一時間點只能有一個 QueueSort 插件生效。
Pre-filter 擴展用于對 Pod 的信息進(jìn)行預(yù)處理,或者檢查一些集群或 Pod 必須滿足的前提條件,如果 pre-filter 返回了 error,則調(diào)度過程終止。
Filter 擴展用于排除那些不能運行該 Pod 的節(jié)點,對于每一個節(jié)點,調(diào)度器將按順序執(zhí)行 filter 擴展;如果任何一個 filter 將節(jié)點標(biāo)記為不可選,則余下的 filter 擴展將不會被執(zhí)行。調(diào)度器可以同時對多個節(jié)點執(zhí)行 filter 擴展。
Post-filter 是一個通知類型的擴展點,調(diào)用該擴展的參數(shù)是 filter 階段結(jié)束后被篩選為可選節(jié)點的節(jié)點列表,可以在擴展中使用這些信息更新內(nèi)部狀態(tài),或者產(chǎn)生日志或 metrics 信息。
Scoring 擴展用于為所有可選節(jié)點進(jìn)行打分,調(diào)度器將針對每一個節(jié)點調(diào)用 Soring 擴展,評分結(jié)果是一個范圍內(nèi)的整數(shù)。在 normalize scoring 階段,調(diào)度器將會把每個 scoring 擴展對具體某個節(jié)點的評分結(jié)果和該擴展的權(quán)重合并起來,作為最終評分結(jié)果。
Normalize scoring 擴展在調(diào)度器對節(jié)點進(jìn)行最終排序之前修改每個節(jié)點的評分結(jié)果,注冊到該擴展點的擴展在被調(diào)用時,將獲得同一個插件中的 scoring 擴展的評分結(jié)果作為參數(shù),調(diào)度框架每執(zhí)行一次調(diào)度,都將調(diào)用所有插件中的一個 normalize scoring 擴展一次。
Reserve 是一個通知性質(zhì)的擴展點,有狀態(tài)的插件可以使用該擴展點來獲得節(jié)點上為 Pod 預(yù)留的資源,該事件發(fā)生在調(diào)度器將 Pod 綁定到節(jié)點之前,目的是避免調(diào)度器在等待 Pod 與節(jié)點綁定的過程中調(diào)度新的 Pod 到節(jié)點上時,發(fā)生實際使用資源超出可用資源的情況。(因為綁定 Pod 到節(jié)點上是異步發(fā)生的)。這是調(diào)度過程的最后一個步驟,Pod 進(jìn)入 reserved 狀態(tài)以后,要么在綁定失敗時觸發(fā) Unreserve 擴展,要么在綁定成功時,由 Post-bind 擴展結(jié)束綁定過程。
Permit 擴展用于阻止或者延遲 Pod 與節(jié)點的綁定。Permit 擴展可以做下面三件事中的一項:
- approve(批準(zhǔn)):當(dāng)所有的 permit 擴展都 approve 了 Pod 與節(jié)點的綁定,調(diào)度器將繼續(xù)執(zhí)行綁定過程
- deny(拒絕):如果任何一個 permit 擴展 deny 了 Pod 與節(jié)點的綁定,Pod 將被放回到待調(diào)度隊列,此時將觸發(fā)
Unreserve擴展 - wait(等待):如果一個 permit 擴展返回了 wait,則 Pod 將保持在 permit 階段,直到被其他擴展 approve,如果超時事件發(fā)生,wait 狀態(tài)變成 deny,Pod 將被放回到待調(diào)度隊列,此時將觸發(fā) Unreserve 擴展
Pre-bind 擴展用于在 Pod 綁定之前執(zhí)行某些邏輯。例如,pre-bind 擴展可以將一個基于網(wǎng)絡(luò)的數(shù)據(jù)卷掛載到節(jié)點上,以便 Pod 可以使用。如果任何一個 pre-bind 擴展返回錯誤,Pod 將被放回到待調(diào)度隊列,此時將觸發(fā) Unreserve 擴展。
Bind 擴展用于將 Pod 綁定到節(jié)點上:
- 只有所有的 pre-bind 擴展都成功執(zhí)行了,bind 擴展才會執(zhí)行
- 調(diào)度框架按照 bind 擴展注冊的順序逐個調(diào)用 bind 擴展
- 具體某個 bind 擴展可以選擇處理或者不處理該 Pod
- 如果某個 bind 擴展處理了該 Pod 與節(jié)點的綁定,余下的 bind 擴展將被忽略
Post-bind 是一個通知性質(zhì)的擴展:
- Post-bind 擴展在 Pod 成功綁定到節(jié)點上之后被動調(diào)用
- Post-bind 擴展是綁定過程的最后一個步驟,可以用來執(zhí)行資源清理的動作
Unreserve 是一個通知性質(zhì)的擴展,如果為 Pod 預(yù)留了資源,Pod 又在被綁定過程中被拒絕綁定,則 unreserve 擴展將被調(diào)用。Unreserve 擴展應(yīng)該釋放已經(jīng)為 Pod 預(yù)留的節(jié)點上的計算資源。在一個插件中,reserve 擴展和 unreserve 擴展應(yīng)該成對出現(xiàn)。
如果我們要實現(xiàn)自己的插件,必須向調(diào)度框架注冊插件并完成配置,另外還必須實現(xiàn)擴展點接口,對應(yīng)的擴展點接口我們可以在源碼 pkg/scheduler/framework/v1alpha1/interface.go 文件中找到,如下所示:
// Plugin is the parent type for all the scheduling framework plugins.
type Plugin interface {
Name() string
}
type QueueSortPlugin interface {
Plugin
Less(*PodInfo, *PodInfo) bool
}
// PreFilterPlugin is an interface that must be implemented by "prefilter" plugins.
// These plugins are called at the beginning of the scheduling cycle.
type PreFilterPlugin interface {
Plugin
PreFilter(pc *PluginContext, p *v1.Pod) *Status
}
// FilterPlugin is an interface for Filter plugins. These plugins are called at the
// filter extension point for filtering out hosts that cannot run a pod.
// This concept used to be called 'predicate' in the original scheduler.
// These plugins should return "Success", "Unschedulable" or "Error" in Status.code.
// However, the scheduler accepts other valid codes as well.
// Anything other than "Success" will lead to exclusion of the given host from
// running the pod.
type FilterPlugin interface {
Plugin
Filter(pc *PluginContext, pod *v1.Pod, nodeName string) *Status
}
// PostFilterPlugin is an interface for Post-filter plugin. Post-filter is an
// informational extension point. Plugins will be called with a list of nodes
// that passed the filtering phase. A plugin may use this data to update internal
// state or to generate logs/metrics.
type PostFilterPlugin interface {
Plugin
PostFilter(pc *PluginContext, pod *v1.Pod, nodes []*v1.Node, filteredNodesStatuses NodeToStatusMap) *Status
}
// ScorePlugin is an interface that must be implemented by "score" plugins to rank
// nodes that passed the filtering phase.
type ScorePlugin interface {
Plugin
Score(pc *PluginContext, p *v1.Pod, nodeName string) (int, *Status)
}
// ScoreWithNormalizePlugin is an interface that must be implemented by "score"
// plugins that also need to normalize the node scoring results produced by the same
// plugin's "Score" method.
type ScoreWithNormalizePlugin interface {
ScorePlugin
NormalizeScore(pc *PluginContext, p *v1.Pod, scores NodeScoreList) *Status
}
// ReservePlugin is an interface for Reserve plugins. These plugins are called
// at the reservation point. These are meant to update the state of the plugin.
// This concept used to be called 'assume' in the original scheduler.
// These plugins should return only Success or Error in Status.code. However,
// the scheduler accepts other valid codes as well. Anything other than Success
// will lead to rejection of the pod.
type ReservePlugin interface {
Plugin
Reserve(pc *PluginContext, p *v1.Pod, nodeName string) *Status
}
// PreBindPlugin is an interface that must be implemented by "prebind" plugins.
// These plugins are called before a pod being scheduled.
type PreBindPlugin interface {
Plugin
PreBind(pc *PluginContext, p *v1.Pod, nodeName string) *Status
}
// PostBindPlugin is an interface that must be implemented by "postbind" plugins.
// These plugins are called after a pod is successfully bound to a node.
type PostBindPlugin interface {
Plugin
PostBind(pc *PluginContext, p *v1.Pod, nodeName string)
}
// UnreservePlugin is an interface for Unreserve plugins. This is an informational
// extension point. If a pod was reserved and then rejected in a later phase, then
// un-reserve plugins will be notified. Un-reserve plugins should clean up state
// associated with the reserved Pod.
type UnreservePlugin interface {
Plugin
Unreserve(pc *PluginContext, p *v1.Pod, nodeName string)
}
// PermitPlugin is an interface that must be implemented by "permit" plugins.
// These plugins are called before a pod is bound to a node.
type PermitPlugin interface {
Plugin
Permit(pc *PluginContext, p *v1.Pod, nodeName string) (*Status, time.Duration)
}
// BindPlugin is an interface that must be implemented by "bind" plugins. Bind
// plugins are used to bind a pod to a Node.
type BindPlugin interface {
Plugin
Bind(pc *PluginContext, p *v1.Pod, nodeName string) *Status
}
對于調(diào)度框架插件的啟用或者禁用,我們同樣可以使用上面的 KubeSchedulerConfiguration 資源對象來進(jìn)行配置。
下面的例子中的配置啟用了一個實現(xiàn)了 reserve 和 preBind 擴展點的插件,并且禁用了另外一個插件,同時為插件 foo 提供了一些配置信息:
apiVersion: kubescheduler.config.k8s.io/v1alpha1
kind: KubeSchedulerConfiguration
---
plugins:
reserve:
enabled:
- name: foo
- name: bar
disabled:
- name: baz
preBind:
enabled:
- name: foo
disabled:
- name: baz
pluginConfig:
- name: foo
args: >
foo插件可以解析的任意內(nèi)容 擴展的調(diào)用順序如下:
- 如果某個擴展點沒有配置對應(yīng)的擴展,調(diào)度框架將使用默認(rèn)插件中的擴展
- 如果為某個擴展點配置且激活了擴展,則調(diào)度框架將先調(diào)用默認(rèn)插件的擴展,再調(diào)用配置中的擴展
- 默認(rèn)插件的擴展始終被最先調(diào)用,然后按照
KubeSchedulerConfiguration中擴展的激活enabled順序逐個調(diào)用擴展點的擴展 - 可以先禁用默認(rèn)插件的擴展,然后在
enabled列表中的某個位置激活默認(rèn)插件的擴展,這種做法可以改變默認(rèn)插件的擴展被調(diào)用時的順序
假設(shè)默認(rèn)插件 foo 實現(xiàn)了 reserve 擴展點,此時我們要添加一個插件 bar,想要在 foo 之前被調(diào)用,則應(yīng)該先禁用 foo 再按照 bar foo 的順序激活。
示例配置如下所示:
apiVersion: kubescheduler.config.k8s.io/v1alpha1
kind: KubeSchedulerConfiguration
---
plugins:
reserve:
enabled:
- name: bar
- name: foo
disabled:
- name: foo
在源碼目錄 pkg/scheduler/framework/plugins/examples 中有幾個示范插件,我們可以參照其實現(xiàn)方式。
示例
其實要實現(xiàn)一個調(diào)度框架的插件,并不難,我們只要實現(xiàn)對應(yīng)的擴展點,然后將插件注冊到調(diào)度器中即可,下面是默認(rèn)調(diào)度器在初始化的時候注冊的插件:
func NewRegistry() Registry {
return Registry{
// FactoryMap:
// New plugins are registered here.
// example:
// {
// stateful_plugin.Name: stateful.NewStatefulMultipointExample,
// fooplugin.Name: fooplugin.New,
// }
}
}
但是可以看到默認(rèn)并沒有注冊一些插件,所以要想讓調(diào)度器能夠識別我們的插件代碼,就需要自己來實現(xiàn)一個調(diào)度器了,當(dāng)然這個調(diào)度器我們完全沒必要完全自己實現(xiàn),直接調(diào)用默認(rèn)的調(diào)度器,然后在上面的 NewRegistry() 函數(shù)中將我們的插件注冊進(jìn)去即可。在 kube-scheduler 的源碼文件 kubernetes/cmd/kube-scheduler/app/server.go 中有一個 NewSchedulerCommand 入口函數(shù),其中的參數(shù)是一個類型為 Option 的列表,而這個 Option 恰好就是一個插件配置的定義:
// Option configures a framework.Registry.
type Option func(framework.Registry) error
// NewSchedulerCommand creates a *cobra.Command object with default parameters and registryOptions
func NewSchedulerCommand(registryOptions ...Option) *cobra.Command {
......
}
所以我們完全就可以直接調(diào)用這個函數(shù)來作為我們的函數(shù)入口,并且傳入我們自己實現(xiàn)的插件作為參數(shù)即可,而且該文件下面還有一個名為 WithPlugin 的函數(shù)可以來創(chuàng)建一個 Option 實例:
// WithPlugin creates an Option based on plugin name and factory.
func WithPlugin(name string, factory framework.PluginFactory) Option {
return func(registry framework.Registry) error {
return registry.Register(name, factory)
}
}
所以最終我們的入口函數(shù)如下所示:
func main() {
rand.Seed(time.Now().UTC().UnixNano())
command := app.NewSchedulerCommand(
app.WithPlugin(sample.Name, sample.New),
)
logs.InitLogs()
defer logs.FlushLogs()
if err := command.Execute(); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}
其中 app.WithPlugin(sample.Name, sample.New) 就是我們接下來要實現(xiàn)的插件,從 WithPlugin 函數(shù)的參數(shù)也可以看出我們這里的 sample.New 必須是一個 framework.PluginFactory 類型的值,而 PluginFactory 的定義就是一個函數(shù):
type PluginFactory = func(configuration *runtime.Unknown, f FrameworkHandle) (Plugin, error)
所以 sample.New 實際上就是上面的這個函數(shù),在這個函數(shù)中我們可以獲取到插件中的一些數(shù)據(jù)然后進(jìn)行邏輯處理即可,插件實現(xiàn)如下所示,我們這里只是簡單獲取下數(shù)據(jù)打印日志,如果你有實際需求的可以根據(jù)獲取的數(shù)據(jù)就行處理即可,我們這里只是實現(xiàn)了 PreFilter、Filter、PreBind 三個擴展點,其他的可以用同樣的方式來擴展即可:
// 插件名稱
const Name = "sample-plugin"
type Args struct {
FavoriteColor string `json:"favorite_color,omitempty"`
FavoriteNumber int `json:"favorite_number,omitempty"`
ThanksTo string `json:"thanks_to,omitempty"`
}
type Sample struct {
args *Args
handle framework.FrameworkHandle
}
func (s *Sample) Name() string {
return Name
}
func (s *Sample) PreFilter(pc *framework.PluginContext, pod *v1.Pod) *framework.Status {
klog.V(3).Infof("prefilter pod: %v", pod.Name)
return framework.NewStatus(framework.Success, "")
}
func (s *Sample) Filter(pc *framework.PluginContext, pod *v1.Pod, nodeName string) *framework.Status {
klog.V(3).Infof("filter pod: %v, node: %v", pod.Name, nodeName)
return framework.NewStatus(framework.Success, "")
}
func (s *Sample) PreBind(pc *framework.PluginContext, pod *v1.Pod, nodeName string) *framework.Status {
if nodeInfo, ok := s.handle.NodeInfoSnapshot().NodeInfoMap[nodeName]; !ok {
return framework.NewStatus(framework.Error, fmt.Sprintf("prebind get node info error: %+v", nodeName))
} else {
klog.V(3).Infof("prebind node info: %+v", nodeInfo.Node())
return framework.NewStatus(framework.Success, "")
}
}
//type PluginFactory = func(configuration *runtime.Unknown, f FrameworkHandle) (Plugin, error)
func New(configuration *runtime.Unknown, f framework.FrameworkHandle) (framework.Plugin, error) {
args := &Args{}
if err := framework.DecodeInto(configuration, args); err != nil {
return nil, err
}
klog.V(3).Infof("get plugin config args: %+v", args)
return &Sample{
args: args,
handle: f,
}, nil
}
完整代碼可以前往倉庫 GitHub - cnych/sample-scheduler-framework: This repo is a sample for Kubernetes scheduler framework. 獲取。
實現(xiàn)完成后,編譯打包成鏡像即可,然后我們就可以當(dāng)成普通的應(yīng)用用一個 Deployment 控制器來部署即可,由于我們需要去獲取集群中的一些資源對象,所以當(dāng)然需要申請 RBAC 權(quán)限,然后同樣通過 --config 參數(shù)來配置我們的調(diào)度器,同樣還是使用一個 KubeSchedulerConfiguration 資源對象配置,可以通過 plugins 來啟用或者禁用我們實現(xiàn)的插件,也可以通過 pluginConfig 來傳遞一些參數(shù)值給插件:
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: sample-scheduler-clusterrole
rules:
- apiGroups:
- ""
resources:
- endpoints
- events
verbs:
- create
- get
- update
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- pods
verbs:
- delete
- get
- list
- watch
- update
- apiGroups:
- ""
resources:
- bindings
- pods/binding
verbs:
- create
- apiGroups:
- ""
resources:
- pods/status
verbs:
- patch
- update
- apiGroups:
- ""
resources:
- replicationcontrollers
- services
verbs:
- get
- list
- watch
- apiGroups:
- apps
- extensions
resources:
- replicasets
verbs:
- get
- list
- watch
- apiGroups:
- apps
resources:
- statefulsets
verbs:
- get
- list
- watch
- apiGroups:
- policy
resources:
- poddisruptionbudgets
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- persistentvolumeclaims
- persistentvolumes
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch
- apiGroups:
- "storage.k8s.io"
resources:
- storageclasses
- csinodes
verbs:
- get
- list
- watch
- apiGroups:
- "coordination.k8s.io"
resources:
- leases
verbs:
- create
- get
- list
- update
- apiGroups:
- "events.k8s.io"
resources:
- events
verbs:
- create
- patch
- update
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: sample-scheduler-sa
namespace: kube-system
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: sample-scheduler-clusterrolebinding
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: sample-scheduler-clusterrole
subjects:
- kind: ServiceAccount
name: sample-scheduler-sa
namespace: kube-system
---
apiVersion: v1
kind: ConfigMap
metadata:
name: scheduler-config
namespace: kube-system
data:
scheduler-config.yaml: |
apiVersion: kubescheduler.config.k8s.io/v1alpha1
kind: KubeSchedulerConfiguration
schedulerName: sample-scheduler
leaderElection:
leaderElect: true
lockObjectName: sample-scheduler
lockObjectNamespace: kube-system
plugins:
preFilter:
enabled:
- name: "sample-plugin"
filter:
enabled:
- name: "sample-plugin"
preBind:
enabled:
- name: "sample-plugin"
pluginConfig:
- name: "sample-plugin"
args:
favorite_color: "#326CE5"
favorite_number: 7
thanks_to: "thockin"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sample-scheduler
namespace: kube-system
labels:
component: sample-scheduler
spec:
replicas: 1
selector:
matchLabels:
component: sample-scheduler
template:
metadata:
labels:
component: sample-scheduler
spec:
serviceAccount: sample-scheduler-sa
priorityClassName: system-cluster-critical
volumes:
- name: scheduler-config
configMap:
name: scheduler-config
containers:
- name: scheduler-ctrl
image: cnych/sample-scheduler:v0.1.6
imagePullPolicy: IfNotPresent
args:
- sample-scheduler-framework
- --config=/etc/kubernetes/scheduler-config.yaml
- --v=3
resources:
requests:
cpu: "50m"
volumeMounts:
- name: scheduler-config
mountPath: /etc/kubernetes直接部署上面的資源對象即可,這樣我們就部署了一個名為 sample-scheduler 的調(diào)度器了,接下來我們可以部署一個應(yīng)用來使用這個調(diào)度器進(jìn)行調(diào)度:
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-scheduler
spec:
replicas: 1
selector:
matchLabels:
app: test-scheduler
template:
metadata:
labels:
app: test-scheduler
spec:
schedulerName: sample-scheduler
containers:
- image: nginx
imagePullPolicy: IfNotPresent
name: nginx
ports:
- containerPort: 80這里需要注意的是我們現(xiàn)在手動指定了一個 schedulerName 的字段,將其設(shè)置成上面我們自定義的調(diào)度器名稱 sample-scheduler。
我們直接創(chuàng)建這個資源對象,創(chuàng)建完成后查看我們自定義調(diào)度器的日志信息:
$ kubectl get pods -n kube-system -l component=sample-scheduler
NAME READY STATUS RESTARTS AGE
sample-scheduler-7c469787f-rwhhd 1/1 Running 0 13m
$ kubectl logs -f sample-scheduler-7c469787f-rwhhd -n kube-system
I0104 08:24:22.087881 1 scheduler.go:530] Attempting to schedule pod: default/test-scheduler-6d779d9465-rq2bb
I0104 08:24:22.087992 1 plugins.go:23] prefilter pod: test-scheduler-6d779d9465-rq2bb
I0104 08:24:22.088657 1 plugins.go:28] filter pod: test-scheduler-6d779d9465-rq2bb, node: ydzs-node1
I0104 08:24:22.088797 1 plugins.go:28] filter pod: test-scheduler-6d779d9465-rq2bb, node: ydzs-node2
I0104 08:24:22.088871 1 plugins.go:28] filter pod: test-scheduler-6d779d9465-rq2bb, node: ydzs-node3
I0104 08:24:22.088946 1 plugins.go:28] filter pod: test-scheduler-6d779d9465-rq2bb, node: ydzs-node4
I0104 08:24:22.088992 1 plugins.go:28] filter pod: test-scheduler-6d779d9465-rq2bb, node: ydzs-master
I0104 08:24:22.090653 1 plugins.go:36] prebind node info: &Node{ObjectMeta:{ydzs-node3 /api/v1/nodes/ydzs-node3 1ff6e228-4d98-4737-b6d3-30a5d55ccdc2 15466372 0 2019-11-10 09:05:09 +0000 UTC <nil> <nil> ......}
I0104 08:24:22.091761 1 factory.go:610] Attempting to bind test-scheduler-6d779d9465-rq2bb to ydzs-node3
I0104 08:24:22.104994 1 scheduler.go:667] pod default/test-scheduler-6d779d9465-rq2bb is bound successfully on node "ydzs-node3", 5 nodes evaluated, 4 nodes were found feasible. Bound node resource: "Capacity: CPU<4>|Memory<8008820Ki>|Pods<110>|StorageEphemeral<17921Mi>; Allocatable: CPU<4>|Memory<7906420Ki>|Pods<110>|StorageEphemeral<16912377419>.".
可以看到當(dāng)我們創(chuàng)建完 Pod 后,在我們自定義的調(diào)度器中就出現(xiàn)了對應(yīng)的日志,并且在我們定義的擴展點上面都出現(xiàn)了對應(yīng)的日志,證明我們的示例成功了,也可以通過查看 Pod 的 schedulerName 來驗證:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
test-scheduler-6d779d9465-rq2bb 1/1 Running 0 22m
$ kubectl get pod test-scheduler-6d779d9465-rq2bb -o yaml
......
restartPolicy: Always
schedulerName: sample-scheduler
securityContext: {}
serviceAccount: default
......
在最新的 Kubernetes v1.17 版本中,Scheduler Framework 內(nèi)置的預(yù)選和優(yōu)選函數(shù)已經(jīng)全部插件化,所以要擴展調(diào)度器我們應(yīng)該掌握并理解調(diào)度框架這種方式。
5. 疑問和思考
5.1 在k8s環(huán)境中,由于調(diào)度對于機器的資源只考慮request類型,實際上一臺服務(wù)器的cpu使用率可能已經(jīng)很高,但是依然被分配了pod上去,能否實現(xiàn)一種調(diào)度器,將cpu使用率作為一種考慮?該如何實現(xiàn)?
是的。
在實際情況中,pod申請到的資源并不一定會被pod使用,而k8s層面多數(shù)并沒有進(jìn)行資源綁核,因此就會導(dǎo)致cpu被分配出去實際上并沒有被使用的情況。對于k8s而言,緊緊依賴request資源調(diào)度pod到運行的pod,這種類型的策略有點單薄,但是當(dāng)前的調(diào)度策略中并沒有該類型的調(diào)度策略方式。 因此需要自定義研發(fā)調(diào)度策略。
自定義研發(fā)調(diào)度策略最難的還是指標(biāo)采集鏈路,因為k8s容器調(diào)度是核心鏈路環(huán)節(jié),而k8s調(diào)度器在申請調(diào)度時如果依賴外部的指標(biāo)采集,從而獲取cpu、內(nèi)存的使用率情況,這會引入一個依賴,并且通常該依賴的鏈條都比較長。同時由于是監(jiān)控系統(tǒng),通常公司級別對于監(jiān)控系統(tǒng)的優(yōu)先級定位并不高,因此對應(yīng)的服務(wù)性難以保證,因此在實現(xiàn)策略時可以這樣。
- 定時采集外部指標(biāo),并進(jìn)行緩存機器指標(biāo)
- 在調(diào)度策略中,對于機器的優(yōu)選和過濾環(huán)節(jié),加入cpu、內(nèi)存使用率的指標(biāo)考慮,參與調(diào)度邏輯
- 充分考慮降級策略,如果出現(xiàn)指標(biāo)延遲、臟數(shù)據(jù)、壞數(shù)據(jù)等情況,能夠及時的降級到默認(rèn)調(diào)度策略,避免核心鏈路環(huán)節(jié)異?;蛘哒{(diào)度緩慢。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Kubernetes中crictl的詳細(xì)用法教程與應(yīng)用實戰(zhàn)記錄
crictl作為Kubernetes的容器運行時接口(CRI)的命令行工具,為Kubernetes的調(diào)試和管理提供了強大的支持,通過本文的詳細(xì)介紹,你應(yīng)該已經(jīng)掌握了crictl的基本安裝、配置、常用命令以及高級用法,需要的朋友可以參考下2024-07-07
k8s?pod始終處于pending狀態(tài)的解決方案
新K8s部署后服務(wù)重啟導(dǎo)致dashboard無法訪問,所有Pod處于Pending狀態(tài),原因分析顯示,因節(jié)點污點引發(fā)調(diào)度失敗,刪除污點后問題解決,總結(jié)Pending原因分為三類:調(diào)度問題(污點、資源不足)、鏡像問題(拉取失?。?、依賴性問題(卷/Secret/ConfigMap缺失)2025-08-08
Rainbond部署組件Statefulset的使用官方文檔
這篇文章主要為大家介紹了官方文檔Rainbond部署組件Statefulset的使用,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04
Kubernetes?權(quán)限管理認(rèn)證鑒權(quán)詳解
這篇文章主要為大家介紹了Kubernetes?權(quán)限管理認(rèn)證鑒權(quán)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
訪問k8s集群部署的微服務(wù)內(nèi)部服務(wù)接口問題
文章介紹了在Kubernetes集群中訪問微服務(wù)內(nèi)部服務(wù)接口的幾種方法,包括通過Pod的localhost訪問、使用服務(wù)名和DNS解析、以及通過注冊中心的地址訪問2026-02-02

