Kubergnetesスケジューラによる初探

8552 ワード

KubenetesスケジューラKubergnetesは、コンテナベースの分散スケジューラであり、自分のスケジューラを実現しています.Kubergnetesクラスタでは、スケジューラは独立したモジュールとしてpodを介して動作する.いくつかの面からKubenetesスケジューラを紹介します.
スケジューラの働き方Kubergnetesのスケジューラは、単独のコンポーネントとして動作し、一般的にはマスターの中で実行され、マスターの数と一致しています.Raftプロトコルを通じて、Leaderとして動作するインスタンスを選択し、他のインスタンスBackup.Masterが故障した場合、他の例の間で引き続きRaftプロトコルによって新しいMaster作業を選出する.その作業パターンは以下の通りです.
スケジューラ内部では、スケジューラのpodsキューpodQueを維持し、API Serverを傍受します.Podを作成すると、まずAPI Serverを通じてETCDにpodメタデータを書き込みます.スケジューラはInformerでpodsの状態をモニターし、podが追加されるとpodQueにpodを追加します.スケジューラ内のメインプロセスは、podQueから取り出したpodを継続的に行い、スケジュール割り当てノード環節スケジューリング環節にpodを入れて2段階奏にし、Filterフィルタで条件を満たすノード、Prioritizeは、pod構成、例えばリソース使用率、親和性などの指標に基づいて、これらのノードに点数を付けて、最終的に点数の一番高いノードを選出する.割り当てノードが成功し、appServerのbinding podインターフェースを呼び出し、pod.Spec.NodeNameを割り当てられたノードに設定する.ノードのkubeletは同様にApp Serverを傍受し、新しいpodがあると発見されたら、所在ノードにスケジュールされ、ローカルのdockerDaemenを呼び出してコンテナを実行します.スケジューラがPodのスケジューリングに失敗した場合、優先度と占有機能がオンになったら、一度の占有を試み、ノードの中で優先度の低いpodを削除し、スケジューラされるpodをノードにスケジュールする.開いていない場合や、フライングに失敗した場合は、ログを記録し、podQueチームの最後にpodを入れます.1
詳細を実現するためには、Kbe-schedulingは独立して動作するコンポーネントであり、主な仕事内容はRun関数である.
この中には主にいくつかのことがあります.
Schedulerのインスタンスschedを初期化し、様々なInformerに導入され、関心のあるリソースの変化のためにヘッドセットを確立し、ハンダーに登録し、例えばpodQueneを維持してeventsコンポーネントを登録し、ログ登録http/httpsを設定し、健康診断とmetrics要求を提供して、主要なスケジュール内容の入口sched.runを実行します.設定されている場合--leader-elect=trueは、代表的に複数のインスタンスを起動し、Raftを通じて主を選択し、例はmasterとして選択された場合にのみ、主な仕事関数sched.runを実行します.スケジュールコアの内容はsched.run関数で、Go routineはsched.scheduleOneを継続的に実行します.毎回の運行はスケジュール周期を表します.
func(sched*Scheduler)Run()
if !sched.config.WaitForCacheSync() {
    return
}
go wait.Until(sched.scheduleOne, 0, sched.config.StopEverything)
私たちはsched.scheduleOneを見て、主に何をしますか?
func(sched*Schduler)scheduleOne(){pod:=sched.co nfig.NextPod()…//do some pre check scheduleResult,err:=sched.schedule(pod)
if err != nil {
    if fitError, ok := err.(*core.FitError); ok {
        if !util.PodPriorityEnabled() || sched.config.DisablePreemption {
            ..... // do some log
        } else {
            sched.preempt(pod, fitError)
        }
    }
}
... 
// Assume volumes first before assuming the pod.
allBound, err := sched.assumeVolumes(assumedPod, scheduleResult.SuggestedHost)
...     
fo func() {
    // Bind volumes first before Pod
    if !allBound {
        err := sched.bindVolumes(assumedPod)
        if err != nil {
            klog.Errorf("error binding volumes: %v", err)
            metrics.PodScheduleErrors.Inc()
            return
        }
    }
  err := sched.bind(assumedPod, &v1.Binding{
        ObjectMeta: metav1.ObjectMeta{Namespace: assumedPod.Namespace, Name: assumedPod.Name, UID: assumedPod.UID},
        Target: v1.ObjectReference{
            Kind: "Node",
            Name: scheduleResult.SuggestedHost,
        },
    })
}
}sched.scheduleOneでは、主にいくつかのことをします.
sched.co nfig.NextPod()を通じて、podQueneからpod実行sched.scheduleを取り出して、スケジュールを試してみます.もしスケジュールが失敗したら、占有機能を起動したら、sched.preemptを呼び出して占用を試みます.いくつかのpodを駆逐して、スケジュールされたpodのために空間を予約して、次のスケジュールで有効になります.スケジュールが成功したら、bindインターフェースを実行します.ビndを実行する前に、pod volumeで宣言したPVCのためにprovisionを作ります.sched.scheduleは主にpodスケジューリングロジックです.
func(g generanc Scheduler)Schedule(pod v 1.Pod,nodeLister algorithm.NodeLister)
// Get node list
nodes, err := nodeLister.List()
// Filter
filteredNodes, failedPredicateMap, err := g.findNodesThatFit(pod, nodes)
if err != nil {
    return result, err
}
// Priority
priorityList, err := PrioritizeNodes(pod, g.cachedNodeInfoMap, metaPrioritiesInterface, g.prioritizers, filteredNodes, g.extenders)
if err != nil {
    return result, err
}

