思考 Q1 k8s的預設排程器是在哪個環節過濾滿足這個pod資源的節點的?
- 如果問你是否瞭解k8s的排程原理,大家估計都會滔滔不絕說一通
- 但是是否真正的瞭解其中的細節估計就不好說了
- 下面是我閱讀k8s排程器的原始碼分析的全過程
我的23個課程推薦
k8s零基礎入門運維課程
k8s純原始碼解讀教程(3個課程內容合成一個大課程)
k8s運維進階調優課程
k8s管理運維平臺實戰
k8s二次開發課程
cicd 課程
prometheus全元件的教程
- 01_prometheus零基礎入門,grafana基礎操作,主流exporter採集配置
- 02_prometheus全元件配置使用、底層原理解析、高可用實戰
- 03_prometheus-thanos使用和原始碼解讀
- 04_kube-prometheus和prometheus-operator實戰和原理介紹
- 05_prometheus原始碼講解和二次開發
- 06_prometheus監控k8s的實戰配置和原理講解,寫go專案暴露業務指標
go語言課程
- golang基礎課程
- golang實戰課,一天編寫一個任務執行系統,客戶端和服務端架構
- golang運維開發專案之k8s網路探測實戰
- golang運維平臺實戰,服務樹,日誌監控,任務執行,分散式探測
- golang運維開發實戰課程之k8s巡檢平臺
直播答疑sre職業發展規劃
官方排程框架文件地址
01 預設排程器何時 根據pod的容器 資源request量挑選節點
- 預設排程器的原始碼位置 D:\go_path\src\github.com\kubernetes\kubernetes\pkg\scheduler\scheduler.go
- 由排程 一個pod的方法入口 ,其中sched.Algorithm.Schedule代表演算法排程
func (sched *Scheduler) scheduleOne(ctx context.Context) {
scheduleResult, err := sched.Algorithm.Schedule(schedulingCycleCtx, sched.Extenders, fwk, state, pod)
}
分析 Schedule方法
- 預設排程Schedule方法的原始碼位置 D:\go_path\src\github.com\kubernetes\kubernetes\pkg\scheduler\generic_scheduler.go
從它的方法註釋可以看到
// Schedule tries to schedule the given pod to one of the nodes in the node list. // If it succeeds, it will return the name of the node. // If it fails, it will return a FitError error with reasons.
- 翻譯過來就是Schedule方法 嘗試從給出的節點列表中選擇一個排程這個pod
- 如果成功,會返回節點的名稱
- 如果失敗,會返回錯誤
來分析一下 這個方法的返回值
- 這個ScheduleResult結構體他的欄位定義的很清晰一看就知道幹啥的
(result ScheduleResult, err error)
type ScheduleResult struct {
// Name of the scheduler suggest host
SuggestedHost string 結果節點
// Number of nodes scheduler evaluated on one pod scheduled
EvaluatedNodes int 參與計算的節點數
// Number of feasible nodes on one pod scheduled
FeasibleNodes int 合適的節點數
}
再分析一下這個方法的 引數
- (ctx context.Context, extenders []framework.Extender, fwk framework.Framework, state framework.CycleState, pod v1.Pod)
- ctx 上下文
- extenders 應該是擴充套件的排程外掛?
- fwk為內建的排程框架物件
- state應該是 排程的結果快取
- pod就是待排程的目標pod
其中核心的內容就是 findNodesThatFitPod
- 程式碼如 feasibleNodes, diagnosis, err := g.findNodesThatFitPod(ctx, extenders, fwk, state, pod)
- findNodesThatFitPod 就是執行filter外掛列表中的外掛
step01 執行prefilter外掛們
// Run "prefilter" plugins.
s := fwk.RunPreFilterPlugins(ctx, state, pod)
allNodes, err := g.nodeInfoSnapshot.NodeInfos().List()
if err != nil {
return nil, diagnosis, err
}
遍歷執行的程式碼如下
func (f *frameworkImpl) RunPreFilterPlugins(ctx context.Context, state *framework.CycleState, pod *v1.Pod) (status *framework.Status) { startTime := time.Now() defer func() { metrics.FrameworkExtensionPointDuration.WithLabelValues(preFilter, status.Code().String(), f.profileName).Observe(metrics.SinceInSeconds(startTime)) }() for _, pl := range f.preFilterPlugins { status = f.runPreFilterPlugin(ctx, pl, state, pod) if !status.IsSuccess() { status.SetFailedPlugin(pl.Name()) if status.IsUnschedulable() { return status } return framework.AsStatus(fmt.Errorf("running PreFilter plugin %q: %w", pl.Name(), status.AsError())).WithFailedPlugin(pl.Name()) } } return nil }
- 核心就是執行 各個 PreFilterPlugin的 PreFilter方法
type PreFilterPlugin interface {
Plugin
// PreFilter is called at the beginning of the scheduling cycle. All PreFilter
// plugins must return success or the pod will be rejected.
PreFilter(ctx context.Context, state *CycleState, p *v1.Pod) *Status
// PreFilterExtensions returns a PreFilterExtensions interface if the plugin implements one,
// or nil if it does not. A Pre-filter plugin can provide extensions to incrementally
// modify its pre-processed info. The framework guarantees that the extensions
// AddPod/RemovePod will only be called after PreFilter, possibly on a cloned
// CycleState, and may call those functions more than once before calling
// Filter again on a specific node.
PreFilterExtensions() PreFilterExtensions
}
預設的PreFilterPlugin都有哪些呢
- 我們可以在官方文件中 搜尋 prefilter
- 發現有8個 比如 NodePorts、NodeResourcesFit、VolumeBinding等
- 這跟我們在ide中檢視 PreFilter的實現者基本能對上
挑1個 NodeResourcesFit的 PreFilterPlugin 來看下
- 位置 D:\go_path\src\github.com\kubernetes\kubernetes\pkg\scheduler\framework\plugins\noderesources\fit.go
func (f *Fit) PreFilter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod) *framework.Status {
cycleState.Write(preFilterStateKey, computePodResourceRequest(pod, f.enablePodOverhead))
return nil
}
- 從上面的方法來看 只是計算了pod 的資源情況,寫入快取 為後面的過濾做準備
- 其中的資料統計來自 computePodResourceRequest,我們不用看具體程式碼,看註釋就能清楚這個方法的含義
- 從pod 的init和app容器中彙總,求最大的資源使用情況
- 其中init和app容器的處理方式不一致
- 比如註釋中給出的樣例,init容器按順序執行,那麼找其中最大的資源就可以 也就是 2c 3G
- app容器要求同時啟動,所以需要求sum 也就是 3c 3G
- 最後再求2者的max 也就是3c 3G
// computePodResourceRequest returns a framework.Resource that covers the largest
// width in each resource dimension. Because init-containers run sequentially, we collect
// the max in each dimension iteratively. In contrast, we sum the resource vectors for
// regular containers since they run simultaneously.
//
// If Pod Overhead is specified and the feature gate is set, the resources defined for Overhead
// are added to the calculated Resource request sum
//
// Example:
//
// Pod:
// InitContainers
// IC1:
// CPU: 2
// Memory: 1G
// IC2:
// CPU: 2
// Memory: 3G
// Containers
// C1:
// CPU: 2
// Memory: 1G
// C2:
// CPU: 1
// Memory: 1G
//
// Result: CPU: 3, Memory: 3G
看到這裡就會疑惑了,fit 的prefilter 中並沒有過濾節點資源的程式碼
- 其實相關的邏輯在 filter外掛中
- 因為在 findNodesThatFitPod函式中執行完 所有prefilter外掛後該執行 filter外掛了
- 也就是 NodeResourcesFit 的filter函式
- 位置 D:\go_path\src\github.com\kubernetes\kubernetes\pkg\scheduler\framework\plugins\noderesources\fit.go
// Filter invoked at the filter extension point.
// Checks if a node has sufficient resources, such as cpu, memory, gpu, opaque int resources etc to run a pod.
// It returns a list of insufficient resources, if empty, then the node has all the resources requested by the pod.
func (f *Fit) Filter(ctx context.Context, cycleState *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo) *framework.Status {
s, err := getPreFilterState(cycleState)
if err != nil {
return framework.AsStatus(err)
}
insufficientResources := fitsRequest(s, nodeInfo, f.ignoredResources, f.ignoredResourceGroups)
if len(insufficientResources) != 0 {
// We will keep all failure reasons.
failureReasons := make([]string, 0, len(insufficientResources))
for _, r := range insufficientResources {
failureReasons = append(failureReasons, r.Reason)
}
return framework.NewStatus(framework.Unschedulable, failureReasons...)
}
return nil
}
從上面的註釋就可以看出,這個是檢查一個節點 是否具備滿足 目標pod申請資源的
- 其中具體的資源計算邏輯在 fitsRequest中
以計算cpu為例
if podRequest.MilliCPU > (nodeInfo.Allocatable.MilliCPU - nodeInfo.Requested.MilliCPU) { insufficientResources = append(insufficientResources, InsufficientResource{ v1.ResourceCPU, "Insufficient cpu", podRequest.MilliCPU, nodeInfo.Requested.MilliCPU, nodeInfo.Allocatable.MilliCPU, }) }
思考如果上面有多個節點滿足 pod 資源request怎麼辦
- 其實很簡單就是: findNodesThatPassFilters有多個node 結果
然後交給後面的 score 方法打分計算挑選即可
feasibleNodes, err := g.findNodesThatPassFilters(ctx, fwk, state, pod, diagnosis, allNodes) if err != nil { return nil, diagnosis, err }
總結
- NodeResourcesFit的 PreFilterPlugin 負責計算pod 的資源申請值,並且計算時處理init和app容器有所區別
- k8s的預設排程器是在哪個環節過濾滿足這個pod資源的節點的:答案是NodeResourcesFit的Filter函式
- filter如果返回多個節點,那麼交給 score外掛打分計算挑選即可
腦洞
- 如果使用k8s的排程框架寫個擴充套件排程器,只實現Filter方法根據 節點的真實負載過濾那麼會有什麼問題
- 答案是:因為跳過了預設的NodeResourcesFit 可能會導致 被kubelet 的admit攔截 出現OutOfMemory等錯誤
- 因為 kubelet還是會校驗 新pod的request和本節點已分配的資源