Kubernetes模型通常由以下部分組成:
TypeMeta
TypeMeta是Kubernetes對象的最基本定義,它通過引入GKV(Group,Kind,Version)定義了一個對象的類型。
Group
Kubernetes定義了非常多對象,如何歸類這些對象是一門學問,將對象依據(jù)其功能范圍歸入不同的分組,比如把支撐最基本功能的對象歸入core組,把與應用部署有關的對象歸入apps組,會使這些對象可維護性和可理解性更高。
Kind
定義一個對象的基本類型,比如Node,Pod,Deployment等。
Version
社區(qū)每個季度會推出一個Kubernetes版本,隨著Kubernetes版本的演進,對象從創(chuàng)建之初到能夠完全生產(chǎn)化就緒的版本是不斷變化的。與軟件版本類似,通常社區(qū)提出一個模型定義以后,隨著該對象不斷成熟,其版本可能會從v1alpha1,到v1alpha2,或者到v1beta1,最終變成生產(chǎn)就緒版本v1。
Kubernetes通過Version屬性來控制版本。當不同版本的對象定義發(fā)生變更時,有可能需要涉及到數(shù)據(jù)遷移,Kubernetes API Server允許通過Conversion方法轉換不同版本的對象屬性。這是一種自動數(shù)據(jù)遷移的機制,當集羣版本升級以后,已經(jīng)創(chuàng)建的老版本對象會被自動轉換為新版本。
這里所説的版本是對外版本(External Version),用戶通過API能看到的版本。事實上資源定義都有對內版本(Internal Version),在Kubernetes API Server處先將對外版本轉換成對內版本,然后再進行持久化。
Metadata
TypeMeta定義了“我是什麼”,Metadata定義了“我是誰”。為方便管理,Kubernetes將不同用戶或不同業(yè)務的對象用不同的Namespace隔離。Metadata中有兩個最重要屬性——Namespace和Name,分別定義了對象的Namespace歸屬及名字,這兩個屬性唯一定義了某個對象實例。
前面説過,所有對象都會以API的形式發(fā)佈供用戶訪問,Typemeta、Namespace和Name唯一確定了該對象所在的API訪問路徑,該路徑也會被自動生成并保存在對象Metadata屬性的selfLink中,如下所示:
selfLink: /api/v1/namespaces/default/pods/nginx-6ccb6b48dd-zvfrj
Label
傳統(tǒng)面向對象設計系統(tǒng)中,對象組合的方法通常是內嵌或引用,即將對象A內嵌到對象B中,或者將對象A的ID內嵌到對象B中。這種設計的弊端是這種關係是固化的,一個對象可能對多個其他對象發(fā)生關聯(lián),如果該對象發(fā)生變更,系統(tǒng)需要遍歷所有其關聯(lián)對象并做修改。
Kubernetes採用了更巧妙的方式管理對象和對象的松耦合關係,其依賴的就是Label和Selector。Label,顧名思義就是給對象打標籤,一個對象可以有任意對標籤,其存在形式是鍵值對。不像名字和UID,標籤不需要獨一無二,多個對象可以有同一個標籤,每個對象可以有多組標籤。
Label定義了這些對象的可識別屬性,Kubernetes API支持以Label作為過濾條件查詢對象。因此Label通常用最簡形式定義:
metadata:
labels:
app: web
tier: front
其他對象只需要定義Label Selector就可按條件查詢出其需要關聯(lián)的對象。Label的查詢可以基于等式如app=web,或app!=db,或基于集合如app in (web, db)或app notin (web, db),可以只查詢Label鍵,比如app。Label對多個條件查詢只支持“與”操作,如app=web, tier=front。
Annotation
Annotation與Label一樣用鍵值對來定義,但其功能與Label不一樣,所有在用法上也有不同原則,API也不支持針用Annotation做條件過濾。雖然Kubernetes把對象做了很好的抽象,在實際運用中特別是生產(chǎn)化落地過程中,總是需要保存一些在對象內置屬性中無法保存的信息,Annotation就是為了滿足這類需求,事實上Annotation是對象的屬性擴展。社區(qū)在開發(fā)新功能,需要對象發(fā)生變更之前,往往會先把需要變更的屬性放在Annotation中,當功能經(jīng)歷完實驗階段再將其移至正式屬性中。
Annotation作為屬性擴展,更多是面向系統(tǒng)管理員和開發(fā)人員的,因此Annotation需要像其他屬性一樣做合理歸類。與Java開發(fā)中的包名設計類似,通常需要將系統(tǒng)以不同功能規(guī)劃為不同的Annotation Namespace,其鍵應以如下形式存在:<namespace>/key:value, 比如一個最常用場景,為Pod標記如下Annotation以吿知Prometheus為其抓取系統(tǒng)指標。
annotations:
prometheus.io/path: /mymetrics
prometheus.io/port: "7355"
prometheus.io/scrape: "true"
Finalizer
如果只看社區(qū)實現(xiàn),那麼該屬性毫無存在感,因為在社區(qū)代碼中,很少有對Finalizer的操作。但在企業(yè)化落地過程中,它是一個十分重要,值得重點強調的屬性。因為Kubernetes不是一個獨立存在的系統(tǒng),它最終會跟企業(yè)資源和系統(tǒng)整合,這意味著Kubernetes會操作這些集羣外部資源或系統(tǒng)。試想一個場景,用戶創(chuàng)建了一個Kubernetes對象,假設對應的控制器需要從外部系統(tǒng)獲取資源,當用戶刪除該對象時,控制器接收到刪除事件后,會嘗試釋放該資源。可是如果此時外部系統(tǒng)無法連通,并且同時控制器發(fā)生重啟了會有何后果?該對象永遠泄露了。
Finalizer本質上是一個資源鎖,Kubernetes在接收到某對象的刪除請求,會檢查Finalizer是否為空,如果為空則只對其做邏輯刪除,即只會更新對象中metadata.deletionTimestamp字段。具有Finalizer的對象,不會立刻刪除,需等到Finalizer列表中所有字段被刪除后,也就是該對象相關的所有外部資源已被刪除,這個對象才會被最終被刪除。
因此,如果控制器需要操作集羣外部資源,則一定要在操作外部資源之前為對象添加Finalizer,確保資源不會因對象刪除而泄露。同時控制器需要監(jiān)聽對象的更新時間,當對象的deletionTimestamp不為空時,則處理對象刪除邏輯,回收外部資源,并清空自己之前添加的Finalizer。
ResourceVersion
通常在多線程操作相同資源時,為保證實物的一致性,需要在對象進行訪問時加鎖,以確保在一個線程訪問該對象時,其他線程無法修改該對象。排它鎖的存在確保某一對象在同一時刻只有一個線程在修改,但其排它的特性會讓其他線程等待鎖,使得系統(tǒng)整體效率顯著降低。
ResourceVersion可以被看做是一種樂觀鎖,每個對象在任意時刻都有其ResourceVersion,當Kubernetes對象被客戶端讀取以后,ResourceVersion信息也被一併讀取??蛻舳烁膶ο蟛⒒貙慉PIServer時,ResourceVersion會被增加,同時APIServer需要確?;貙懙陌姹颈确掌鞫水斍鞍姹靖?,在回寫成功后服務器端的版本會更新為新的ResourceVersion。因此當兩個線程同時訪問某對象時,假設它們獲取的對象ResourceVersion為1。緊接著第一個線程修改了對象,資源版本會變?yōu)?,回寫至APIServer以后,該對象服務器端ResourceVersion會被更新為2。此時如果第二個線程對該對象在1的版本基礎上做了更改,回寫APIServer時,所帶的新的版本信息也為2,APIServer校驗會發(fā)現(xiàn)第二個線程新寫入的對象ResourceVersion與服務器端ResourceVersion衝突,寫入失敗,需要第二個線程讀取最新版本重新更新。
此機制確保了分佈式系統(tǒng)中,任意多線程無鎖併發(fā)訪問對象,極大提升系統(tǒng)整體效率。
Spec和Status
Spec和Status才是對象的核心,Spec是用戶的期望狀態(tài),由創(chuàng)建對象的用戶端定義。Status是對象的實際狀態(tài),由對應的控制器收集實際狀態(tài)并更新。與TypeMeta和Metadata等通用屬性不同,Spec和Status是每個對象獨有的,后續(xù)的章節(jié)會通過介紹一些核心對象來深入理解。
為方便對Kubernetes對象的理解,下圖展示了按照業(yè)務目的歸類的常用Kubernetes對象和其分組。Kubernetes對象設計完全遵循互補的原則。鼓勵API對象儘量實現(xiàn)面向對象設計時的要求,即“高內聚,松耦合”,對業(yè)務相關的概念有一個合適的分解,提高分解出來的對象的可重用性。高層API對象設計一定是從業(yè)務出發(fā)的,低層API對象能夠被高層API對象所使用,從而實現(xiàn)減少宂馀、提高重用性的目的。

