1. 程式人生 > >圖解kubernetes排程器ScheduleAlgorithm核心實現學習框架設計

圖解kubernetes排程器ScheduleAlgorithm核心實現學習框架設計

ScheduleAlgorithm是一個介面負責為pod選擇一個合適的node節點,本節主要解析如何實現一個可擴充套件、可配置的通用演算法框架來實現通用排程,如何進行演算法的統一註冊和構建,如何進行metadata和排程流程上下文資料的傳遞

1. 設計思考

1.1 排程設計

1.1.1 排程與搶佔


當接收到pod需要被排程後,預設首先呼叫schedule來進行正常的業務排程嘗試從當前叢集中選擇一個合適的node

如果排程失敗則嘗試搶佔排程,根據優先順序搶佔低優先順序的pod執行高優先順序pod

1.1.2 排程階段


在k8s的排程演算法執行流程中,主要分為兩個階段:預選和優選,即從當前叢集中選擇符合要求的node,再從這些node中選擇最合適的節點

1.1.3 節點選擇

隨著叢集的增加叢集中的node數量越來越多,k8s並不是遍歷所有叢集資源,而是隻選取部分節點,同時藉助之前說的 schedulerCache來實現pod節點的分散

1.2 框架設計

1.2.1 登錄檔與演算法工廠

針對不同的演算法,宣告不同的登錄檔,負責叢集中當前所有演算法的註冊,從而提供給排程配置決策載入那些外掛,實現演算法的可擴充套件性

並通過工廠模式來進行統一管理,解耦演算法的註冊與具體排程流程中的使用,由每個演算法的工廠方法來接受引數進行具體演算法的建立

1.2.3 metadata與PluginContext


在排程實際執行的過程中,需要集合當前叢集中的元資料資訊(node和pod)來進行具體演算法的決策,scheduler採用PredicateMetadataProducer和PriorityMetadataProducer來進行元資料的構建, 其次針對一些可能被多個演算法都使用的資料,也會在這裡完成構建,比如親和性的pod、拓撲等

並通過PluginContext進行本次排程上下文資料的儲存,用於在多個排程演算法之間儲存資料進行互動

1.2.4  Provider

Provider主要是封裝一組具體的預選和優選演算法,並通過註冊來實現統一管理, 其中系統內建了DefaultProvider

1.2.5 framework

framework是一種內部的擴充套件機制,通過定製給定的階段函式,進行排程流程的影響,本節先不介紹

1.2.6 extender

一種外部的擴充套件機制,可以根據需要進行動態的配置,其實就是外部的一個service,但是相比framework可以使用自己獨立的資料儲存,實現對排程器的擴充套件

2. 原始碼分析

2.1 資料結構

type genericScheduler struct {
    cache                    internalcache.Cache
    schedulingQueue          internalqueue.SchedulingQueue
    predicates               map[string]predicates.FitPredicate
    priorityMetaProducer     priorities.PriorityMetadataProducer
    predicateMetaProducer    predicates.PredicateMetadataProducer
    prioritizers             []priorities.PriorityConfig
    framework                framework.Framework
    extenders                []algorithm.SchedulerExtender
    alwaysCheckAllPredicates bool
    nodeInfoSnapshot         *schedulernodeinfo.Snapshot
    volumeBinder             *volumebinder.VolumeBinder
    pvcLister                corelisters.PersistentVolumeClaimLister
    pdbLister                algorithm.PDBLister
    disablePreemption        bool
    percentageOfNodesToScore int32
    enableNonPreempting      bool
}

2.1.1 叢集資料

叢集元資料主要分為三部分:
Cache: 儲存從apiserver獲取的資料 
SchedulingQueue: 儲存當前佇列中等待排程和經過排程但是未真正執行的pod

    cache                    internalcache.Cache
    schedulingQueue          internalqueue.SchedulingQueue
    nodeInfoSnapshot         *schedulernodeinfo.Snapshot

2.1.1 預選演算法相關

預選演算法主要包含兩部分:當前使用的預選排程演算法結合和元資料構建器

    predicates               map[string]predicates.FitPredicate
    predicateMetaProducer    predicates.PredicateMetadataProducer

2.1.3 優先順序演算法相關

優選演算法與預選演算法不太相同,在後續文章中會進行介紹

    priorityMetaProducer     priorities.PriorityMetadataProducer
    prioritizers             []priorities.PriorityConfig

2.1.4 擴充套件相關

    framework                framework.Framework
    extenders                []algorithm.SchedulerExtender

2.2 排程演算法登錄檔


Priority會複雜一點,這裡就不介紹了,其核心設計都是一樣的

2.2.1 工廠登錄檔

