1. 程式人生 > >10.深入k8s:排程的優先順序及搶佔機制原始碼分析

10.深入k8s:排程的優先順序及搶佔機制原始碼分析

> 轉載請宣告出處哦~,本篇文章釋出於luozhiyun的部落格:https://www.luozhiyun.com > > 原始碼版本是[1.19](https://github.com/kubernetes/kubernetes/tree/release-1.19) ![84253409_p0](https://img.luozhiyun.com/20200912223315.png) 上一篇我們將了獲取node成功的情況,如果是一個優先pod獲取node失敗,那麼就會進入到搶佔環節中,那麼搶佔環節k8s會做什麼呢,搶佔是如何發生的,哪些資源會被搶佔這些都是我們這篇要研究的內容。 ## 排程的優先順序與搶佔機制 正常情況下,當一個 Pod 排程失敗後,它就會被暫時“擱置”起來,直到 Pod 被更新,或者叢集狀態發生變化,排程器才會對這個 Pod 進行重新排程。但是我們可以通過PriorityClass優先順序來避免這種情況。通過設定優先順序一些優先順序比較高的pod,如果pod 排程失敗,那麼並不會被”擱置”,而是會”擠走”某個 node 上的一些低優先順序的 pod,這樣就可以保證高優先順序的 pod 排程成功。 要使用PriorityClass,首先我們要定義一個PriorityClass物件,例如: ```yaml apiVersion: v1 kind: PriorityClass metadata: name: high-priority value: 1000000 globalDefault: false description: "This priority class should be used for XYZ service pods only." ``` value越高則優先順序越高;globalDefault 被設定為 true 的話,那就意味著這個 PriorityClass 的值會成為系統的預設值,如果是false則表示我們只希望宣告使用該 PriorityClass 的 Pod 擁有值為 1000000 的優先順序,而對於沒有宣告 PriorityClass 的 Pod 來說,它們的優先順序就是 0。 Pod 就可以宣告使用它了: ```yaml apiVersion: v1 kind: Pod metadata: name: nginx labels: env: test spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent priorityClassName: high-priority ``` 高優先順序的 Pod 排程失敗的時候,排程器的搶佔能力就會被觸發。排程器就會試圖從當前叢集裡尋找一個節點,使得當這個節點上的一個或者多個低優先順序 Pod 被刪除後,待排程的高優先順序 Pod 就可以被排程到這個節點上。 高優先順序Pod進行搶佔的時候會將pod的 nominatedNodeName 欄位,設定為被搶佔的 Node 的名字。然後,在下一週期中決定是不是要執行在被搶佔的節點上,當這個Pod在等待的時候,如果有其他更高優先順序的 Pod 也要搶佔同一個節點,那麼排程器就會清空原搶佔者的 spec.nominatedNodeName 欄位,從而允許更高優先順序的搶佔者執行搶佔。 ## 原始碼解析 這裡我依舊拿出這張圖來進行講解,上一篇我們將了獲取node成功的情況,如果是一個優先pod獲取node失敗,那麼就會進入到搶佔環節中。 ![排程流程](https://img.luozhiyun.com/20200905190824.png) 通過上一篇的分析,我們知道,在scheduleOne方法中執行sched.Algorithm.Schedule會選擇一個合適的node節點,如果獲取node失敗,那麼就會進入到一個if邏輯中執行搶佔。 程式碼路徑:pkg/scheduler/scheduler.go ```go func (sched *Scheduler) scheduleOne(ctx context.Context) { ... //為pod資源物件選擇一個合適的節點 scheduleResult, err := sched.Algorithm.Schedule(schedulingCycleCtx, prof, state, pod) //獲取node失敗,搶佔邏輯 if err != nil { //上面呼叫失敗之後,下面會根據pod執行搶佔 nominatedNode := "" if fitError, ok := err.(*core.FitError); ok { if !prof.HasPostFilterPlugins() { klog.V(3).Infof("No PostFilter plugins are registered, so no preemption will be performed.") } else { result, status := prof.RunPostFilterPlugins(ctx, state, pod, fitError.FilteredNodesStatuses) if status.Code() == framework.Error { klog.Errorf("Status after running PostFilter plugins for pod %v/%v: %v", pod.Namespace, pod.Name, status) } else { klog.V(5).Infof("Status after running PostFilter plugins for pod %v/%v: %v", pod.Namespace, pod.Name, status) } //搶佔成功後,將nominatedNodeName設定為被搶佔的 Node 的名字,然後重新進入下一個排程週期 if status.IsSuccess() && result != nil { nominatedNode = result.NominatedNodeName } } metrics.PodUnschedulable(prof.Name, metrics.SinceInSeconds(start)) } else if err == core.ErrNoNodesAvailable { metrics.PodUnschedulable(prof.Name, metrics.SinceInSeconds(start)) } else { klog.ErrorS(err, "Error selecting node for pod", "pod", klog.KObj(pod)) metrics.PodScheduleError(prof.Name, metrics.SinceInSeconds(start)) } sched.recordSchedulingFailure(prof, podInfo, err, v1.PodReasonUnschedulable, nominatedNode) return } ... } ``` 在這個方法裡面RunPostFilterPlugins會執行具體的搶佔邏輯,然後返回被搶佔的node節點。搶佔者並不會立刻被排程到被搶佔的 node 上,排程器只會將搶佔者的 status.nominatedNodeName 欄位設定為被搶佔的 node 的名字。然後,搶佔者會重新進入下一個排程週期,在新的排程週期裡來決定是不是要執行在被搶佔的節點上,當然,即使在下一個排程週期,排程器也不會保證搶佔者一定會執行在被搶佔的節點上。 這樣設計的一個重要原因是排程器只會通過標準的 DELETE API 來刪除被搶佔的 pod,所以,這些 pod 必然是有一定的“優雅退出”時間(預設是 30s)的。而在這段時間裡,其他的節點也是有可能變成可排程的,或者直接有新的節點被新增到這個叢集中來。 而在搶佔者等待被排程的過程中,如果有其他更高優先順序的 pod 也要搶佔同一個節點,那麼排程器就會清空原搶佔者的 status.nominatedNodeName 欄位,從而允許更高優先順序的搶佔者執行搶佔,並且,這也使得原搶佔者本身也有機會去重新搶佔其他節點。 接著我們繼續看,RunPostFilterPlugins會遍歷所有的postFilterPlugins,然後執行runPostFilterPlugin方法: ```go func (f *frameworkImpl) RunPostFilterPlugins(ctx context.Context, state *framework.CycleState, pod *v1.Pod, filteredNodeStatusMap framework.NodeToStatusMap) (_ *framework.PostFilterResult, status *framework.Status) { startTime := time.Now() defer func() { metrics.FrameworkExtensionPointDuration.WithLabelValues(postFilter, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime)) }() statuses := make(framework.PluginToStatus) //postFilterPlugins裡面只有一個defaultpreemption for _, pl := range f.postFilterPlugins { r, s := f.runPostFilterPlugin(ctx, pl, state, pod, filteredNodeStatusMap) if s.IsSuccess() { return r, s } else if !s.IsUnschedulable() { // Any status other than Success or Unschedulable is Error. return nil, framework.NewStatus(framework.Error, s.Message()) } statuses[pl.Name()] = s } return nil, statuses.Merge() } ``` 根據我們上一節看的scheduler的初始化可以知道設定的PostFilter如下: 程式碼路徑:pkg/scheduler/algorithmprovider/registry.go ``` PostFilter: &schedulerapi.PluginSet{ Enabled: []schedulerapi.Plugin{ {Name: defaultpreemption.Name}, }, }, ``` 可見,目前只有一個defaultpreemption來執行搶佔邏輯,在postFilterPlugins迴圈裡面會呼叫到runPostFilterPlugin然後執行defaultpreemption的PostFilter方法,最後執行到preempt執行具體搶佔邏輯。 程式碼路徑:pkg/scheduler/framework/plugins/defaultpreemption/default_preemption.go ```go func (pl *DefaultPreemption) PostFilter(...) (*framework.PostFilterResult, *framework.Status) { ... //執行搶佔 nnn, err := pl.preempt(ctx, state, pod, m) ... return &framework.PostFilterResult{NominatedNodeName: nnn}, framework.NewStatus(framework.Success) } ``` 搶佔的執行流程圖如下: ![搶佔](https://img.luozhiyun.com/20200912223345.png) 程式碼路徑:pkg/scheduler/framework/plugins/defaultpreemption/default_preemption.go ```go func (pl *DefaultPreemption) preempt(ctx context.Context, state *framework.CycleState, pod *v1.Pod, m framework.NodeToStatusMap) (string, error) { cs := pl.fh.ClientSet() ph := pl.fh.PreemptHandle() //返回node列表 nodeLister := pl.fh.SnapshotSharedLister().NodeInfos() pod, err := util.GetUpdatedPod(cs, pod) if err != nil { klog.Errorf("Error getting the updated preemptor pod object: %v", err) return "", err } //確認搶佔者是否能夠進行搶佔,如果對應的node節點上的pod正在優雅退出(Graceful Termination ),那麼就不應該進行搶佔 if !PodEligibleToPreemptOthers(pod, nodeLister, m[pod.Status.NominatedNodeName]) { klog.V(5).Infof("Pod %v/%v is not eligible for more preemption.", pod.Namespace, pod.Name) return "", nil } // 查詢所有搶佔候選者 candidates, err := FindCandidates(ctx, cs, state, pod, m, ph, nodeLister, pl.pdbLister) if err != nil || len(candidates) == 0 { return "", err } //若有 extender 則執行 candidates, err = CallExtenders(ph.Extenders(), pod, nodeLister, candidates) if err != nil { return "", err } // 查詢最佳搶佔候選者 bestCandidate := SelectCandidate(candidates) if bestCandidate == nil || len(bestCandidate.Name()) == 0 { return "", nil } // 在搶佔一個node之前做一些準備工作 if err := PrepareCandidate(bestCandidate, pl.fh, cs, pod); err != nil { return "", err } return bestCandidate.Name(), nil } ``` preempt方法首先會去獲取node列表,然後獲取最新的要執行搶佔的pod資訊,接著分下面幾步執行搶佔: 1. 呼叫PodEligibleToPreemptOthers方法,檢查搶佔者是否能夠進行搶佔,如果當前的pod已經搶佔了一個node節點或者在被搶佔node節點中有pod正在執行優雅退出,那麼不應該執行搶佔; 2. 呼叫FindCandidates找到所有node中能被搶佔的node節點,並返回候選列表以及node節點中需要被刪除的pod; 3. 若有 extender 則執行CallExtenders; 4. 呼叫SelectCandidate方法在所有候選列表中找出最合適的node節點執行搶佔; 5. 呼叫PrepareCandidate方法刪除被搶佔的node節點中victim(犧牲者),以及清除NominatedNodeName欄位資訊; **PodEligibleToPreemptOthers** ```go func PodEligibleToPreemptOthers(pod *v1.Pod, nodeInfos framework.NodeInfoLister, nominatedNodeStatus *framework.Status) bool { if pod.Spec.PreemptionPolicy != nil && *pod.Spec.PreemptionPolicy == v1.PreemptNever { klog.V(5).Infof("Pod %v/%v is not eligible for preemption because it has a preemptionPolicy of %v", pod.Namespace, pod.Name, v1.PreemptNever) return false } //檢視搶佔者是否已經搶佔過 nomNodeName := pod.Status.NominatedNodeName if len(nomNodeName) > 0 { if nominatedNodeStatus.Code() == framework.UnschedulableAndUnresolvable { return true } //獲取被搶佔的node節點 if nodeInfo, _ := nodeInfos.Get(nomNodeName); nodeInfo != nil { //檢視是否存在正在被刪除並且優先順序比搶佔者pod低的pod podPriority := podutil.GetPodPriority(pod) for _, p := range nodeInfo.Pods { if p.Pod.DeletionTimestamp != nil && podutil.GetPodPriority(p.Pod) < podPriority { // There is a terminating pod on the nominated node. return false } } } } return true } ``` 這個方法會檢查該pod是否已經搶佔過其他node節點,如果是的話就遍歷節點上的所有pod物件,如果發現節點上有pod資源物件的優先順序小於待排程pod資源物件並處於終止狀態,則返回false,不會發生搶佔。 接下來看FindCandidates方法: **FindCandidates** ```go func FindCandidates(ctx context.Context, cs kubernetes.Interface, state *framework.CycleState, pod *v1.Pod, m framework.NodeToStatusMap, ph framework.PreemptHandle, nodeLister framework.NodeInfoLister, pdbLister policylisters.PodDisruptionBudgetLister) ([]Candidate, error) { allNodes, err := nodeLister.List() if err != nil { return nil, err } if len(allNodes) == 0 { return nil, core.ErrNoNodesAvailable } //找 predicates 階段失敗但是通過搶佔也許能夠排程成功的 nodes potentialNodes := nodesWherePreemptionMightHelp(allNodes, m) if len(potentialNodes) == 0 { klog.V(3).Infof("Preemption will not help schedule pod %v/%v on any node.", pod.Namespace, pod.Name) if err := util.ClearNominatedNodeName(cs, pod); err != nil { klog.Errorf("Cannot clear 'NominatedNodeName' field of pod %v/%v: %v", pod.Namespace, pod.Name, err) } return nil, nil } if klog.V(5).Enabled() { var sample []string for i := 0; i < 10 && i < len(potentialNodes); i++ { sample = append(sample, potentialNodes[i].Node().Name) } klog.Infof("%v potential nodes for preemption, first %v are: %v", len(potentialNodes), len(sample), sample) } //獲取PDB物件,PDB能夠限制同時終端的pod資源物件的數量,以保證叢集的高可用性 pdbs, err := getPodDisruptionBudgets(pdbLister) if err != nil { return nil, err } //尋找符合條件的node,並封裝成candidate陣列返回 return dryRunPreemption(ctx, ph, state, pod, potentialNodes, pdbs), nil } ``` FindCandidates方法首先會獲取node列表,然後呼叫nodesWherePreemptionMightHelp方法來找出predicates 階段失敗但是通過搶佔也許能夠排程成功的nodes,因為並不是所有的node都可以通過搶佔來排程成功。最後呼叫dryRunPreemption方法來獲取符合條件的node節點。 **dryRunPreemption** ```go func dryRunPreemption(ctx context.Context, fh framework.PreemptHandle, state *framework.CycleState, pod *v1.Pod, potentialNodes []*framework.NodeInfo, pdbs []*policy.PodDisruptionBudget) []Candidate { var resultLock sync.Mutex var candidates []Candidate checkNode := func(i int) { nodeInfoCopy := potentialNodes[i].Clone() stateCopy := state.Clone() //找到node上被搶佔的pod,也就是victims pods, numPDBViolations, fits := selectVictimsOnNode(ctx, fh, stateCopy, pod, nodeInfoCopy, pdbs) if fits { resultLock.Lock() victims := extenderv1.Victims{ Pods: pods, NumPDBViolations: int64(numPDBViolations), } c := candidate{ victims: &victims, name: nodeInfoCopy.Node().Name, } candidates = append(candidates, &c) resultLock.Unlock() } } parallelize.Until(ctx, len(potentialNodes), checkNode) return candidates } ``` 這裡會開啟16個執行緒呼叫checkNode方法,checkNode方法裡面會呼叫selectVictimsOnNode方法來檢查這個node是不是能被執行搶佔,如果能執行搶佔返回的pods表示需要刪除的節點,然後封裝成candidate新增到candidates列表中返回。 **selectVictimsOnNode** ```go func selectVictimsOnNode( ctx context.Context, ph framework.PreemptHandle, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo, pdbs []*policy.PodDisruptionBudget, ) ([]*v1.Pod, int, bool) { var potentialVictims []*v1.Pod //移除node節點的pod removePod := func(rp *v1.Pod) error { if err := nodeInfo.RemovePod(rp); err != nil { return err } status := ph.RunPreFilterExtensionRemovePod(ctx, state, pod, rp, nodeInfo) if !status.IsSuccess() { return status.AsError() } return nil } //將node節點新增pod addPod := func(ap *v1.Pod) error { nodeInfo.AddPod(ap) status := ph.RunPreFilterExtensionAddPod(ctx, state, pod, ap, nodeInfo) if !status.IsSuccess() { return status.AsError() } return nil } // 獲取pod的優先順序,並將node中所有優先順序低於該pod的呼叫removePod方法pod移除 podPriority := podutil.GetPodPriority(pod) for _, p := range nodeInfo.Pods { if podutil.GetPodPriority(p.Pod) < podPriority { potentialVictims = append(potentialVictims, p.Pod) if err := removePod(p.Pod); err != nil { return nil, 0, false } } } //沒有優先順序低的node,直接返回 if len(potentialVictims) == 0 { return nil, 0, false } if fits, _, err := core.PodPassesFiltersOnNode(ctx, ph, state, pod, nodeInfo); !fits { if err != nil { klog.Warningf("Encountered error while selecting victims on node %v: %v", nodeInfo.Node().Name, err) } return nil, 0, false } var victims []*v1.Pod numViolatingVictim := 0 //將potentialVictims集合裡的pod按照優先順序進行排序 sort.Slice(potentialVictims, func(i, j int) bool { return util.MoreImportantPod(potentialVictims[i], potentialVictims[j]) }) //將pdb的pod分離出來 //基於 pod 是否有 PDB 被分為兩組 violatingVictims 和 nonViolatingVictims //PDB:https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ violatingVictims, nonViolatingVictims := filterPodsWithPDBViolation(potentialVictims, pdbs) reprievePod := func(p *v1.Pod) (bool, error) { if err := addPod(p); err != nil { return false, err } fits, _, _ := core.PodPassesFiltersOnNode(ctx, ph, state, pod, nodeInfo) if !fits { if err := removePod(p); err != nil { return false, err } // 加入到 victims 中 victims = append(victims, p) klog.V(5).Infof("Pod %v/%v is a potential preemption victim on node %v.", p.Namespace, p.Name, nodeInfo.Node().Name) } return fits, nil } //刪除pod,並記錄刪除個數 for _, p := range violatingVictims { if fits, err := reprievePod(p); err != nil { klog.Warningf("Failed to reprieve pod %q: %v", p.Name, err) return nil, 0, false } else if !fits { numViolatingVictim++ } } //刪除pod for _, p := range nonViolatingVictims { if _, err := reprievePod(p); err != nil { klog.Warningf("Failed to reprieve pod %q: %v", p.Name, err) return nil, 0, false } } return victims, numViolatingVictim, true } ``` 這個方法首先定義了兩個方法,一個是removePod,另一個是addPod,這兩個方法都差不多,如果是removePod就會將pod從node中移除,然後修改node一些屬性,如將Requested.MilliCPU、Requested.Memory中減去,表示已用資源大小,將該pod從node節點的Pods列表中移除等等。 回到selectVictimsOnNode繼續往下,會遍歷node裡面的pod列表,如果找到優先順序小於搶佔pod的就加入到potentialVictims集合中,並呼叫removePod方法,將當前被遍歷的pod從node中移除。 接著會呼叫PodPassesFiltersOnNode方法,這個方法會執行兩次。第一次會呼叫addNominatedPods方法將排程佇列中找到節點上優先順序大於或等於當前pod資源物件的nominatedPods加入到nodeInfo物件中,然後執行FilterPlugin列表;第二次則直接執行FilterPlugins列表。之所以要這麼做,是由於親和性的關係,k8s需要判斷當前排程的pod親和性是否依賴了nominatedPods。 繼續往下會對potentialVictims按照優先順序進行排序,優先順序高的在前面。 接著會呼叫filterPodsWithPDBViolation方法,將 PDB 約束的 Pod和未約束的Pod分離成兩個組,然後會分別遍歷violatingVictims和nonViolatingVictims呼叫reprievePod方法對pod進行移除。這裡我們在[官方文件](https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#poddisruptionbudget-%E6%98%AF%E8%A2%AB%E6%94%AF%E6%8C%81%E7%9A%84-%E4%BD%86%E4%B8%8D%E6%8F%90%E4%BE%9B%E4%BF%9D%E8%AF%81)也可以看其設計理念,PodDisruptionBudget 是在搶佔中被支援的,但不提供保證,然後將被移除的pod新增到victims列表中,並記錄好被刪除的刪除pod個數,最後返回。 到這裡整個FindCandidates方法就探索完畢了,還是比較長的,我們繼續回到preempt方法中往下看,SelectCandidate方法會查詢最佳搶佔候選者。 **SelectCandidate** ```go func SelectCandidate(candidates []Candidate) Candidate { if len(candidates) == 0 { return nil } if len(candidates) == 1 { return candidates[0] } victimsMap := candidatesToVictimsMap(candidates) // 選擇1個 node 用於 schedule candidateNode := pickOneNodeForPreemption(victimsMap) for _, candidate := range candidates { if candidateNode == candidate.Name() { return candidate } } klog.Errorf("None candidate can be picked from %v.", candidates) return candidates[0] } ``` 這個方法裡面會呼叫candidatesToVictimsMap方法做一個name和victims對映map,然後呼叫pickOneNodeForPreemption執行主要過濾邏輯。 **pickOneNodeForPreemption** ```go func pickOneNodeForPreemption(nodesToVictims map[string]*extenderv1.Victims) string { //若該 node 沒有 victims 則返回 if len(nodesToVictims) == 0 { return "" } minNumPDBViolatingPods := int64(math.MaxInt32) var minNodes1 []string lenNodes1 := 0 //尋找 PDB violations 數量最小的 node for node, victims := range nodesToVictims { numPDBViolatingPods := victims.NumPDBViolations if numPDBViolatingPods < minNumPDBViolatingPods { minNumPDBViolatingPods = numPDBViolatingPods minNodes1 = nil lenNodes1 = 0 } if numPDBViolatingPods == minNumPDBViolatingPods { minNodes1 = append(minNodes1, node) lenNodes1++ } } //如果最小的node只有一個,直接返回 if lenNodes1 == 1 { return minNodes1[0] } minHighestPriority := int32(math.MaxInt32) var minNodes2 = make([]string, lenNodes1) lenNodes2 := 0 // 找到node裡面pods 最高優先順序最小的 for i := 0; i < lenNodes1; i++ { node := minNodes1[i] victims := nodesToVictims[node] highestPodPriority := podutil.GetPodPriority(victims.Pods[0]) if highestPodPriority < minHighestPriority { minHighestPriority = highestPodPriority lenNodes2 = 0 } if highestPodPriority == minHighestPriority { minNodes2[lenNodes2] = node lenNodes2++ } } if lenNodes2 == 1 { return minNodes2[0] } // 找出node裡面Victims列表優先順序加和最小的 minSumPriorities := int64(math.MaxInt64) lenNodes1 = 0 for i := 0; i < lenNodes2; i++ { var sumPriorities int64 node := minNodes2[i] for _, pod := range nodesToVictims[node].Pods { sumPriorities += int64(podutil.GetPodPriority(pod)) + int64(math.MaxInt32+1) } if sumPriorities < minSumPriorities { minSumPriorities = sumPriorities lenNodes1 = 0 } if sumPriorities == minSumPriorities { minNodes1[lenNodes1] = node lenNodes1++ } } if lenNodes1 == 1 { return minNodes1[0] } // 找到node列表中需要犧牲的pod數量最小的 minNumPods := math.MaxInt32 lenNodes2 = 0 for i := 0; i < lenNodes1; i++ { node := minNodes1[i] numPods := len(nodesToVictims[node].Pods) if numPods < minNumPods { minNumPods = numPods lenNodes2 = 0 } if numPods == minNumPods { minNodes2[lenNodes2] = node lenNodes2++ } } if lenNodes2 == 1 { return minNodes2[0] } //若多個 node 的 pod 數量相等,則選出高優先順序 pod 啟動時間最短的 latestStartTime := util.GetEarliestPodStartTime(nodesToVictims[minNodes2[0]]) if latestStartTime == nil { klog.Errorf("earliestStartTime is nil for node %s. Should not reach here.", minNodes2[0]) return minNodes2[0] } nodeToReturn := minNodes2[0] for i := 1; i < lenNodes2; i++ { node := minNodes2[i] earliestStartTimeOnNode := util.GetEarliestPodStartTime(nodesToVictims[node]) if earliestStartTimeOnNode == nil { klog.Errorf("earliestStartTime is nil for node %s. Should not reach here.", node) continue } if earliestStartTimeOnNode.After(latestStartTime.Time) { latestStartTime = earliestStartTimeOnNode nodeToReturn = node } } return nodeToReturn } ``` 這個方法看起來很長,其實邏輯十分的清晰: 1. 找出最少的的PDB violations的node節點,如果找出的node集合大於1則往下走; 2. 找出找到node裡面pods 最高優先順序最小的node,如果還是找出的node集合大於1則往下走; 3. 找出node裡面Victims列表優先順序加和最小的,如果還是找出的node集合大於1則往下走; 4. 找到node列表中需要犧牲的pod數量最小的,如果還是找出的node集合大於1則往下走; 5. 若多個 node 的 pod 數量相等,則選出高優先順序 pod 啟動時間最短的,然後返回。 然後preempt方法往下走到呼叫PrepareCandidate方法: **PrepareCandidate** ```go func PrepareCandidate(c Candidate, fh framework.FrameworkHandle, cs kubernetes.Interface, pod *v1.Pod) error { for _, victim := range c.Victims().Pods { if err := util.DeletePod(cs, victim); err != nil { klog.Errorf("Error preempting pod %v/%v: %v", victim.Namespace, victim.Name, err) return err } if waitingPod := fh.GetWaitingPod(victim.UID); waitingPod != nil { waitingPod.Reject("preempted") } fh.EventRecorder().Eventf(victim, pod, v1.EventTypeNormal, "Preempted", "Preempting", "Preempted by %v/%v on node %v", pod.Namespace, pod.Name, c.Name()) } metrics.PreemptionVictims.Observe(float64(len(c.Victims().Pods))) //移除低優先順序 pod 的 Nominated,更新這些 pod,移動到 activeQ 佇列中,讓排程器為這些 pod 重新 bind node nominatedPods := getLowerPriorityNominatedPods(fh.PreemptHandle(), pod, c.Name()) if err := util.ClearNominatedNodeName(cs, nominatedPods...); err != nil { klog.Errorf("Cannot clear 'NominatedNodeName' field: %v", err) // We do not return as this error is not critical. } return nil } ``` 這個方法會呼叫DeletePod刪除Victims列表裡面的pod,然後將這些pod中的Status.NominatedNodeName屬性置空。 到這裡整個搶佔過程就講解完畢了~ ### 總結 看完這一篇我們對k8s的搶佔可以說有一個全域性的瞭解,心裡應該非常清楚k8s在搶佔的時候會發生什麼,例如什麼時候時候哪些pod會執行搶佔,以及為什麼執行搶佔,以及搶佔了哪些pod的資源,對於被搶佔的pod會不會重新被排程等等。 ## Reference https://kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework/ https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/ https://kubernetes.io/docs/concepts/configuration/pod-overhead/ https://kubernetes.io/docs/concepts/workloads/pods/disruptions/ https://kubernetes.io/docs/tasks/run-application/configure-pdb/ https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-a