Kubernetes 的開始起源于谷歌,是一個谷歌花了很多力氣來為你的工作和服務創建的新管理工具。它在谷歌系統中有自己的起源: Borg 和 Omega 。無論是公有云還是私有云甚至混合云,Kubernetes將作為一個為任何應用,任何環境的容器管理框架無處不在。正因為如此, 受到各大巨頭及初創公司的青睞,如IBM,Microsoft、VMWare、Red Hat、CoreOS、Mesos等,紛紛加入給Kubernetes貢獻代碼。Kubernetes毫無疑問坐擁容器編排的霸主地位。
關于Kubernetes的起源,架構,原理,組件的介紹網上太多了,感興趣的可以自行google,但是我發現關于源碼的解讀比較少,即使有,也比較泛泛。大多是從某個函數入口講起,然后講到包,結構體,屬性,方法結束,關于每個方法的作用要么是略過,要么是一句話解釋,對Kubernetes的一個組件的解讀盡然一篇文章就寫完了,大師的思維太快,跟不上~~~
架構是大師們的事情,我們凡夫俗子能做到清楚就很不容易了!我打算在接下來的文章里,每一篇文章之讀一個關鍵方法。細細品味大神們的杰作。
我想好多用過Kubernetes的都有一個疑問,Pod是如何通過層層篩選被綁定到一個節點的。接下來就分析這個方法實現:scheduleOne()。
關于scheduler的predicate
和prioirity
設計請參考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機制,敬請期待。