Kubernetes聲明式API
其中kubectl是我們使用最多的命令行工具。K8s官方對kubectl管理K8s對象的技術做了如下表的總結:
假如我們現在要在名為test的namespace下創建一個Deployment對象來運行nginx容器,接著對nginx的副本數量進行更改為5,三種方式流程如下:
1)指令式命令方式
創建deployment對象:
kubectl create deployment nginx-deployment --image nginx -n test
更改副本數:
kubectl scale deployment nginx-deployment --replicas 5 -n test
2)指令式對象配置方式
首先創建配置文件定義,定義如圖1-1所示:
圖1-1 ngin.yaml配置
創建deployment對象:
kubectl create -f nginx.yaml
將配置文件中的replicas由2改成5,執行命令更改副本數:
kubectl replace -f nginx.yaml
3)聲明式對象配置方式
使用圖1-1的配置,創建deployment對象:
kubectl apply -f nginx.yaml
將配置文件中的replicas由2改成5,執行命令更改副本數:
kubectl apply -f nginx.yaml
后兩者對象配置的方式提供了用于創建新對象的模板,并且能夠提供與更改關聯的審核跟蹤,命令除了實時內容外,不能提供記錄源。所以在生產項目的情況下,我們使用 K8s 對象的方式往往是通過編寫對應的.yaml文件交給 K8s(聲明式API的形式),而不是直接使用指令式命令。
我們配置文件只是聲明了想要創建的K8s對象的信息以及想要它們達到的期望狀態,這是一種聲明式API的交互方式。不同于吃飯、睡覺等明確的指令(這種方式也稱為:命令式API),使用配置創建的對象的交互感覺類似顧客與實施方的交流模式,因為顧客跟實施方相比,往往不清楚為了達到目標而需要進行的操作,比如說:給我搭建一個標準的籃球場。
聲明式API往往預備了合并多個操作的能力,我們在搭建球場時可以同時進行地板的鋪設和大屏幕的安裝,命令式API在并發訪問情況下會十分復雜、低效,往往通過加鎖才能保證最后結果的可預見性,孩子必須吃完了飯才能睡覺,而不是睡覺的時候吃飯。并且聲明式API天然地記錄了現在和最終狀態,便于對結果進行檢查。每天必須吃3碗飯和保持體重在50kg,不同體重的人吃三碗飯導致的體重結果是無法估計的,不同的人但卻可以通過調整熱量的攝入而達到50kg的目標。這也是為什么K8s中聲明式API如此重要的原因,而K8s聲明式API描述的主體便是K8s對象,正如開頭所說它是我們操作K8s的媒介和接口。
那么K8s對象在K8s API中是如何表示的,我們怎么用.yaml文件描述它們,當把一個.yaml文件提交給 K8s后,它究竟又是如何創建出一個K8s對象的呢?
01K8s對象
K8s對象指的是K8s系統中持久化后的實體,K8s用這些實體去表示整個集群的運行狀態。它們特別描述了以下信息:
- 哪些容器化應用在運行,以及運行在哪些節點上
- 可以被應用使用的資源
應用運行時的表現策略,如重啟策略、升級策略以及容錯策略。
K8s對象的操作(創建、修改、刪除)都需要使用K8s API,無論是使用kubectl命令行還是程序中使用客戶端,最終都會調用K8s API。
每個K8s對象都包含兩個嵌套的對象字段spec(規約)和status(狀態)。其中spec描述的是K8s在創建完畢后應該達到的期望狀態,如期望開啟多少個副本,status描述了對象的當前狀態,它由K8s系統和組件進行監管設置并積極地使它與spec相匹配。這兩個字段也是對于K8s聲明式API實現最為關鍵的一環。
02K8s是如何描述和管理API對象的
我們在創建 Kubernetes對象時,必須提供對象的spec字段,告知K8s我們期望這個對象所達到狀態,同時也必須提供一些關于對象的基本信息(例如名稱)。不論我們是通過kubectl命令行或者是客戶端調用K8s API創建對象,API 請求必須在請求體中包含JSON 格式的信息。大多數情況下,我們都以 .yaml 文件的形式提供這些信息。kubectl 在發起API請求時,再將這些信息轉換成 JSON 格式。以圖1-1舉例,圖中展示了使用.yaml來描述K8s對象的必需字段和對象規約,以下是其中必需字段的簡要說明:
- apiVersion - 創建該對象所使用的K8s API版本
- kind - 想要創建的對象類別
- metadata – 幫助唯一性表示對象的一些數據,包括一個name字符串、UID和可選的namespace
- spec – 你期望該對象所達到的狀態
其中spec的精確格式對于不同類別的K8s對象來說往往是不同的,它是對管理對象詳細描述的主體,會被K8s持久化到etcd中保存。如果spec被刪除,那么該對象也會從系統中被刪除。
一個API對象在etcd里的完整資源路徑是由Group(API組)、Version(API版本)、Resource(API資源類型)三個部分組成。具體案例如圖1-2所示:
圖1-2 K8s的API結構
從圖中我們可以看出,在K8s內API對象的組織方式是層層遞進的,這里我們不去深究具體怎么使用這些api去檢索需要的資源,感興趣的讀者可以參考如下鏈接地址,這是官方的詳細說明:https://kubernetes.io/zh/docs/reference/using-api/api-concepts/
假定現在我們要創建一個CronJob對象,且它的yaml文件開頭部分如下所示:
apiVersion: batch/v2alpha1
kind: CronJob
...
其中,batch就表示它的API組(Group),v2alpha1就是它的API版本(Version),CronJob即是它所屬API資源類型(Resource)conjobs下的一個具體類別。而其整體創建流程下圖1-3所示:
圖1-3 持久化流程
提交階段
當我們的yaml提交給K8s集群后(不論是通過程序客戶端還是kubectl),請求會被轉化成一個POST,接著交由K8s的API Server進行處理。
過濾和前置處理階段
API Server首先會對請求進行過濾,獲取其中的有效信息,再完成諸如:授權、超時處理、審計等前置性工作。
路由查找階段
主要是借由MUX和Routes路由查找CronJob的定義,具體流程大致為:1. 匹配Group。對于CronJob這類非核心API對象,K8s會在如圖1-2所示的/apis層級內查找它的所屬組(核心對象,如Pod、Node,隸屬于/api層級且它們的組為””),根據yaml的開頭信息,查找到/apis/batch。2. 匹配版本號。接著,根據v2alpha1這個版本號進一步匹配到/apis/batch/ v2alpha1這個路徑,在K8s中,同種API對象可以有多個版本,這是K8s給予用戶管理共存多版本API的手段。3.匹配資源類型。最后匹配資源類型cronjobs,這時K8s便可明確知道自己所需要創建的具體資源類型為CronJob。
創建資源階段
在獲取到CornJob的類型定義后,API Server會進行一個Convert工作:把用戶提交的yaml轉換成一個Super Version的對象,它是該API資源類型所有版本的字段全集,之后用戶提交其他版本的yaml,也都可以用這個Super Version對象來處理。接著,再進行Admit處理,主要是在對象創建完成后,注入一些公共處理邏輯,例如加入一些標簽Label。最后,對這個對象里各個字段的合法性進行校驗。
定義存儲階段
若通過了Validate,會被API Server保存在名為Registry的結構中。它包含了對一個有效K8s API對象的定義。
持久化階段
API Server會把通過驗證的API對象由Super Version版本重新轉換為最初提交的版本,再進行序列化操作,最后調用etcd的API將它們變為K8s內的實體對象。
此時,對象已經被創建持久化完成,那么它又是如何一步步達到我們所期望的狀態的呢?K8s對象章節所提及到的spec和status信息正是這個解決問題的基礎,K8s基于此采用了控制器模式來進行實現。
03控制器模式實現聲明式API的sepc
K8s是通過控制器模式來實現聲明式API中描述的spec狀態靠攏的,而控制型模式最核心的就是“控制循環“的概念。在K8s中,控制器是一個控制循環,它通過API服務器監視集群的共享狀態,并進行更改,試圖將當前狀態(status)轉移到期望狀態(spec)。
實際狀態的獲取往往是通過K8s本身的一些組件,例如定時調用API獲取資源狀態信息,或kubelet通過心跳匯報的容器狀態和節點狀態。所需期望狀態,往往由yaml文件的spec字段進行定義提交持久化到K8s的etcd中。圖1-4大致表述了K8s中控制循環的過程:
圖1-4控制循環
- 比較spec和status,若相同,不進行操作,繼續進行1,否則輸出不同點,進入2;
- 控制器(Controller)根據不同點,對系統發出調控操作,系統執行操作,進入3;
- 系統上報當前系統狀態給傳感器(Sensor)或者傳感器主動拉取狀態,輸出當前狀態status,返回1。
目的是使status不斷趨近達到spec,從而完成yaml聲明狀態的實現。從圖中也可以看出Sensor和Controller在整個循環中扮演了極其重要的角色。
在K8s中,Sensor主要由Reflector、Informer、Indexer 三個組件構成。
Sensor的整體工作流程如圖1-5所示:
圖1-5 Sensor組件
1.Reflector通過List和Watch操作從API Server 獲取各個資源對象的狀態。其中,List用來描述返回多個資源的集合,主要用于系統資源的全量更新;Watch用于客戶端獲取當前狀態,然后訂閱后續更改,主要用于系統資源的增量更新;
2.Reflector在獲取新的資源數據后,會往Delta隊列中推入一個Delta記錄,這個記錄包含了資源對象本身的信息以及資源對象事件的類型。Delta隊列可以保證同一個對象在隊列中僅有一條記錄,從而避免Reflector重新List和Watch的時候產生重復的記錄。
3.Informer組件不斷地從Delta隊列中彈出delta記錄,然后把資源對象交給indexer。indexer把資源記錄在一個默認使用命名空間做索引的緩存中,記錄操作是線程安全的并且緩存是可以在Controller Manager或多個Controller間共享的。之后,informer再把這個事件交給事件回調函數。
Controller的主要流程如圖1-6所示:
圖1-6 controller組件
控制循環中的Controller(控制器)組件主要由事件處理函數以及worker組成,事件處理函數根據控制器邏輯決定是否要進行相應的回調處理。若需要進行事件處理,它會把事件關聯資源的命名空間和資源名字當成一個標識塞入工作隊列中,由后續worker池中的一個worker來處理,工作隊列會對存儲的對象進行去重,從而避免多個woker處理同一個資源的情況。
worker 在處理資源對象時,一般需要用資源的名字來重新獲得最新的資源數據,之后使用這些數據會有三種情況:
- 創建或者更新資源對象,
- 調用其他的外部服務,
- worker處理失敗,把資源的名字重新加入到工作隊列中進行重試。
現在假如我們需要將圖1-1所配置的nginx副本由2擴容為3個進行舉例說明,方便讀者理解,整體流程如圖1-7所示:
圖1-7副本擴展整體流程圖
首先,Reflector會watch到Deployment發生變化,在delta隊列中塞入了對象是nginx-deployment并且類型是更新的記錄。
Informer一方面把新的Deployment更新到緩存中,并用名為test的namespace作為索引。另外一方面,調用Update的回調函數,Deployment控制器發現Deployment發生變化后會把字符串test/nginx-deployment塞入到工作隊列中,工作隊列后的一個 worker從工作隊列中取到了test/nginx-deployment這個字符串的key,并且從緩存中取到了最新的Deployment數據。
worker通過比較Deployment中spec和status里的數值,發現需要對這個Deployment進行擴容,因此Deployment的worker創建了一個Pod,這個pod中的Ownereference指向了nginx-deployment。
然后Reflector Watch到的Pod新增事件,在delta隊列中額外加入了Add類型的deta記錄。
Informer一方面把新的Pod記錄通過Indexer存儲到了緩存中,另一方面調用了 Deployment控制器的Add回調函數,Add回調函數通過檢查pod3的ownerReferences 找到了對應的Deployment為nginx-deployment,把test/nginx-deployment字符串塞入到了工作隊列中。
Deployment的woker在得到新的工作項之后,從緩存中取到新的Deployment記錄,并得到了其所有的Pod信息,對比發現Deployment的狀態不是最新的(關聯的pod數量已經發生改變)。此時 Deployment更新 status使得spec和 status達成一致。
04總結
- 由于聲明式API具有天然的記錄初始和最終狀態等特性,K8s采用了其作為最關鍵部分的API交互方式。
- 在生產環境中,對于K8s的管理往往是通過管理器API對象,而API對象又是通過yaml進行描述的,其中的spec和status是最為關鍵的信息,K8s會通過定位和路由把一個yaml描述的信息持久化到etcd中成為實體。
- K8s所采用的控制器模式,是由聲明式API驅動的。它是實現K8s聲明式API的關鍵,資源對應的控制器結合感應器通過控制循環,異步地將控制系統向設置的終態趨近。
- 因為控制器是自主運行的,使得整體系統的自動化和無人值守成為可能。
參考文獻
[1] https://edu.aliyun.com/lesson_1651_18353?spm=5176.10731542.0.0.5f9e7abdivOOXz#_18353
[2] https://kubernetes.io/zh/docs/concepts/overview/working-with-objects/object-management/
[3] https://kubernetes.io/zh/docs/reference/using-api/api-concepts/#efficient-detection-of-changes
[4] https://kubernetes.io/zh/docs/concepts/overview/working-with-objects/kubernetes-objects/
[5] http://borismattijssen.github.io/articles/kubernetes-informers-controllers-reflectors-stores
[6] https://ghostwritten.blog.csdn.net/article/details/119155497
[7] https://www.codercto.com/a/65740.html?