這篇文章源于我在學習scheduler時找到的,閱讀完了后讓我感覺這確實是一篇非常Excellent的文章,所以決定翻譯一下。
# :-: How does the Kubernetes scheduler work?
  之前我們談過了Kubernetes的整體結構,這周我又學到了kubernetes sceduler是如何工作的,所以我把它分享出來。這涉及到了調度器的具體工作方式。
這同樣也闡述了如何從“我對于這個系統完全一無所知”到“ok,我理解了它的的基本設計決策與為什么是這樣的”這一過程,并且是在,完全不問任何人的前提下,(因為事實上我不認識任何kubernetes的Contribute,所以也就無法找人向我解釋。)
這只是一種很little的思維,但希望他能夠對某些人有用。我找到的最有用的鏈接就是從讓人大吃一驚的[kubernetes developer documentation folder](https://github.com/kubernetes/community/tree/8decfe42b8cc1e027da290c4e98fa75b3e98e2cc/contributors/devel)中的[Writing Controllers](https://github.com/kubernetes/community/blob/8decfe4/contributors/devel/controllers.md)這個文件。
### what is the scheduler for?
kubernetes schedulor負責調度pod到Node上,它的基本工作是:
1.你創建pod
2.scedulor 察覺到有一個新創建的pod還沒有分配到node上
3.scheduler分配pod
他并不為運行的pod負責,那是kubelet的工作,因此它的工作就是確定每一個pod都有一個分配了的節點,Easy,right?
Kubernetes的controller也有一個這樣的概念,controller的工作是:
觀察系統的狀態
看目標狀態與實際狀態是否相符(就像需要被分配的pod一樣)(這地方我還真是沒想到)
重復
scheduler某種程度上就是一種controller,有很多不同的控制器,它們都有不同的工作并且獨立運行。
所以,你可以想象scheduler的運行循環就像下面這樣:
~~~
while True:
pods = get_all_pods()
for pod in pods:
if pod.node == nil:
assignNode(pod)
~~~
(注:后面可以看到,這個想象確實相當‘科學’)。
如果你并不對scheduler的詳細細節感興趣的話,讀到這里就ok了,這確實是一個相當合理的運作結構。
我之所以認為scheduler是這樣運作的是因為cronjob controller是這樣運作的,且我只看過這部分的代碼,cronjob控制器基本上就是遍歷所有的cronjobs,查看是否有任何事情要做,休眠10秒,然后永遠重復。Super simple!
### this isn’t how it works though
so!這周我們在Kubernetes上附在了更多的工作,我們察覺到一個問題。
有時候一個pod會一直被卡在pending狀態(沒有為其分配node),如果我們重啟scheduler,pod就能恢復正常,
([this issue](https://github.com/kubernetes/kubernetes/issues/49314))
但這跟我對于scheduler的運作的想法并不匹配。正常來說不是應該不必重新啟動就能分配到pod上嗎。
因此我去閱讀了一大堆代碼,下面是我了解到的scheduler實際的工作原理,
因為我這周才知道這個,所以也可能有錯。
### how the scheduler works: a very quick code walkthrough
我們的入口在[scheduler.go](https://github.com/kubernetes/kubernetes/blob/e4551d50e57c089aab6f67333412d3ca64bc09ae/plugin/pkg/scheduler/scheduler.go)文件中,但事實上我把所有的文件都湊到了一塊[concatenated all the files in the scheduler together](https://gist.github.com/jvns/5d492d66130a2f47b47820fd6b52eab5)。
scheduler的核心loop(commmit e4551d50e5):
([link](https://github.com/kubernetes/kubernetes/blob/e4551d50e57c089aab6f67333412d3ca64bc09ae/plugin/pkg/scheduler/scheduler.go#L150-L156))
~~~
go wait.Until(sched.scheduleOne, 0, sched.config.StopEverything)
~~~
大意就是,讓scheduler一直不停的run下去。Cool,那具體怎么做的?
~~~
func (sched *Scheduler) scheduleOne() {
pod := sched.config.NextPod()
// do all the scheduler stuff for `pod`
}
~~~
那NextPod()是做什么的?來自于哪里?
~~~
func (f *ConfigFactory) getNextPod() *v1.Pod {
for {
pod := cache.Pop(f.podQueue).(*v1.Pod)
if f.ResponsibleForPod(pod) {
glog.V(4).Infof("About to try and schedule pod %v", pod.Name)
return pod
}
}
}
~~~
Okay,這很簡單,從podQueue當中取到next pod。
但那pod是怎么放進去的呢(But how do pods end up on that queue?)?下面的代碼就是:
~~~
podInformer.Informer().AddEventHandler(
cache.FilteringResourceEventHandler{
Handler: cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
if err := c.podQueue.Add(obj); err != nil {
runtime.HandleError(fmt.Errorf("unable to queue %T: %v", obj, err))
}
},
~~~
添加了一個事件監聽器,當有新pod添加進去時,將他放入到queen當中。
### how the scheduler works, in English
Okay,現在我們已經看完了代碼,下面是一個總結:
1.首先每一個需要被調度的pod添加到queen當中。
2.當新的pod創建時,也會添加到queen當中。
3.調度器不聽得取出pod,然后調度他。
4.就這樣
這里有個有意思的事情是,如果出于某種原因pod調度失敗,這里沒有任何重新調度pod的嘗試,pod從queen中取出,然后調度失敗,然后就完了。他失去了他唯一的機會。(除非你重啟scheduler,這種情況下所有都會被再一次添加到pod queen當中。)
當然scheduler沒那么笨,當一個pod調度失敗時,通常會調用一個像下面這樣的錯誤處理。
~~~go
host, err := sched.config.Algorithm.Schedule(pod, sched.config.NodeLister)
if err != nil {
glog.V(1).Infof("Failed to schedule pod: %v/%v", pod.Namespace, pod.Name)
sched.config.Error(pod, err)
~~~
這個方法會把pod重新調度到queue中然后重試。
### wait why did our pod get stuck then?
這很簡單啦,說明了錯誤處理方法并不總是成功被調用當出現錯誤時。我們在error方法調用的地方做了點修改,然后看上去就能正常運轉了。Cool!
### why is the scheduler designed this way?
我感覺下面這種設計企不是根據有健壯性:
~~~
while True:
pods = get_all_pods()
for pod in pods:
if pod.node == nil:
assignNode(pod)
~~~
那為什么是采用caches,quene,然后還有callbacks這些復雜的操作呢?我回顧了下歷史然后想到了可能是性能的原因------比如你可以看這篇文章[update on scalability updates for Kubernetes 1.6](https://blog.kubernetes.io/2017/03/scalability-updates-in-kubernetes-1.6.html)
還有這篇來自coreos 的[improving Kubernetes scheduler performance](https://coreos.com/blog/improving-kubernetes-scheduler-performance.html).
一篇說調度30000pods從14分鐘到10分鐘,另一篇說調度30000pod從2小時到10分鐘,2小時真是太慢了,性能很重要。
### what the scheduler actually uses: kubernetes “informers”
我還想談及Kubernetes上的一個非常重要的所有kubernetes的controllers的設計。那就是informer,幸運的是這里我通過gooleing”kubernetes informer“找到了這篇文章。
這篇非常有用的文章稱作[Writing Controllers](https://github.com/kubernetes/community/blob/8decfe4/contributors/devel/controllers.md),文章給你設計的建議關于編寫controller(就像scheduler和cronjob controller)。VERY COOL。
如果我第一時間就能找到這篇文章的話,我肯定可以更快的理解這里發生的事情。
因此,informers!doc是這樣寫的:
~~~
Use SharedInformers. SharedInformers provide hooks to receive notifications of adds, updates, and deletes for a particular resource. They also provide convenience functions for accessing shared caches and determining when a cache is primed
使用SharedInformers。SharedInformers提供鉤子來接收特定資源的添加、更新和刪除通知。它們還提供了一些方便的函數來訪問共享緩存,并確定何時啟動緩存。
~~~
當一個controller運行起來后會創建一個”informer“(例如一個”pod informer“)負責以下功能:
1.首先列出所有的pod。
2.告訴你update
cronjob控制器并不是使用一個informer(使用informers更復雜了,我想他根本不會考錄那么多的性能因素),但許多其他的(大多數)controllers是這樣做的。特別是scheduler,你可以看到他配置的infromers在[這里](https://github.com/kubernetes/kubernetes/blob/e4551d50e57c089aab6f67333412d3ca64bc09ae/plugin/pkg/scheduler/factory/factory.go#L175).
### requeueing
在”write controller“文檔里面實際上有一些關于如何處理item的requeu。
過濾錯誤到頂層以實現連續的requeue,我們有一個workqueue.RateLimitingInterface允許簡單的重新requeue一個合理的backoff(補償?)
你的main方法應該返回一個錯誤當requeue必要時,如果不是的話,就應當使用utilruntime.HandleError返回一個nil,這會使得reviewers輕松的檢查錯誤處理以及對于控制器并沒有偶然的丟失掉應該重試東西而有信心。
這看上去是一個很好的建議,正確處理所有錯誤看起來很棘手,所以有一種簡單的方法來確保審核人員能夠知道錯誤被正確處理是很重要的!COOL。
### you should “sync” your informers (or should you?)
Okay,這是我最后學到的東西了。
Informers有一個sync的概念,像是重新啟動程序一樣,你會拿到每一個你所監視的資源列表,然后你可以檢查事實上是不是ok,下面是”writing controllers“介紹的關于syncing。
Watchs與Informers將會”sync“,定期性的,他們會在集群中分發每一個匹配的對象到你的update方法上,這是一個很好的案例當你需要在對象上采取額外的動作時。此處留下。。。
因此,這暗示”你應當sync,如果你不sync,那么最后你會丟掉一個item然后在一不會重試“,這也正是我們所遇見的情況。
### the kubernetes scheduler doesn’t resync
so!!當我學了sync這個想法后,I was like....,wait,這是否意味著kubernetes scheduler從不resyncs?答案是,是sync的。下面是代碼:
~~~
informerFactory := informers.NewSharedInformerFactory(kubecli, 0)
// cache only non-terminal pods
podInformer := factory.NewPodInformer(kubecli, 0)`
~~~
這些數字0,代表了resync周期,我解讀為他從不resync,Interesting!!為什么他從不resync?我并不確定,接著google了”kubernetes scheduler resync”,
然后找到了這個提交申請[#16840](https://github.com/kubernetes/kubernetes/pull/16840)(為scheduler添加resync),有下面兩個評論。
@brendandburns-我非常反對有如此小的resync周期,因為它會明顯的影響性能。
以及
我同意@wojtek-t,如果resync能夠解決問題,這意味著我們隱藏了一個bug,我認為resync并不是正確的解決方案。
因此,項目維護著決定不再進行同步,因為當存在bugs,他們希望被發現且加以修復,而不是被resync隱藏。
### some code-reading tips
就我目前所知,kubernetes scheduler內部如何運作目前還沒有人寫下來(下大多數事情一樣)。
在閱讀代碼時這里有幾件事情幫助了我
1 把各個文件聚集到一個文本中,我已經說過了但這確實對我很有幫助,因為在各種方法與文件中跳轉確實讓人頭暈,特別是在我并不理解是怎樣組織的情況下。
2 找準關鍵性的問題,在這里我主要是想弄清楚錯誤處理是如何工作的?如果一個pod并沒有被調度會發生什么?這里有許多的代碼....,關于他如何選擇node的我并不需要去關心。
### kubernetes is pretty good to work with so far
Kubernetes是一個非常復雜的軟件!為了讓整個集群能夠運行起來,你需要設置至少6個不同的組件(API server,scheduler,controller manage,container network例如flanned,kube-proxy,kubelet),如果你像我一樣想要理解軟件是如何運作的那就必須要去理解他們之間的交互以及諸多的配置。
但到目前為止文檔都是很棒的,如果文檔沒有涵蓋的話讀源碼也是很容易的,并且他們也非常愿意對提交請求作出回應。
我確實需要比通常更多的去閱讀源碼,這是一個很好的技能!
- 文章翻譯
- Large-scale cluster management at Google with Borg
- Borg Omega and kubernetes
- scaling kubernetes to 7500 nodes
- bpf 的過去,未來與現在
- Demystifying Istio Circuit Breaking
- 知識圖譜
- skill level up graph
- 一、運維常用技能
- 1.0 Vim (編輯器)
- 1.1 Nginx & Tengine(Web服務)
- 基礎
- 1.2 zabbix
- 定義
- 登錄和配置用戶
- 1.3 RabbitMQ(消息隊列)
- 原理
- RabbitMQ(安裝)
- 1.4虛擬化技術
- KVM
- 1.5 Tomcat(Web中間件)
- 1.6Jenkins
- pipline
- 1.7 Docker
- network
- 1.8 Keepalived(負載均衡高可用)
- 1.9 Memcache(分布式緩存)
- 1.10 Zookeeper(分布式協調系統)
- 1.11 GitLab(版本控制)
- 1.12 Jenkins(運維自動化)
- 1.13 WAF(Web防火墻)
- 1.14 HAproxy負載均衡
- 1.15 NFS(文件傳輸)
- 1.16 Vim(編輯器)
- 1.17 Cobbler(自動化部署)
- 二、常用數據庫
- 2.1 MySQL(關系型數據庫)
- mysql主從復制
- 2.2 Mongodb(數據分析)
- 2.3 Redis(非關系數據庫)
- 三、自動化運維工具
- 3.1 Cobbler(系統自動化部署)
- 3.2 Ansible(自動化部署)
- 3.3 Puppet(自動化部署)
- 3.4 SaltStack(自動化運維)
- 四、存儲
- 4.1 GFS(文件型存儲)
- 4.2 Ceph(后端存儲)
- 五、運維監控工具
- 5.1 云鏡
- 5.2 ELK
- 六、運維云平臺
- 6.1 Kubernetes
- 6.2 OpenStack
- 介紹
- 安裝
- 七、Devops運維
- 7.1 理念
- 7.2 Devops運維實戰
- 八、編程語言
- 8.1 Shell
- 書籍《Wicked Cool Shell Scripts》
- 8.2 Python
- 8.3 C
- 8.4 Java
- leecode算法與數據結構
- 九、雜記
- 高優先級技能
- 知識點
- JD搜集
- 明顯的短板
- 1.0 Python
- 1.1 Kubernetes
- 1.18.2 《kubernetes in action》
- 遺漏知識點
- 1.18.3 GCP、azure、aliyun
- Azure文檔
- 1.18.5 《program with kubernetes》
- Istio
- HELM
- 《Kubernetes best practice》
- Kubernetes源碼學習
- Scheduler源碼
- 調度器入口
- 調度器框架
- Node篩選算法
- Node優先級算法
- pod搶占調度
- 入口
- 主要代碼結構
- new
- 文章翻譯
- Flannel
- 從二進制集群搭建
- 信息收集
- docker優化
- 1.2 shell
- 面試題
- grep awk sed 常見用法
- shell實踐
- 1.3 Data structure(數據結構)
- Calico
- Aliyun文檔以及重點模塊
- git
- 大數據組件
- 前端,后端,web框架
- cgroup,namespace
- 內核
- Linux搜集
- crontab
- centos7常用優化配置
- centos Mariadb
- eBPF
- ebpf的前世今生
- Linux性能問題排查與分析
- 性能分析搜集
- 性能分析常用10條
- 網絡性能優化
- 文本處理命令
- sql
- Iptables
- python面試題
- iptables
- iptables詳細
- zabbix面試題,proj
- prometheus
- web中間件
- nginx
- Haproxy
- grep sed awk
- Linux常用命令
- 云平臺
- 書籍Linux應用技巧
- kafka
- kafka面試題
- ETCD
- Jenkins
- 3天補充的點
- K8s源碼
- K8s
- k8s實操
- etcd
- test
- BPF
- PSFTP使用
- StackOverflow問答精選
- 問題
- 我對于學習思考
- 修改ssh超時時間
- 課程目錄
- 運維與運維開發
- The Person
- 個人雜談
- mysql主從復制
- 對于工作的認識與思考