fitPredicateMap        = make(map[string]FitPredicateFactory)

2.2.2 登錄檔註冊

註冊主要分兩類:如果後續演算法不會使用當前Args裡面的資料,只需要使用metadata裡面的,就直接返回註冊演算法,下面的函式就是返回一個工廠方法,但是不會使用Args引數

func RegisterFitPredicate(name string, predicate predicates.FitPredicate) string {
    return RegisterFitPredicateFactory(name, func(PluginFactoryArgs) predicates.FitPredicate { return predicate })
}

最終註冊都是通過下面的工廠註冊函式實現,通過mutex和map實現

func RegisterFitPredicateFactory(name string, predicateFactory FitPredicateFactory) string {
    schedulerFactoryMutex.Lock()
    defer schedulerFactoryMutex.Unlock()
    validateAlgorithmNameOrDie(name)
    fitPredicateMap[name] = predicateFactory
    return name
}

2.2.3 生成預選演算法

通過外掛工廠引數影響和Factory構建具體的預選演算法,上面構建的工廠方法,下面則給定引數,通過工廠方法利用閉包的方式來進行真正演算法的生成

func getFitPredicateFunctions(names sets.String, args PluginFactoryArgs) (map[string]predicates.FitPredicate, error) {
    schedulerFactoryMutex.RLock()
    defer schedulerFactoryMutex.RUnlock()

    fitPredicates := map[string]predicates.FitPredicate{}
    for _, name := range names.List() {
        factory, ok := fitPredicateMap[name]
        if !ok {
            return nil, fmt.Errorf("invalid predicate name %q specified - no corresponding function found", name)
        }
        fitPredicates[name] = factory(args)
    }

    // k8s中預設包含一些強制性的策略,不允許使用者自己進行刪除,這裡是載入這些引數
    for name := range mandatoryFitPredicates {
        if factory, found := fitPredicateMap[name]; found {
            fitPredicates[name] = factory(args)
        }
    }

    return fitPredicates, nil
}

2.2.4 根據當前feature進行演算法刪除

當我們在系統演進的時候,也可以借鑑這種思想,來避免使用者使用那些當前或者未來版本中可能逐漸被放棄的設計

if utilfeature.DefaultFeatureGate.Enabled(features.TaintNodesByCondition) {
        // Remove "CheckNodeCondition", "CheckNodeMemoryPressure", "CheckNodePIDPressure"
        // and "CheckNodeDiskPressure" predicates
        factory.RemoveFitPredicate(predicates.CheckNodeConditionPred)
        factory.RemoveFitPredicate(predicates.CheckNodeMemoryPressurePred)
    }

2.3 predicateMetadataProducer

2.3.1 PredicateMetadata

// PredicateMetadata interface represents anything that can access a predicate metadata.
type PredicateMetadata interface {
    ShallowCopy() PredicateMetadata
    AddPod(addedPod *v1.Pod, nodeInfo *schedulernodeinfo.NodeInfo) error
    RemovePod(deletedPod *v1.Pod, node *v1.Node) error
}

2.3.2 宣告

predicateMetadataProducer PredicateMetadataProducerFactory

工廠函式

// PredicateMetadataProducerFactory produces PredicateMetadataProducer from the given args.
type PredicateMetadataProducerFactory func(PluginFactoryArgs) predicates.PredicateMetadataProducer

PredicateMetadataProducer通過上面的工廠函式建立而來,其接受當前需要排程的pod和snapshot裡面的node資訊,從而構建當前的PredicateMetadata


// PredicateMetadataProducer is a function that computes predicate metadata for a given pod.
type PredicateMetadataProducer func(pod *v1.Pod, nodeNameToInfo map[string]*schedulernodeinfo.NodeInfo) PredicateMetadata

2.3.2 註冊

// RegisterPredicateMetadataProducerFactory registers a PredicateMetadataProducerFactory.
func RegisterPredicateMetadataProducerFactory(factory PredicateMetadataProducerFactory) {
    schedulerFactoryMutex.Lock()
    defer schedulerFactoryMutex.Unlock()
    predicateMetadataProducer = factory
}

2.3.4 意義

PredicateMetadata其本質上就是當前系統中的元資料,其設計的主要目標是為了當前的排程流程中後續多個排程演算法中都可能需要計算的資料,進行統一的計算,比如節點的親和性、反親和、拓撲分佈等,都在此進行統一的控制, 當前版本的實現時PredicateMetadataFactory,這裡不進行展開

2.4 Provider

2.4.1 AlgorithmProviderConfig

// AlgorithmProviderConfig is used to store the configuration of algorithm providers.
type AlgorithmProviderConfig struct {
    FitPredicateKeys     sets.String
    PriorityFunctionKeys sets.String
}