常用Kubernetes對象和其分組
核心對象概覽
Kubernetes的對象設計避免了簡單封裝和內部隱藏機制。簡單地封裝,A對象封裝了B對象的定義,實際沒有提供新的功能,反而增加了對所封裝API的依賴性。內部隱藏的機制也非常不利于系統(tǒng)維護的設計方式。例如StatefulSet、ReplicaSet和DaemonSet,如圖所示,本來就是三種Pod集合,那麼Kubernetes就用不同API對象來定義它們,而不會説將它們封裝在同一個資源對象,內部再通過特殊的隱藏算法再來區(qū)分這個資源對象是有狀態(tài)的、無狀態(tài)的還是節(jié)點服務。Pod是Kubernetes應用程序的基本執(zhí)行單元,即它是Kubernetes對象模型中創(chuàng)建或部署的最小和最簡單的單元。多數(shù)核心對象都為Pod對象服務的,但是它們都從Pod對象中所剝離出來的,有自己的API定義。Secret、ConfigMap和PVC是不同的資源對象定義,都可以作為存儲卷在Pod中使用。而在Pod中使用時,只需要指定該對象的名稱即可,無需將其具體信息在Pod資源對象中擴展。

Namespace
Namespace是Kubernetes進行歸類的對象,當一個集羣有多個用戶或一個用戶有多個應用需要管理時,有時需要將所有被管理的對象進行一定的隔離。Kubernetes引入了Namespace對象,類似文件目錄,不同對象被劃分到不同Namespace以后,可以通過權限控制來限制哪些用戶以何種權限訪問哪些Namespace的哪些對象,進而構建一個多租戶、彼此隔離的通用集羣。
Pod
容器云平臺需要解決的最核心問題是應用運行,Kubernetes將容器化應用運行的實體抽象為Pod,Pod類似豆莢,它是一個或者多個容器鏡像的組合。當應用啟動以后,每一個容器鏡像對應一組進程,而同一個Pod的所有容器中的進程默認公用同一網(wǎng)絡Namespace,并且共用同一網(wǎng)絡標識。Pod具有基本的自恢復能力,當某個副本出現(xiàn)問題時,它會按照預定策略被重啟。
當然,應用運行通常需要配置文件,這些配置文件又有可以明文讀寫的配置,也包含需要加密和嚴格權限控制的密碼證書等配置,Kubernetes為這些配置分別定義了Configmap和Secret。Configmap和Secret,和PersistVolumeClaim類似,都可以作為卷加載給運行的Pod,Pod中運行的進程可以像訪問本地文件一樣訪問它們。Configmap和Secret沒有本質區(qū)別,Secret只是將內容進行base64編碼,我們知道base64編碼是一種對稱加密,可以輕鬆解密,事實上沒有太多安全性可言。但Kubneretes支持Secret在持久化時的加密存儲,這樣保存在硬盤的Secret數(shù)據(jù)是無法解密的。其次,Kubernetes可以通過權限嚴格控制能夠訪問Secret的用戶,以保證密碼和證書信息的安全。
Pod除了包含用戶希望運行的容器鏡像和配置文件,還允許用戶定義其運行所需的資源,用戶創(chuàng)建Pod以后,Kubernetes會為其選擇一個最佳節(jié)點運行。計算節(jié)點被抽象成Node對象,節(jié)點數(shù)量和每個節(jié)點的資源彙總起來就是整個集羣能提供的算力。每個計算節(jié)點負責彙報自己的心跳信息,并上報節(jié)點的資源總量和可用資源。
ServiceAccount
Pod中運行的進程有時需要與Kubernetes API通信,在啟用了安全配置的集羣后,Pod一定要以某種身份與Kubernetes通信,這個身份就是系統(tǒng)賬戶(ServiceAccount)。Kubernetes會默認為每個Namespace創(chuàng)建一個default ServiceAccount,并且為每個ServiceAccount生成一個JWT Token,這個Token保存在Secret中。用戶可以在其Pod定義中指定ServiceAccount(默認為default),其對應的Token會被掛載在Pod中,Pod中的進程可以帶著該Token與Kubernetes通信以標識其身份。
ReplicaSet
Pod只是單個應用實例的抽象,要構建高可用應用,通常需要構建多個同樣的副本,提供同一個服務。Kubernetes為此抽象出副本集ReplicaSet,其允許用戶定義Pod的副本數(shù),每一個Pod都會被當作一個無狀態(tài)的成員管理,Kubernetes保證總是有用戶期望的數(shù)量的Pod正常運行。當某個副本宕機以后,控制器將會創(chuàng)建一個新的副本。當因業(yè)務負載發(fā)生變更而需要調整擴縮容時,可以方便地調整副本數(shù)量。
Deployment
對于無狀態(tài)在線應用,Kubernetes提供了更高級的版本變更控制。版本變更是一個日常頻繁發(fā)生的關鍵操作,如何在不中斷業(yè)務的前提下更新版本,一直是業(yè)界努力解決的問題。Deployment就是一個用來描述發(fā)佈過程的對象,其實現(xiàn)機制是,當某個應用有新版本發(fā)佈時,Deployment會同時操作兩個版本的ReplicaSet。其內置多種滾動升級策略,會按照既定策略降低老版本的Pod數(shù)量,同時創(chuàng)建新版本的Pod,并且總是保證正在運行的Pod總數(shù)與用戶期望副本數(shù)一致,并依次將該Deployment中的所有副本都更新至新版本。下圖展示了基于Deployment進行版本發(fā)佈的一箇中間狀態(tài)。

