kube-schedulerソース分析

21017 ワード

kubernetesクラスタの3ステップインストール
kube-schedulerソース分析
ソースコンパイルについて
私は公式に提供されたコンパイルスクリプトが面倒であることを嫌って、k 8 sコードをより簡単で乱暴な方法でコンパイルしました.もちろん、公式スクリプトはすべてのプロジェクトをコンパイルしたり、プラットフォームのコンパイルやrealseを誇張したりするときに役立ちます.
コンテナでコンパイル:
docker run -v /work/src/k8s.io/kubernetes:/go/src/k8s.io/kubernetes golang:1.11.2 bash

容器の中で環境がきれいであることを保証することができます
bashに入ってからkube-schedulerのホームディレクトリに直接入ってコンパイルすればいいです.
cd cmd/kube-scheduler && go build

バイナリが生まれました...
ソースコードコンパイルアクセスCI/CD
ハイエンドプレイヤーとして、自動化は必要です.サーバーの性能がもっと良いので、CI/CDでコンパイルするのがもっと速いです.ここで私のいくつかの構成を共有します.
  • コンパイルされたベースミラーにvendorを入れました.vendorが大きく、
  • を頻繁に更新しないからです.
    $ cat Dockerfile-build1.12.2
    FROM golang:1.11.2
    COPY vendor/ /vendor
    

    そしてコードのvendorは削除できます
  • .drone.yml
  • workspace:
      base: /go/src/k8s.io
      path: kubernetes
    
    pipeline:
        build:
            image: fanux/kubernetes-build:1.12.2-beta.3
            commands:
               - make all WHAT=cmd/kube-kubescheduler GOFLAGS=-v
        publish:
            image: plugins/docker
            registry: xxx
            username: xxx
            password: xxx
            email: xxx
            repo: xxx/container/kube-scheduler
            tags: ${DRONE_TAG=latest}
            dockerfile: dockerfile/Dockerfile-kube-scheduler
            insecure: true
            when:
                event: [push, tag]
    
  • Dockerfile静的コンパイルベースミラーも
  • 節約
    $ cat dockerfile/Dockerfile-kube-scheduler
    FROM scratch
    COPY  _output/local/bin/linux/amd64/kube-scheduler /
    CMD ["/kube-scheduler"]
    

    kubeadmというバイナリ配信の場合、直接コンパイルしてnexusに転送し、drone deployイベントでkubeadmをコンパイルするかどうかを選択します.
        build_kubeadm:
            image: fanux/kubernetes-build:1.12.2-beta.3
            commands:
               - make all WHAT=cmd/kube-kubeadm GOFLAGS=-v
               - curl -v -u container:container --upload-file kubeadm http://172.16.59.153:8081/repository/kubernetes/kubeadm/
            when:
                event: deployment
                enviroment: kubeadm
    

    ダイレクトgo buildの大きな穴
    buildが完了したkubeadmバイナリは使用できないことがわかりました.buildで選択したベースミラーの問題かもしれません.コードを生成しなかった問題かもしれません.
    [signal SIGSEGV: segmentation violation code=0x1 addr=0x63 pc=0x7f2b7f5f057c]
    
    runtime stack:
    runtime.throw(0x17c74a8, 0x2a)
        /usr/local/go/src/runtime/panic.go:608 +0x72
    runtime.sigpanic()
        /usr/local/go/src/runtime/signal_unix.go:374 +0x2f2
    

    あとでCDのセットを追加します
    このようにschedulerコードをコンパイルするのは約40秒ぐらいで、vendorがソフト接続できるように十数秒節約できます.
    スケジューラcache
    Cacheステータスマシン
       +-------------------------------------------+  +----+
       |                            Add            |  |    |
       |                                           |  |    | Update
       +      Assume                Add            v  v    |
    Initial +--------> Assumed +------------+---> Added  Expired   +----> Deleted
    
  • Assumeがスケジューリングを試みると、pod requireのCPUメモリがどれだけあるかなどのnode情報がnodeに集約され、nodeに加算され、タイムアウトしたら
  • を再削減する必要がある.
  • AddPodは、そのpodがスケジュールされているかどうかを検出し、期限が切れているかどうかを確認し、期限が切れている場合は
  • が追加されます.
  • Remove pod情報は、ノード上で消去される
  • .
  • cache node関連cacheインタフェースADD updateなどの他のインタフェース
  • .
    Cache実装
    type schedulerCache struct {
        stop   

    ここには、基本スケジューリングに必要なすべての情報が格納されています.
    AddPodインタフェースを例にとると、本質的には傍受されたpodをcacheのmapに入れることです.
    cache.addPod(pod)
    ps := &podState{
        pod: pod,
    }
    cache.podStates[key] = ps
    

    Node Treeノード情報には、次のような構造体が保存されます.
    type NodeTree struct {
        tree      map[string]*nodeArray // a map from zone (region-zone) to an array of nodes in the zone.
        zones     []string              // a list of all the zones in the tree (keys)
        zoneIndex int
        NumNodes  int
        mu        sync.RWMutex
    }
    

    Cache実行時に期限切れのassume podをループクリーンアップ
    func (cache *schedulerCache) run() {
        go wait.Until(cache.cleanupExpiredAssumedPods, cache.period, cache.stop)
    }
    

    scheduler
    schedulerの中で最も重要な2つのもの:cacheとスケジューリングアルゴリズム
    type Scheduler struct {
        config *Config  -------> SchedulerCache
                           |
                           +---> Algorithm
    }
    

    cacheが更新されると、スケジューラはpodをスケジューリングします.
    func (sched *Scheduler) Run() {
        if !sched.config.WaitForCacheSync() {
            return
        }
    
        go wait.Until(sched.scheduleOne, 0, sched.config.StopEverything)
    }
    

    コアロジックが来ました:
       +-------------+
       |     pod |
       +-------------+
              |
       +-----------------------------------------------------------------------------------+
       |   pod DeletionTimestamp          , kubelet            pod |
       +-----------------------------------------------------------------------------------+
              |
       +-----------------------------------------+
       |    suggestedHost,          |
       +-----------------------------------------+
              |_____________           ,     swarm       
              |
       +--------------------------------------------------------------------------------+
       |         node ,    cache pod      node  ,  assume pod  |
       |        volumes                                                          |
       |   :err = sched.assume(assumedPod, suggestedHost)   pod    node      |
       +--------------------------------------------------------------------------------+
              |
       +---------------------------+
       |    bind  pod node  |
       |  bind volume             |
       | bind pod                  |
       +---------------------------+
              |
       +----------------+
       |     metric |
       +----------------+
    

    bindアクション:
    err := sched.bind(assumedPod, &v1.Binding{
        ObjectMeta: metav1.ObjectMeta{Namespace: assumedPod.Namespace, Name: assumedPod.Name, UID: assumedPod.UID},
        Target: v1.ObjectReference{
            Kind: "Node",
            Name: suggestedHost,
        },
    })
    

    先にbind podに行って、それからcache bindに終わりを教えます
    err := sched.config.GetBinder(assumed).Bind(b)
    if err := sched.config.SchedulerCache.FinishBinding(assumed); 
    

    bindプロセス
       +----------------+
       | GetBinder.Bind
       +----------------+
           |
       +-------------------------------------+
       |   cache bind   FinishBinding  
       +-------------------------------------+
           |
       +-----------------------------------------------------+
       |     ForgetPod,     pod    BindingRejected
       +-----------------------------------------------------+
    

    bind実装
    最終的にapiserver bindインタフェースが呼び出されます.
    func (b *binder) Bind(binding *v1.Binding) error {
        glog.V(3).Infof("Attempting to bind %v to %v", binding.Name, binding.Target.Name)
        return b.Client.CoreV1().Pods(binding.Namespace).Bind(binding)
    }
    

    スヶジューリングアルゴリズム
    ▾ algorithm/
      ▸ predicates/    
      ▸ priorities/    
    

    今最も重要なのはノード選択の実装です
    suggestedHost, err := sched.schedule(pod)
    

    つまり、スケジューリングアルゴリズムの実装:
    type ScheduleAlgorithm interface {
        //   pod     ,         
        Schedule(*v1.Pod, NodeLister) (selectedMachine string, err error)
        //       
        Preempt(*v1.Pod, NodeLister, error) (selectedNode *v1.Node, preemptedPods []*v1.Pod, cleanupNominatedPods []*v1.Pod, err error)
    
        //      ,
        Predicates() map[string]FitPredicate
                                    |                                            pod,         
                                    +-------type FitPredicate func(pod *v1.Pod, meta PredicateMetadata, nodeInfo *schedulercache.NodeInfo) (bool, []PredicateFailureReason, error)
        //       ,        map   reduce
        Prioritizers() []PriorityConfig
                             |____________PriorityMapFunction          
                             |____________PriorityReduceFunction   map       node     
                             |____________PriorityFunction   
    }
    

    スケジューリングアルゴリズムは、次の2つの方法で生成できます.
  • Providerデフォルト、汎用スケジューラ
  • Policyポリシー方式、特殊スケジューラ
  • 最終的にはschedulerが
    priorityConfigs, err := c.GetPriorityFunctionConfigs(priorityKeys)
    priorityMetaProducer, err := c.GetPriorityMetadataProducer()
    predicateMetaProducer, err := c.GetPredicateMetadataProducer()
                                                  |
    algo := core.NewGenericScheduler(             |
        c.schedulerCache,                         |
        c.equivalencePodCache,                    V
        c.podQueue,
        predicateFuncs,   ============>                 
        predicateMetaProducer,
        priorityConfigs,
        priorityMetaProducer,
        extenders,
        c.volumeBinder,
        c.pVCLister,
        c.alwaysCheckAllPredicates,
        c.disablePreemption,
        c.percentageOfNodesToScore,
    )
    
    
    type genericScheduler struct {
        cache                    schedulercache.Cache
        equivalenceCache         *equivalence.Cache
        schedulingQueue          SchedulingQueue
        predicates               map[string]algorithm.FitPredicate
        priorityMetaProducer     algorithm.PriorityMetadataProducer
        predicateMetaProducer    algorithm.PredicateMetadataProducer
        prioritizers             []algorithm.PriorityConfig
        extenders                []algorithm.SchedulerExtender
        lastNodeIndex            uint64
        alwaysCheckAllPredicates bool
        cachedNodeInfoMap        map[string]*schedulercache.NodeInfo
        volumeBinder             *volumebinder.VolumeBinder
        pvcLister                corelisters.PersistentVolumeClaimLister
        disablePreemption        bool
        percentageOfNodesToScore int32
    }
    

    このschedulerはScheduleAlgorithmで定義されたインタフェースを実現した.
    Scheduleプロセス:
       +------------------------------------+
       | trace    ,       pod  | 
       +------------------------------------+
              |
       +-----------------------------------------------+
       | pod    ,        delete timestamp |
       +-----------------------------------------------+
              |
       +----------------------------------------+
       |   node  ,   cache node info map |
       +----------------------------------------+
              |
       +----------------------------------------------+
       |   ,                    |
       +----------------------------------------------+
              |
       +----------------------------------------------------------+
       |   ,                                                   |
       |             ,       ,        |
       |                                                  |
       +----------------------------------------------------------+
              |
       +------------------------------------+
       |                   |
       +------------------------------------+
    

    せんたく
    主に二つに分ける
  • の予選で、ノードが
  • に合致するかどうかを確認します.
  • extender、カスタムスケジューラ拡張を実行し、HTTP extenderが予選結果をユーザーに送ることを公式に実現し、ユーザーは
  • をフィルタリングする.
    podFitOnNode:このノードがこのpodスケジューリングに適しているかどうかを判断する
    ここには小さな知識が挿入されています.スケジューラにはEcacheがあります.
    Equivalence Classは現在、Kubernetes SchedulerでPredicateを加速させ、Schedulerのスループット性能を向上させるために使用されています.Kubernetes schedulerはEquivalence Cacheのデータをタイムリーに維持しており、場合によってはdelete node、bind podなどのイベントが発生すると、すぐにinvalid関連のEquivalence Cacheのキャッシュデータが必要になります.
    1つのEquivalence Classは、同じRequirementsとConstraintsを持つPodsの関連情報のセットを定義するために使用され、SchedulerがPredicateフェーズを行う場合、Equivalence Classの1つのPodに対してPredicateを行い、Predicateの結果をEquivalence Cacheに配置してEquivalence Classの他のPods(Equivalent Podsとなる)結果を再利用します.通常のPredicateプロセスは、Equivalence Cacheに再利用可能なPredicate Resultがない場合にのみ実行されます.
    ecacheという後続は深く議論することができ、本稿ではコアアーキテクチャとプロセスに注目する.
    すべての予選関数を1回実行するのは簡単です
        predicates.Ordering() 
    if predicate, exist := predicateFuncs[predicateKey]; exist {
            fit, reasons, err = predicate(pod, metaToUse, nodeInfoToUse)
    

    順序は次のとおりです.
        predicatesOrdering = []string{CheckNodeConditionPred, CheckNodeUnschedulablePred,
            GeneralPred, HostNamePred, PodFitsHostPortsPred,
            MatchNodeSelectorPred, PodFitsResourcesPred, NoDiskConflictPred,
            PodToleratesNodeTaintsPred, PodToleratesNodeNoExecuteTaintsPred, CheckNodeLabelPresencePred,
            CheckServiceAffinityPred, MaxEBSVolumeCountPred, MaxGCEPDVolumeCountPred, MaxCSIVolumeCountPred,
            MaxAzureDiskVolumeCountPred, CheckVolumeBindingPred, NoVolumeZoneConflictPred,
            CheckNodeMemoryPressurePred, CheckNodePIDPressurePred, CheckNodeDiskPressurePred, MatchInterPodAffinityPred}
    

    これらの予選関数はmapに存在し、keyはstringであり、valueは予選関数であり、mapを登録する論理を振り返る.
    predicateFuncs, err := c.GetPredicates(predicateKeys)
    

    pkg/scheduler/algorithmprovider/defaults/defaults.goには、次のような関数が登録されています.
    factory.RegisterFitPredicate(predicates.NoDiskConflictPred, predicates.NoDiskConflict),
    factory.RegisterFitPredicate(predicates.GeneralPred, predicates.GeneralPredicates),
    factory.RegisterFitPredicate(predicates.CheckNodeMemoryPressurePred, predicates.CheckNodeMemoryPressurePredicate),
    factory.RegisterFitPredicate(predicates.CheckNodeDiskPressurePred, predicates.CheckNodeDiskPressurePredicate),
    factory.RegisterFitPredicate(predicates.CheckNodePIDPressurePred, predicates.CheckNodePIDPressurePredicate),
    

    そしてinit関数で登録ロジックを直接呼び出します
    好ましい
    PrioritizeNodesは、3つのステップに分けられることが好ましい.
  • Mapは単一ノードを計算し、優先度
  • Reduce各ノードの結果集約を計算し、全ノードの最終スコア
  • を計算する.
  • Extenderと予選差が少ない
  • 好ましい関数も同様に登録されており,これ以上述べることはない
    factory.RegisterPriorityFunction2("LeastRequestedPriority", priorities.LeastRequestedPriorityMap, nil, 1),
    // Prioritizes nodes to help achieve balanced resource usage
    factory.RegisterPriorityFunction2("BalancedResourceAllocation", priorities.BalancedResourceAllocationMap, nil, 1),
    

    ここで登録する時に2つ登録して、1つのmap関数1つのreduce関数、もっと良い理解のmapreduceのために、1つの実現を見に行きます
    factory.RegisterPriorityFunction2("NodeAffinityPriority", priorities.CalculateNodeAffinityPriorityMap, priorities.CalculateNodeAffinityPriorityReduce, 1)
    

    node Affinity map reduce
    mapコアロジック、比較的に理解しやすい:
            ,     
    count += preferredSchedulingTerm.Weight
    
    return schedulerapi.HostPriority{
        Host:  node.Name,
        Score: int(count),  #     
    }, nil
    

    reduce:1つのノードは多くのmapを歩き、各mapはnode affinityが1つ生成され、pod affinityがもう1つ生成されるので、nodeとスコアは1対多の関係です.
    reverseの論理を削除(スコアが高いほど優先度が低い)
    var maxCount int
    for i := range result {
        if result[i].Score > maxCount {
            maxCount = result[i].Score  #          
        }
    }
    
    for i := range result {
        score := result[i].Score
        score = maxPriority * score / maxCount  #           maxPriority = 10,                     ;
        result[i].Score = score
    }
    

    ここで正規化処理をすると点数が[0,maxPriority]の間になります
    for i := range priorityConfigs {
        if priorityConfigs[i].Function != nil {
            continue
        }
        results[i][index], err = priorityConfigs[i].Map(pod, meta, nodeInfo)
        if err != nil {
            appendError(err)
            results[i][index].Host = nodes[index].Name
        }
    }
    
    err := config.Reduce(pod, meta, nodeNameToInfo, results[index]); 
    

    ここにresultsがあります.理解にとって重要な2次元配列です.
    xxx
    node1
    node2
    node3
    nodeaffinity
    1点
    2点
    1点
    pod affinity
    1点
    3点
    6点
    ...
    ...
    ...
    ...
    このようにreduceで1行をとると,実際にはすべてのノードの得点を処理する.
    result[i].Score += results[j][i].Score * priorityConfigs[j].Weight  (     )
    
    

    reduceが最終的にこのノードの得点を完了すると、このノードの各得点にその重みを乗じた和に等しくなり、最後に最高点(1次元が0緯になる)をソートします.
    スケジューリングキューSchedulingQueue
    scheduler構成にはNextPodメソッドがあり、podを取得し、スケジューリングを行います.
    pod := sched.config.NextPod()
    

    プロファイルはここで初期化されます.
    pkg/scheduler/factory/factory.go
    NextPod: func() *v1.Pod {
        return c.getNextPod()
    },
    
    func (c *configFactory) getNextPod() *v1.Pod {
        pod, err := c.podQueue.Pop()
        if err == nil {
            return pod
        }
    ...
    }
    

    キューインタフェース:
    type SchedulingQueue interface {
        Add(pod *v1.Pod) error
        AddIfNotPresent(pod *v1.Pod) error
        AddUnschedulableIfNotPresent(pod *v1.Pod) error
        Pop() (*v1.Pod, error)
        Update(oldPod, newPod *v1.Pod) error
        Delete(pod *v1.Pod) error
        MoveAllToActiveQueue()
        AssignedPodAdded(pod *v1.Pod)
        AssignedPodUpdated(pod *v1.Pod)
        WaitingPodsForNode(nodeName string) []*v1.Pod
        WaitingPods() []*v1.Pod
    }
    

    優先キューとFIFOの2つのインプリメンテーションが与えられました.
    func NewSchedulingQueue() SchedulingQueue {
        if util.PodPriorityEnabled() {
            return NewPriorityQueue()  #        ,       
        }
        return NewFIFO() #        
    }
    

    キューの実装は簡単で、深く分析しないで、もっと重要なのはキュー、スケジューラ、cacheの関係に注目することです.
    AddFunc:    c.addPodToCache,
    UpdateFunc: c.updatePodInCache,
    DeleteFunc: c.deletePodFromCache,
                | informer  , pod       cache        
                V 
    if err := c.schedulerCache.AddPod(pod); err != nil {
        glog.Errorf("scheduler cache AddPod failed: %v", err)
    }
    
    c.podQueue.AssignedPodAdded(pod)
    
    +------------+ ADD   +-------------+   POP  +-----------+
    | informer   |------>|  sche Queue |------->| scheduler |
    +------------+   |   +-------------+        +----^------+
                     +-->+-------------+             |
                         | sche cache  |

    Extender
    スケジューラ拡張
    カスタマイズスケジューラには3つの方法があります.
  • schedulerコードを変更して再コンパイル-
  • について議論することはありません.
  • スケジューラを書き換え、スケジューリング時にスケジューラを選択-比較的簡単で、問題はデフォルトのスケジューラと共同で
  • を使用できないことです.
  • 書き込みスケジューラ拡張(extender)k 8 sのスケジューリングを完了させ、該当するノードをフィルタリングし、最適化します.重点的に議論します.新しいバージョンはいくつかのアップグレードを行い、古い方法では資料が役に立たない可能性があります.
  • ここにスケジューラ拡張事例
  • があります
    現在、3つ目の資料は非常に少なく、多くの詳細はコードの中で答えを見つける必要があり、問題を持ってコードを見ると効果的です.
    Extenderインタフェース
    +----------------------------------+       +----------+
    | kube-scheduler -> extender client|------>| extender | (        ,     )
    +----------------------------------+       +----------+
    

    このインタフェースはkube-schedulerで実装されています.HTTPextenderの実装について説明します.
    type SchedulerExtender interface {
        //         ,  pod     ,             
        Filter(pod *v1.Pod,
            nodes []*v1.Node, nodeNameToInfo map[string]*schedulercache.NodeInfo,
        ) (filteredNodes []*v1.Node, failedNodesMap schedulerapi.FailedNodesMap, err error)
    
        //         ,       
        Prioritize(pod *v1.Pod, nodes []*v1.Node) (hostPriorities *schedulerapi.HostPriorityList, weight int, err error)
    
        // Bind                     extender
        Bind(binding *v1.Binding) error
    
        // IsBinder returns whether this extender is configured for the Bind method.
        IsBinder() bool
    
        //          pod       
        IsInterested(pod *v1.Pod) bool
    
        // ProcessPreemption returns nodes with their victim pods processed by extender based on
        // given:
        //   1. Pod to schedule
        //   2. Candidate nodes and victim pods (nodeToVictims) generated by previous scheduling process.
        //   3. nodeNameToInfo to restore v1.Node from node name if extender cache is enabled.
        // The possible changes made by extender may include:
        //   1. Subset of given candidate nodes after preemption phase of extender.
        //   2. A different set of victim pod for every given candidate node after preemption phase of extender.
        //             ,     TODO
        ProcessPreemption(
            pod *v1.Pod,
            nodeToVictims map[*v1.Node]*schedulerapi.Victims,
            nodeNameToInfo map[string]*schedulercache.NodeInfo,
        ) (map[*v1.Node]*schedulerapi.Victims, error)
    
        //        ,    
        SupportsPreemption() bool
    
        //      extender     ,    extender           
        IsIgnorable() bool
    }
    

    HTTPextenderが公式に実現されました.
    type HTTPExtender struct {
        extenderURL      string
        preemptVerb      string
        filterVerb       string  #   RUL
        prioritizeVerb   string  #   RUL
        bindVerb         string
        weight           int
        client           *http.Client
        nodeCacheCapable bool
        managedResources sets.String
        ignorable        bool
    }
    

    その予選と好ましい論理を見てください.
    args = &schedulerapi.ExtenderArgs{  #       pod,          ,           
        Pod:       pod,
        Nodes:     nodeList,
        NodeNames: nodeNames,
    }
    
    if err := h.send(h.filterVerb, args, &result); err != nil { #    http   extender(      httpserver),         
        return nil, nil, err
    }
    

    HTTPExtender構成パラメータはどこから
    scheduler extender構成:
    NamespaceSystem string = "kube-system"
    
    SchedulerDefaultLockObjectNamespace string = metav1.NamespaceSystem
    
    // SchedulerPolicyConfigMapKey defines the key of the element in the
    // scheduler's policy ConfigMap that contains scheduler's policy config.
    SchedulerPolicyConfigMapKey = "policy.cfg"
    

    まとめ
    スケジューラのコードはよく書けています.kube-proxyよりずっとよくなっています.拡張性もまあまあですが、目測スケジューラは大きな再構築に直面します.現段階では、スケジューラが深く学習したバッチタスクのサポートはよくありません.one by oneスケジューリングのこのような設定はプロジェクト全体のアーキテクチャに関係しています.より優れたスケジューリングを優雅にサポートするには、再構築は逃げられないだろう.
    スキャンコード注目sealyun
    検討可加QQ群:98488045