2.4.2 註冊中心

algorithmProviderMap   = make(map[string]AlgorithmProviderConfig)

2.4.3 註冊


func RegisterAlgorithmProvider(name string, predicateKeys, priorityKeys sets.String) string {
    schedulerFactoryMutex.Lock()
    defer schedulerFactoryMutex.Unlock()
    validateAlgorithmNameOrDie(name)
    algorithmProviderMap[name] = AlgorithmProviderConfig{
        FitPredicateKeys:     predicateKeys,
        PriorityFunctionKeys: priorityKeys,
    }
    return name
}

2.4.4 預設Provider註冊

func init() {
    // 註冊演算法DefaulrProvider 的演算法provider
    registerAlgorithmProvider(defaultPredicates(), defaultPriorities())
}

 

2.5 核心排程流程


核心排程流程,這裡面只介紹主線的流程,至於怎麼預選和優選則在下一篇文章進行更新,因為稍微有點複雜,而framework和extender則在後續介紹完這兩部分在進行介紹, 其中extender的呼叫則是在PrioritizeNodes進行優先順序算中進行呼叫

// 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.
func (g *genericScheduler) Schedule(pod *v1.Pod, pluginContext *framework.PluginContext) (result ScheduleResult, err error) {
    // 省略非核心程式碼
    // 呼叫framework的RunPreFilterPlugins
    preFilterStatus := g.framework.RunPreFilterPlugins(pluginContext, pod)
    if !preFilterStatus.IsSuccess() {
        return result, preFilterStatus.AsError()
    }

    // 獲取當前的node數量
    numNodes := g.cache.NodeTree().NumNodes()
    if numNodes == 0 {
        return result, ErrNoNodesAvailable
    }

    // 更新snapshot
    if err := g.snapshot(); err != nil {
        return result, err
    }
    // 預選階段
    filteredNodes, failedPredicateMap, filteredNodesStatuses, err := g.findNodesThatFit(pluginContext, pod)
    if err != nil {
        return result, err
    }

    // 將預選結果呼叫framework的postfilter
    postfilterStatus := g.framework.RunPostFilterPlugins(pluginContext, pod, filteredNodes, filteredNodesStatuses)
    if !postfilterStatus.IsSuccess() {
        return result, postfilterStatus.AsError()
    }

    if len(filteredNodes) == 0 {
        return result, &FitError{
            Pod:                   pod,
            NumAllNodes:           numNodes,e
            FailedPredicates:      failedPredicateMap,
            FilteredNodesStatuses: filteredNodesStatuses,
        }
    }

    startPriorityEvalTime := time.Now()
    // 如果只有一個節點則直接返回
    if len(filteredNodes) == 1 {
        return ScheduleResult{
            SuggestedHost:  filteredNodes[0].Name,
            EvaluatedNodes: 1 + len(failedPredicateMap),
            FeasibleNodes:  1,
        }, nil
    }

    // 獲取所有的排程策略
    metaPrioritiesInterface := g.priorityMetaProducer(pod, g.nodeInfoSnapshot.NodeInfoMap)
    // 獲取所有node的優先順序,此處會將extenders進行傳入,實現擴充套件介面的呼叫
    priorityList, err := PrioritizeNodes(pod, g.nodeInfoSnapshot.NodeInfoMap, metaPrioritiesInterface, g.prioritizers, filteredNodes, g.extenders, g.framework, pluginContext)
    if err != nil {
        return result, err
    }
    // 從優先順序中選擇出合適的node
    host, err := g.selectHost(priorityList)
    trace.Step("Selecting host done")
    return ScheduleResult{
        SuggestedHost:  host,
        EvaluatedNodes: len(filteredNodes) + len(failedPredicateMap),
        FeasibleNodes:  len(filteredNodes),
    }, err
}

3. 設計總結


在排程演算法框架中大量使用了工廠方法來進行演算法、元資料等的構建,並通過封裝MetadataProducer來進行公共業務邏輯介面的封裝,通過PluginContext進行排程流程中上下文資料的傳遞,並且使用者可以通過定製Provider來進行具體排程演算法的選擇

本文只介紹了大的框架設計,諸如具體的演算法註冊和構建其大多都是在構建scheduler命令列引數處通過載入對應的包和init函式來實現,本文沒有介紹一些具體的細節連搶佔也沒有介紹,後續文章裡面會進行一一展開,感興趣的同學,歡迎一起學習交流

微訊號:baxiaoshi2020
關注公告號閱讀更多原始碼分析文章
更多文章關注 www.sreguide.com
本文由部落格一文多發平臺 OpenWrite 釋出