因為Deployment會維護ReplicaSet,ReplicaSet會創(chuàng)建Pod,因此通過Deployment維護針對無狀態(tài)的應用是第一選擇,它可以滿足諸多需求,縮短應用上線的時間,在不造成停機的情況下創(chuàng)建彈性部署,能夠使用戶更快或更頻繁地發(fā)佈應用和功能。
創(chuàng)建并保證目標數(shù)量的Pod在運行狀態(tài)。
按既定策略滾動升級,同時支持升級暫停、恢復和回滾。選擇滾動升級策略非常靈活,正確的策略對于交付彈性應用程序和基礎架構都是至關重要的。
便利的擴容和縮容。
Service和Ingress
即使在傳統(tǒng)平臺中,為支持應用的高可用,都需要在應用實例之上構建負載均衡。Service和Ingress就是描述負載均衡配置的對象,它允許用戶定義發(fā)佈服務的協(xié)議和端口,并定義Selector選擇后端服務的Pod。Selector本身是一個Label過濾器,它會選擇所有Label與該Selector匹配的Pod作為目標。Kubernetes會為Service和其選擇出來的Pod創(chuàng)建一個關聯(lián)對象,Endpoint里面記錄了所有Pod的IP,以及就緒狀態(tài),這些信息會被相應組件作為期望狀態(tài)進行負載均衡配置。Ingress是在服務的基礎上,定義API網(wǎng)關的對象。通過Ingress,用戶可以定義七層轉發(fā)規(guī)則、網(wǎng)關證書等高級路由功能。
PersistentVolume和PersistentVolumeClaim
PersistentVolume(PV)是集羣中的一塊存儲卷,可由管理員手動設置,或當用戶創(chuàng)建PersistentVolumeClaim(PVC)時根據(jù)StorageClass動態(tài)設置。PV和PVC與Pod生命週期無關。也就是説當Pod中的容器重新啟動、Pod重新調度或者刪除時,PV和PVC不會受到影響,Pod存儲于PV里的數(shù)據(jù)得以保留。對于不同的使用場景,用戶通常需要不同屬性(例如性能、訪問模式等)的PV。所以集羣一般需要提供各種類型的PV,由StorageClass來區(qū)分。一般集羣環(huán)境都設置了默認的StorageClass。如果在PersistentVolumeClaim中未指定StorageClass,則使用羣集的默認StorageClass。
CustomResourceDefinition
自定義資源定義(CRD)是Kubernetes 1.7中引入的一項強大功能,它允許用戶將自己的自定義對象添加到Kubernetes集羣中,當創(chuàng)建新CRD的定義時,APIServer將為指定的每個版本創(chuàng)建一個新的RESTful資源路徑。當集羣中成功地創(chuàng)建了CRD,就可以像Kubernetes原生的資源一樣使用它,利用Kubernetes的所有功能,例如其CLI、安全性、API服務、RBAC等。CRD的定義是集羣范圍內的,CRD的資源對象的作用域可以是命名空間(Namespaced)或者集羣范圍(Cluster-wide)的。與現(xiàn)有的內置對象一樣,刪除Namespace也會刪除該Namespace中所有自定義的對象,但不會刪除CRD的定義。Kubernetes還提供一系列Codegen工具(deepcopy-gen、client-gen、lister-gen、informer-gen等),能夠自動生成該CRD資源的Golang版本的Clientset、Lister及Informer,這為該資源編寫控制器提供了很大便利。
CRD就像數(shù)據(jù)庫的開放式表結構,允許用戶自定義Schema。有了這種開放式設計,使得用戶可以基于CRD定義一切需要的模型,滿足不同業(yè)務的需求。社區(qū)鼓勵基于CRD的業(yè)務抽象,眾多主流的擴展應用都是基于CRD構建的,比如Istio,比如Knative。甚至基于CRD推出了Operator Mode和Operator SDK,可以以極低的開發(fā)成本定義新對象,并構建新對象的控制器。
控制器模式
聲明式系統(tǒng)的工作原理是什麼?當用戶定義了對象的期望狀態(tài),Kubernetes通過何種機制確保實際狀態(tài)與期望狀態(tài)最終保持一致?定義瞭如此多的對象,那麼這些對象是如何聯(lián)動起來,完成一個個業(yè)務流的呢?祕密就是控制器模式,Kubernetes定義了一系列的控制器,事實上幾乎所有的Kubernetes對象都被一個或數(shù)個控制器監(jiān)聽,當對象發(fā)生變化時,控制器會捕獲對象變化并完成配置操作。
Kubernetes的功能組件會在后面章節(jié)中展開,但本節(jié)深入理解控制器模式有助于理解Kubernetes的運作機制。APIServer是Kubernetes的大腦,保存了所有對象和其狀態(tài)。開源項目client-go對控制器的編寫提供了完備的自動化支持,任何Kubernetes對象都可以由client-go創(chuàng)建供控制器使用的Informer()和Lister()接口。如圖所示,控制器的工作流程就是圍繞著Informer()和Lister()的。
Informer()是用來接收資源對象的變化的Event,針對Add、Update和Delete的事件,可注冊相應的EventHandler。在EventHandler內,根據(jù)傳入的object調用controller.KeyFunc計算出字符串key,并把它加入控制器的隊列中。
Lister()是給控制器提供主動查詢資源對象的接口,根據(jù)labels.Selector去指定篩選條件。
控制器模式是一個標準的生產(chǎn)者消費者模式,一方面控制器在啟動后,Informer會監(jiān)聽其所關注的對象變化。一旦對象發(fā)生了創(chuàng)建,更新和刪除等事件,這些事件會由核心組件APIServer推送給控制器??刂破鲿ο蟊4嬖诒镜鼐彺?,并將對象的主鍵推送至消息隊列,此為生產(chǎn)者。
另一方面,控制器會啟動多個工作子線程(Worker),從隊列中依次獲取對象主鍵,并從緩存中讀取完整狀態(tài),按照期望狀態(tài)完成配置更改并將最終狀態(tài)回寫至APIServer,此為消費者。
Kubernetes就是基于此模式保證了整個系統(tǒng)的最終一致性。

