聲明式API
所謂“聲明式”,指的就是我只需要提交一個定義好的 API 對象來“聲明”,我所期望的狀態是什么樣子
“聲明式 API”允許有多個 API 寫端,以 PATCH 的方式對 API 對象進行修改,而無需關心本地原始 YAML 文件的內容
Kubernetes 項目才可以基於對 API 對象的增、刪、改、查,在完全無需外界干預的情況下,完成對“實際狀態”和“期望狀態”的調諧(Reconcile)過程
聲明式 API,才是 Kubernetes 項目編排能力“賴以生存”的核心所在
AdmissionControl機制
在K8S中 當一個Pod或者任何一個API對象提交給APIServer之后 總需要做一些
初始化的工作 比如在自動為Pod添加上某些標簽
這些功能的實現依賴於一組Admission Controller來實現 可以選擇性的編譯到
APIServer中 在API對象創建之后會被立即調用
需要重新編譯自己的APIServer添加自己的規則 比較麻煩
熱插拔Admission機制(Dynamic Admission)
Istio實現機制
編寫一個用來為所有Pod自動注入自己定義的容器的Initializer
這個Initializer的定義會以ConfigMap的方式進行保存在集群中
在Initializer更新用戶的Pod對象的時候,必須使用PATCH API來完成
Istio將一個編寫好Initializer做為一個Pod運行在k8s集群中
在Pod YAML文件提交給K8S之后 在創建好的Pod的API對象上自動添加Envoy容器配置
Initializer初始化器介紹
Initializer可以是以一個Pod的形式運行在集群當中
Initializer就是初始化器的意思 就是在任何一個API對象剛剛創建成功后馬上調用初始化器給這個對象添加一些自定義的屬性
Initializer 要做的工作,就是把這部分單獨定義相關的字段,自動添加到用戶提交的Pod的API對象里.可是,用戶提交的 Pod 里本來就有containers字段和volumes字段
所以Kubernetes 在處理這樣的更新請求時,就必須使用類似於git merge 這樣的操作,才能將這兩部分內容合並在一起 最后按照合並后的結果創建容器和掛載卷等
Initializer在更新用戶pod對象的時候 必須使用PATCH API來完成 而PATCH API正是聲明式API的最主要的能力
k8s能夠對API對象進行在線更新的能力
Initializer邏輯流程
1.首先從ConfigMap中拿到相關數據創建一個空的Pod對象
2.使用新舊兩個Pod對象做為參數調用k8s中TwoWayMergePatch返回patch數據
3.通過client發起PATCH請求更新原來的Pod API對象,此時Pod還只是個API對象 沒有被真正的創建出來
4.根據Merge后的Pod對象定義 創建出Pod
API對象都有revision所以apiserver處理merge的流程跟git Server是一樣的
Initializer和Preset都能注入Pod配置
preset相當於initializer的子集,比較適合在發布流程里處理比較簡單的情況 initializer是要開發代碼
Initializer與新的pod在git merge沖突時就不會出入成功
kubectl apply是通過mvcc實現的並發寫
envoy和nginx比的優點
envoy的設計支持熱更新和熱重啟很適合聲明式規則的開發范式
envoy提供了api形式的配置入口,更方便做流量治理
envoy編程友好的api,方便容器化,配置方便
nginx的reload需要把worker進程退出比較面向命令
initializer步驟發生在Pod創建之前
initializer發生在admission階段,這個階段完成后pod才會創建出來.並不是先創建好原始Pod然后再把initializer里面對原始Pod的修改進行滾動更新
一個用戶提交的 Pod 對象里,就會被自動加上 Envoy容器相關的字段 使用 Kubernetes的Initializer特性,完成 Envoy 容器“自動注入”的原理
聲明式API設計
API對象在Etcd里的完整路徑由Group/Version/Resource組成 同一種API對象可以有多個Version k8s進行API版本化的重要手段
1.首先匹配API對象的組
Pod Node等核心API對象是不需要Group的 它們的Group是"" 直接從/api開始查找
2.根據完整路徑找到k8s的類型定義后使用用戶提交的YAML文件中的字段創建一個實例
在創建實例的過程中會進行一個Convert操作 把用戶提交的YAML文件轉成一個super version對象
它是API資源類型所有版本的字段全集 方便用戶提交不同API版本的YAML
3.進行API對象的Initializer操作和Validation操作
validation操作驗證對象中各個字段的合法性 驗證后保存到Registry數據結構中 一個API對象的定義能在Registry里能查到 那么它就是一個有效的k8s API對象
4.把super version對象轉換成用戶提交版本的對象 序列化后保存到etcd中
自定義API資源類型(CRD)
成功創建CRD之后 只是完成聲明式API的一半工作
因為還沒有為這個CRD創建控制器 所以在k8s中只能對這個CRD進行增刪改查操作
但無法對CRD對象發生增刪改的操作時觸發對應的業務邏輯 必須為每個CRD創建一個對應的CRD控制器來實現當CRD對象發生變化時候觸發控制器里面的業務邏輯代碼
“registry”的作用就是注冊一個類型(Type)給 APIServer.其中Network(CRD)資源類型在服務器端的注冊的工作APIServer 會自動幫我們完成
但與之對應的,我們還需要讓客戶端也能“知道”Network 資源類型的定義.這就需要我們在項目里添加一個 register.go 文件(v1/register.go)
自定義控制器
1.編寫main函數
2.編寫自定義控制器定義
3.編寫控制器的業務邏輯
Informer機制
Reflector使用ListAndWatch方法來獲取並監聽對象實例的變化 一旦任何一個實例有任何變化Reflector都會收到事件通知
收到通知后把(事件和對象)的組合存入一個先進先出的隊列中
Informer不斷從隊列中讀取對象 判斷事件的類型 然后創建或者更新本地對象的緩存 同步本地緩存的工作 是Informer的重要職責
Informer的第二個職責 就是根據事件的類型 觸發事先注冊好的ResourceEventHandler
通過監聽到的事件變化,Informer 就可以實時地更新本地緩存,並且調用這些事件對應的EventHandler了
每經過 resyncPeriod 指定的時間,Informer 維護的本地緩存,都會使用最近一次 LIST 返回的結果強制更新一次,從而保證緩存的有效性
所謂的 Informer,就是一個自帶緩存和索引機制,可以觸發 Handler 的客戶端庫。
這個本地緩存在 Kubernetes 中一般被稱為 Store,索引一般被稱為 Index.Informer 使用了 Reflector 包,它是一個可以通過 ListAndWatch 機制獲取並監視 API 對象變化的客 戶端封裝.Reflector 和 Informer 之間,用到了一個“增量先進先出隊列”進行協同.
而 Informer與你要編寫的控制循環之間,則使用了一個工作隊列來進行協同.在實際應用中,除了控制循環之外的所有代碼,實際上都是 Kubernetes 為你自動生成的
控制循環的邏輯
1.等待Informer完成一次本地緩存的數據同步操作
2.通過goroutine啟動一個或多個無限循環任務
3.在每一個循環周期中 執行的就是用戶真正關心的業務邏輯代碼
1.如果控制循環在Informer緩存中獲取不到相應的對象的信息 說明要執行刪除邏輯
2.如果從緩存中獲取到對象信息 就執行控制器模式對比期望狀態和實際狀態 期望狀態來自於etcd 實際狀態來自於集群本身
這套流程不僅可以用在自定義API資源上也完全可以用在Kubernetes原生的默認API 對象上
這就使得在這個自定義控制器里面,我可以通過對自定義API對象和默認API 對象進行協同,從而實現更加復雜的編排功能
只需要關注如何拿到“實際狀態”,然后如何拿它去跟“期望狀態”做對比,從而決定接下來要做的業務邏輯即可 這個就是Kubernetes API編程范式的核心思想