細讀Kubernetes源碼(scheduler之Pod的綁定)

Kubernetes 的開始起源于谷歌,是一個谷歌花了很多力氣來為你的工作和服務創建的新管理工具。它在谷歌系統中有自己的起源: Borg 和 Omega 。無論是公有云還是私有云甚至混合云,Kubernetes將作為一個為任何應用,任何環境的容器管理框架無處不在。正因為如此, 受到各大巨頭及初創公司的青睞,如IBM,Microsoft、VMWare、Red Hat、CoreOS、Mesos等,紛紛加入給Kubernetes貢獻代碼。Kubernetes毫無疑問坐擁容器編排的霸主地位。

關于Kubernetes的起源,架構,原理,組件的介紹網上太多了,感興趣的可以自行google,但是我發現關于源碼的解讀比較少,即使有,也比較泛泛。大多是從某個函數入口講起,然后講到包,結構體,屬性,方法結束,關于每個方法的作用要么是略過,要么是一句話解釋,對Kubernetes的一個組件的解讀盡然一篇文章就寫完了,大師的思維太快,跟不上~~~

架構是大師們的事情,我們凡夫俗子能做到清楚就很不容易了!我打算在接下來的文章里,每一篇文章之讀一個關鍵方法。細細品味大神們的杰作。

我想好多用過Kubernetes的都有一個疑問,Pod是如何通過層層篩選被綁定到一個節點的。接下來就分析這個方法實現:scheduleOne()

關于scheduler的predicateprioirity設計請參考devel/scheduler.md,關于它的算法請參考devel/scheduler_algorithm.md,讀完之后,你就會對Kubernetes Master三大組件之一的scheduler有了一個基本的印象,甚至感覺好簡單啊~~~ 那么,恭喜你,你與大神的差距又縮短了一厘米~~~

好,現在我們從 kubernetes/plugin/pkg/scheduler/scheduler.go
中摘出scheduleOne方法,我會把解讀放到每一行代碼的前面。

// scheduleOne does the entire scheduling workflow for a single pod.  It is serialized on the scheduling algorithm's host fitting.
func (sched *Scheduler) scheduleOne() {
    // 獲取一個待調度的Pod, NextPod()方法是阻塞的
    pod := sched.config.NextPod()
    //首先,判斷這個Pod是否正在被刪除中,如果是,則返回,跳過調度。
    if pod.DeletionTimestamp != nil {
        //記錄發生的事件,可以通過kubectl describe pods命令看到此處的事件打印信息
        sched.config.Recorder.Eventf(pod, v1.EventTypeWarning, "FailedScheduling", "skip schedule deleting pod: %v/%v", pod.Namespace, pod.Name)
        glog.V(3).Infof("Skip schedule deleting pod: %v/%v", pod.Namespace, pod.Name)
        return
    }
       // 開始嘗試調度pod,以命名空間和pod名稱區分
    glog.V(3).Infof("Attempting to schedule pod: %v/%v", pod.Namespace, pod.Name)

    // Synchronously attempt to find a fit for the pod.
    start := time.Now()
    // 此處的schedule()方法中,會調用注冊的初選和優選的算法,最終返回一個節點的名稱存放到suggestedHost變量中
    suggestedHost, err := sched.schedule(pod)
    // 此處,會調用go的一個客戶端,原子地的更新觀測總和。關于它的具體功能,我們下次再細讀。
    metrics.SchedulingAlgorithmLatency.Observe(metrics.SinceInMicroseconds(start))
    if err != nil {
        return
    }

    // Tell the cache to assume that a pod now is running on a given node, even though it hasn't been bound yet.
    // This allows us to keep scheduling without waiting on binding to occur.

    //即使該pod還未真正綁定到節點上,我們先假設這個pod已經在指定的節點上運行了。這是為了更新shcedulerCache, 與綁定操作(需要花費一些時間)以異步方式進行。 
    err = sched.assume(pod, suggestedHost)
    if err != nil {
        return
    }

    // bind the pod to its host asynchronously (we can do this b/c of the assumption step above).
    // 創建一個協程goruntime,異步地綁定pod。(注意,該shceduleOne方法也是在一個協程中)
    go func() {

        // 執行綁定方法,把該pod的命名空間,名稱和UID綁定到指定的節點(suggestedHost)上,到此,一個pod綁定到一個節點的過程就完成了。
        err := sched.bind(pod, &v1.Binding{
            ObjectMeta: metav1.ObjectMeta{Namespace: pod.Namespace, Name: pod.Name, UID: pod.UID},
            Target: v1.ObjectReference{
                Kind: "Node",
                Name: suggestedHost, // 分配的節點
            },
        })

        // 與上面類似,會在后面單獨分析,它們的實現在vendor/github.com/prometheus/client_golang/prometheus/histogram.go,可以自行看一下
        metrics.E2eSchedulingLatency.Observe(metrics.SinceInMicroseconds(start))
        if err != nil {
            glog.Errorf("Internal error binding pod: (%v)", err)
        }
    }()
}

是不是感覺很簡單?確實,大神的代碼讓你感覺邏輯清晰,代碼簡潔。但是,還有許多問題未解決,比如,如果綁定失敗,retrying 是如何觸發的呢?retrying的篩選邏輯,使用的算法是否和之前一致呢?如果調度過程中節點掛了怎么辦?scheduler是如何拿到各個節點的資源信息的?綁定之后,Kubelet是如何接收Pod的?如何添加自己定制的算法?等等。下一篇文章會分析retrying機制,敬請期待。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容