Kubernetes運行一組控制器,以使資源的當前狀態(tài)與所需狀態(tài)保持匹配?;谑录捏w系結構,控制器利用事件去觸發(fā)相應的自定義代碼,這部分都是由SharedInformer完成。例如創(chuàng)建Deployment的控制器,其核心代碼如下:
kubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeClient, resyncPeriod)
deploymentInformer := kubeInformerFactory.Apps().V1().Deployments()
deploymentInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: controller.handleObject,
UpdateFunc: func(old, new interface{}) {
newDepl := new.(*appsv1.Deployment)
oldDepl := old.(*appsv1.Deployment)
if newDepl.ResourceVersion == oldDepl.ResourceVersion {
return
}
controller.handleObject(new)
},
DeleteFunc: controller.handleObject,
})
kubeInformerFactory.Start(stopCh)
具體地,如圖所示,SharedInformer有Reflector、Informer、Indexer和Store四個組件。

Reflector是用來監(jiān)聽特定的Kubernetes API資源對象,可以是Kubernetes內建的或者是自定義的資源。具體的實現(xiàn)是通過ListAndWatch的方法。Reflector首先會將資源版本號設置為0,使用List操作獲得指定資源對象,可能會導致本地的緩存相對于etcd里面的內容存在延遲。Reflector再通過Watch操作監(jiān)聽到APIServer處資源對象的版本號變化,并將最新的數(shù)據(jù)放入到Delta FIFO隊列中,使得本地的緩存數(shù)據(jù)與etcd的數(shù)據(jù)保持一致。如果resyncPeriod不為零,那麼Reflector會以resyncPeriod為週期定期執(zhí)行Delta FIFO的Resync函數(shù),這樣就可以使Informer定期處理所有的對象。
Informer是從Delta FIFO隊列中彈出對象,一方面將對象存入本地存儲以供檢索,另一方面觸發(fā)事件以調用資源事件回調函數(shù)??刂破骱罄m(xù)的典型模式是獲取資源對象的key,并將該key排入工作隊列以進行進一步處理。Indexer提供對象的索引功能。
Indexer可以根據(jù)多個索引函數(shù)維護索引。Indexer使用線程安全的數(shù)據(jù)存儲來存儲對象及其鍵。在Store中定義了一個名為MetaNamespaceKeyFunc的默認函數(shù),該函數(shù)生成對象的鍵的格式是<namespace>/<name>的組合。
控制器的協(xié)同工作原理
單個Kubernetes資源對象的變更,觸發(fā)多個控制器對該資源對象的變更進行響應,繼而還能引發(fā)其相關的其他對象發(fā)生變更,從而觸發(fā)其他對象控制器的配置邏輯,這一模式使得整個系統(tǒng)成為聲明式。下圖簡要描述了用戶創(chuàng)建一個Deployment對象時各個控制器是如何協(xié)同工作的。