// SelectHost
host, err := g.selectHost(priorityList)
return ScheduleResult{
    SuggestedHost:  host,
    EvaluatedNodes: len(filteredNodes) + len(failedPredicateMap),
    FeasibleNodes:  len(filteredNodes),
}, err
}スケジュールは主に三つのステップに分かれています.
Filters:フィルタ条件が満たされていないノードPrioritizeNodes:条件が満たされているノードでScroringを行い、最終的な採点リストprorityListselectHostを取得する:prortyListで最も点数が高いノードのセットを選択し、中からround-robin方式によってノードを選択する.
続いて私達は引き続き分解して、それぞれこの3つの歩調がどのようにすることができることを見ます.
Filters Filtersは比較的簡単で、スケジューラはデフォルトで一連のpredicates方法を登録して、スケジューラプロセスは各ノードのpredicates方法を同時に起動するために行われる.最終的には、条件に合致するノードオブジェクトを含むnode listが得られる.
func(g generanc Scheduler)findNodes ThatFit(pod v 1.Pod,nodes[]v 1.Node)([]v 1.Node,FailedicateMap,error){
if len(g.predicates) == 0 {
    filtered = nodes
} else {
    allNodes := int32(g.cache.NodeTree().NumNodes())
    numNodesToFind := g.numFeasibleNodesToFind(allNodes)

    checkNode := func(i int) {
        nodeName := g.cache.NodeTree().Next()
  //             predicates   
        fits, failedPredicates, err := podFitsOnNode(
            pod,
            meta,
            g.cachedNodeInfoMap[nodeName],
            g.predicates,
            g.schedulingQueue,
            g.alwaysCheckAllPredicates,
        )

        if fits {
            length := atomic.AddInt32(&filteredLen, 1)
            if length > numNodesToFind {
        //                 ,     。
                cancel()
                atomic.AddInt32(&filteredLen, -1)
            } else {
                filtered[length-1] = g.cachedNodeInfoMap[nodeName].Node()
            }
        }
    }
//     checkNode   
    workqueue.ParallelizeUntil(ctx, 16, int(allNodes), checkNode)
    filtered = filtered[:filteredLen]
}
return filtered, failedPredicateMap, nil
}注目すべきは、1.13にFeasible Nodes機構が導入され、大規模なクラスタのスケジューリング性能を向上させるためである.私たちはbad-percentage-off-nodes-to-scoreパラメータを通じて、filterの計算比率(デフォルト50%)を設定し、ノード数が100より大きい場合、filtersのプロセスでは、条件を満たすノード数がこの比率を超えると、filterプロセスを停止し、全ノードを計算するのではなくて、.例えば、ノード数が1000で、私たちが設定した計算の割合が30%である場合、スケジューラは、filterプロセスは条件を満たす300個のノードしか見つからないと考えています.filterプロセスでは、条件を満たすノード数が300個に達したら、filterプロセスは終了します.このようにfilterは全てのノードを計算しなくても、Prioritizeの計算数を下げることができます.しかし、影響は、podが最も適切なノードにスケジュールされていない可能性がある.
Prioritize Prioritizeの目的は、podを助けることであり、条件に合致するノードごとに採点し、podが最適なノードを見つけることを助けることである.同じスケジューラは、一連のPrioritize方法をデフォルトで登録しました.これはPrioritizeオブジェクトのデータ構造です.
//PriorityConfig is a config used for a priority functions.type PriorityConfig struct{
Name   string
Map    PriorityMapFunction
Reduce PriorityReduceFunction
// TODO: Remove it after migrating all functions to
// Map-Reduce pattern.
Function PriorityFunction
Weight   int
}PriorityConfigごとに評価の指標を表し、サービスの均衡、ノードのリソース配分などを考慮します.PriorityConfigの主なScroringプロセスはMapとReduceに分けられます.
Mapプロセスは、各ノードの分数値Reduceプロセスを計算し、現在のPriorityConfigの全ノードの採点結果をもう一度処理する.すべてのPriorityConfigを計算した後、PriorityConfigの値に対応する重みを乗じて、ノードによってもう一回重合します.
workqueue.ParallelizeUntil(context.TODO(), 16, len(nodes), func(index int) {
    nodeInfo := nodeNameToInfo[nodes[index].Name]
    for i := range priorityConfigs {
        var err error
        results[i][index], err = priorityConfigs[i].Map(pod, meta, nodeInfo)
    }
})