除APIServer和etcd外,所有Kubernetes組件,不論其名稱是Scheduler,Controller Manager、或是Kubelet,其本質都是一致的,都可以被稱為控制器,因為這些組件中都有一個控制循環(huán)。他們監(jiān)聽APIServer中的對象變更,并在自己關注的對象發(fā)生變更后完成既定的邏輯控制,并將控制邏輯執(zhí)行完成后的結果更新回APIServer,并持久化到etcd中。
APIServer作為集羣的API網(wǎng)關,接收所有來自用戶的請求。用戶發(fā)創(chuàng)建Deployment之后,該請求被髮送至APIServer,經(jīng)過認證鑑權和準入三個環(huán)節(jié),該Deployment對象被保存至etcd。
Controller Manager中的Deployment Controller監(jiān)聽APIServer中所有Deployment的變更事件,此時其捕獲了Deployment的創(chuàng)建事件,并開始執(zhí)行控制邏輯。Deployment Controller讀取Deployment對象的Selector定義,并通過該屬性過濾當前Namespace中所有ReplicaSet對象,并判斷是否有任何ReplicaSet對象的OwnerReference屬性為此Deployment。因為此Deployment剛剛創(chuàng)建,因此沒有滿足此查詢條件的ReplicaSet,于是Deployment Controller會讀取Deployment中定義的podTemplate,并將其做哈希計算,并依照如下約定創(chuàng)建新的ReplicaSet:
創(chuàng)建新的ReplicaSet,將其命名為[deployment-name]-[pod-template-hash]。
更新ReplicaSet,為ReplicaSet添加label,記pod-template-hash值為[計算出的哈希值]。
將Deployment設置為ReplicaSet的OwnerReference。
Deployment Controller將新的ReplicaSet創(chuàng)建請求發(fā)送至APIServer,APIServer同樣的經(jīng)過認證授權和準入步驟,將該對象保存至etcd。
ReplicaSet Controller監(jiān)聽APIServer中所有ReplicaSet對象的變更,新對象的創(chuàng)建令其喚醒并開始執(zhí)行控制邏輯。ReplicaSet Controller讀取ReplicaSet對象的Selector定義,并通過該屬性過濾當前Namespace中所有Pod對象,并判斷是否有任何Pod對象的OwnerReference為該ReplicaSet。因為此ReplicaSet剛剛創(chuàng)建,因此沒有滿足此查詢條件的Pod,于是ReplicaSet會按照如下約定創(chuàng)建Pod:
讀取Replicas定義,Replicas的數(shù)量代表需要創(chuàng)建Pod的數(shù)量。
以ReplicaSet名作為Pod的GenerateName,該屬性會作為Pod名的前綴,Kubernetes在此基礎上加一個隨機字符串作為Pod名。
該ReplicaSet作為Pod的OwnerReference。
ReplicaSet Controller將新建Pod的請求發(fā)送至APIServer,APIServer將Pod悉數(shù)保存。
此時調度器被喚醒,其監(jiān)聽APIServer中所有nodeName為空的Pod,即未經(jīng)過調度的Pod。經(jīng)過一系列的調度算法,不滿足Pod需求的節(jié)點被過濾,符合的節(jié)點按照空閒資源,端口占用情況,實際資源利用率等信息被排序,評分最高的節(jié)點名被更新至nodeName屬性,該同樣經(jīng)APIServer保存至etcd。
最后,運行在Pod被調度節(jié)點的Kubelet監(jiān)聽到有歸屬于自己節(jié)點的新Pod,則開始加載Pod清單,下載Pod所需的配置信息,調用容器運行時接口啟動容器,調用容器網(wǎng)絡接口加載網(wǎng)絡,調用容器存儲接口掛載存儲,并完成Pod的啟動。
Kubernetes就是依靠這樣的聯(lián)動機制,通過分散的業(yè)務控制邏輯滿足用戶需求。從用戶的角度看,只是發(fā)送了一個Deployment創(chuàng)建請求,但事實上,為滿足該需求,可能會牽扯到數(shù)個甚至更多Kubernetes組件。此架構模式的優(yōu)勢是每個組件各司其職,巧妙而靈活,代碼易維護,但帶來的運維複雜度相對較高,此業(yè)務流中有任何組件出現(xiàn)故障,對用戶感受來講,都是Kubernetes不可用。