for i := range priorityConfigs {
    wg.Add(1)
    go func(index int) {
        defer wg.Done()
        if err := priorityConfigs[index].Reduce(pod, meta, nodeNameToInfo, results[index]);
    }(i)
}
wg.Wait()

// Summarize all scores.
result := make(schedulerapi.HostPriorityList, 0, len(nodes))

for i := range nodes {
    result = append(result, schedulerapi.HostPriority{Host: nodes[i].Name, Score: 0})
    for j := range priorityConfigs {
        result[i].Score += results[j][i].Score * priorityConfigs[j].Weight
    }
}
また、FilterとPrioritizeは、extenser schedulerの呼び出しをサポートしています.本論文ではあまり説明しません.
現在のクベルネテックスのスケジューラはPod-by-Paodであり、現在のスケジューラが不足しているところです.主要ボトルネックは以下の通りです
Kbernetsは現在スケジュールされている方式で、各podはすべてのノードに対して計算します.クラスタ規模が非常に大きく、ノード数が多い場合、podのスケジュール時間は非常に遅くなります.これもpercentage-off-nodes-to-scoreが解決しようと試みる問題pod-by-podのスケジューリング方式がいくつかのマシン学習シーンに適合しないです.Kbernetesの初期設計は主にオンラインの任務のためにサービスしています.例えば、分散型の機械学習では、新しいアルゴリズムgang schedulerが必要です.podはスケジュールの即時性に対してそんなに高くないかもしれませんが、任務を提出した後、一括計算タスクのすべてのworkersが実行される時にのみ、計算任務を開始します.pod-by-pod方式はこの場合、資源が不足していると、資源のデッドロックを引き起こしやすいです.現在のスケジューラの拡張性は十分ではありません.特定のシーンのスケジューラは、ハードコードによって主流のプロセスに実装される必要があります.例えば、私たちが見ているbindVolumeの部分も、Gang Schedulerが現在のスケジューラの枠組みの中で元の方法で実現できないことになります.Kubergnetesスケジューラの発展はコミュニティスケジューラの発展もこれらの問題を解決するためです.
スケジューラV 2フレームは、拡張性を高め、元のスケジューラでのGang scheduleの実現にも備える.Kube-batch:Gang scheduleの実現https://github.com/kubernetes...poseidon:Firmamentネットワークマップスケジューリングアルゴリズムに基づくスケジューラであり、poseidonはFirmamentをKubergnetesスケジューラにアクセスするための実装である.https://github.com/kubernetes...次に、具体的なスケジューラ法の実現を分析して、スケジューラを分解する過程を理解するのを助けます.そして、スケジューラのコミュニティ動態を分析することに注目します.
本稿の著者:蕭元
原文を読む
本文は雲栖コミュニティのオリジナル内容です.許可なしに転載してはいけません.