《深入剖析Kubernetes》總結六:聲明式API

聲明式API與編程範式

想要使用Kubernetes 的 API 對象,需要編寫一個對應的 YAML 文件交給 Kubernetes,而聲明式API,則爲kubectl apply 命令,先 kubectl create,再 replace 的操作,稱爲命令式配置文件操作,並不是聲明式API

kubectl replace 的執行過程,是使用新的 YAML 文件中的 API 對象,替換原有的 API 對象;
kubectl apply執行了一個對原有 API 對象的 PATCH 操作,類似地,kubectl set image 和 kubectl edit 也是對已有 API 對象的修改

意義:
kube-apiserver 在響應命令式請求(如kubectl replace)的時候, 一次只能處理一個寫請求,否則會有產生衝突的可能;
而對於聲明式請求(如kubectl apply),一次能處理多個寫操作,並且具備 Merge 能力

體現其意義的例子:Istio
在這裏插入圖片描述
Envoy 是一個高性能 C++ 網絡代理,Istio把這個代理服務以 sidecar 容器的方式,運行在了每一個被治理的應用 Pod 中;
因爲Pod 裏的所有容器都共享同一個 Network Namespace,所以,Envoy 容器就能夠通過配置 Pod 裏的 iptables 規則,把整個 Pod 的進出流量接管下來;
這時候,Istio 的控制層(Control Plane)裏的 Pilot 組件,就能夠通過調用每個 Envoy 容器的 API,對這個 Envoy 代理進行配置,從而實現微服務治理

雖然Istio 需要在每個 Pod 裏安裝一個 Envoy 容器,但是在微服務治理的過程中對用戶和應用是無感知的,這使用的是k8s的一個功能:Dynamic Admission Control

在Kubernetes 中,當一個 Pod 或者任何一個 API 對象被提交給 APIServer 之後,有一些“初始化”性質的工作需要在它們被 Kubernetes 項目正式處理之前進行,比如,自動爲所有 Pod 加上某些標籤(Labels)
而這個“初始化”操作的實現,藉助的是一個叫作 Admission 的功能;
它其實是 Kubernetes 裏一組被稱爲 Admission Controller 的代碼,可以選擇性地被編譯進 APIServer 中,在 API 對象創建之後會被立刻調用到;
但這就意味着如果想要添加一些自己的規則到 Admission Controller,就會比較困難,因爲要重新編譯並重啓 APIServer;
顯然。這種使用方法對 Istio 來說,影響太大了,所以,Kubernetes額外提供了一種“熱插拔”式的 Admission 機制,它就是 Dynamic Admission Control,也叫作:Initializer

首先Istio 會將這個 Envoy 容器本身的定義,以 ConfigMap 的方式保存在 Kubernetes 當中;
這個 ConfigMap 的 data 部分是一個 Pod 對象的一部分定義,其中有Envoy 容器對應的 containers 字段,以及一個用來聲明 Envoy 配置文件的 volumes 字段;
Initializer 要做的工作,就是把這部分 Envoy 相關的字段,自動添加到用戶提交的 Pod 的 API 對象裏,而用戶提交的 Pod 裏本來就有 containers 字段和 volumes 字段,所 以 Kubernetes 在處理這樣的更新請求時,就必須使用類似於 git merge 這樣的操作,才能將 這兩部分內容合併在一起;
所以說,在 Initializer 更新用戶的 Pod 對象的時候,必須使用 PATCH API 來完成。而這種 PATCH API,正是聲明式 API 最主要的能力,也就體現了上述所說的意義

在使用 Initializer 的流程中,最核心的步驟就是Initializer“自定義控制器”的編寫過程;
它遵循的,正是標準的“Kubernetes 編程範式”,即:如何使用控制器模式,同 Kubernetes 裏 API 對象的“增、刪、改、查”進行協 作,進而完成用戶業務邏輯的編寫過程。

  • 總結

聲明式 API是 Kubernetes 項目編排能力“賴以生存”的核心所在:

首先,“聲明式”指的就是隻需要提交一個定義好的 API 對象來“聲明”,所期望的狀態是什麼樣子;

其次,“聲明式 API”允許有多個 API 寫端,以 PATCH 的方式對 API 對象進行修改,而無需關心本地原始 YAML 文件的內容;

最後,也是最重要的,有了上述兩個能力,Kubernetes 項目纔可以基於對 API 對象的增、 刪、改、查,在完全無需外界干預的情況下,完成對“實際狀態”和“期望狀態”的調諧 (Reconcile)過程。

深入解析聲明式API(一)

聲明式API工作原理

首先知道一下一個 API 對象在 Etcd 裏的完整資源路徑,是由:Group(API 組)、 Version(API 版本)和 Resource(API 資源類型)三個部分組成的,可以用如下圖的樹形結構表示出來:
在這裏插入圖片描述
API對象的組織方式是層層遞進的,Kubernetes會對Group、Version和Resource進行解析,也就是層層匹配,得到相應的對象定義,如Cronjob(Pod、Node 等核心API對象不需要Group,直接匹配Version)

把YAML 文件提交給 Kubernetes 之後,創建出 API 對象的流程:以創建 CronJob爲例

1 發起創建 CronJob 的 POST 請求後,編寫的 YAML 的信息就被提交給 了 APIServer

2 APIServe過濾這個請求,並完成一些前置性的工作,比如授權、超時 處理、審計等

3 請求進入 MUX 和 Routes 流程;
MUX 和 Routes 是 APIServer 完成 URL 和 Handler 綁定的場所;
APIServer 的 Handler 要做的事 情,就是按照層層匹配的過程,找到對應的 CronJob 類型定義

4 APIServer根據CronJob 類型定義,使用用戶提交的 YAML 文件裏的字段,創建一個 CronJob 對象;
APIServer 會進行一個 Convert 工作,即把用戶提交的 YAML 文件,轉換成一個叫作 Super Version 的對象,它正是該 API 資源類型所有版本的字段全集,這樣用戶提交的不同版本的 YAML 文件,就都可以用這個 Super Version 對象來進行處理了

5 先後進行 Admission() 和 Validation() 操作;
Admission Controller 和 Initializer都屬於 Admission 的內容;
Validation負責驗證這個對象裏的各個字段是否合法,這個被驗證過的 API 對象,都保存在了 APIServer 裏一個叫作 Registry 的數據結構中,也就是說,只要一個 API 對象的定義能 在 Registry 裏查到,它就是一個有效的 Kubernetes API 對象

6 把驗證過的 API 對象轉換成用戶最初提交的版本,進行序列化操作,並調 用 Etcd 的 API 把它保存起來

自定義API對象

如果想要添加自定義API資源類型,建議使用CRD( Custom Resource Definition),它允許用戶在 Kubernetes 中添加一個跟 Pod、Node 類似的、新的 API 資源類型,即:自定義 API 資源

使用CRD創建出自定義API對象後,就是爲這個 API 對象編寫一個自定義控制器(Custom Controller),這樣, Kubernetes 才能根據 自定義 API 對象的“增、刪、改”操作,在真實環境中做出相應的響應

編寫自定義控制器分爲三個過程:編寫 main 函數、編寫自定義控制器的定義,以編寫控制器裏的業務邏輯

自定義控制器的工作流程:
在這裏插入圖片描述
1 首先從 Kubernetes 的 APIServer 裏獲取它所關心的對象,也就是自定義的控制器對象

這個操作,依靠的是一個叫作 Informer(通知器)的代碼庫完成的;
Informer 與 API 對象是一一對應的,所以需要傳遞給自定義控制器一個Informer;
Informer是一個帶有本地緩存和索引機制的、可以註冊 EventHandler 的 client。它是自定義控制器跟 APIServer 進行數據同步的重要組件。

創建Informer的時候需要傳一個Client,Informer 正是使用Client,跟 APIServer 建立了連接;
真正負責維護這個連接的是 Informer 所使用的 Reflector 包

Reflector 使用的是一種叫作ListAndWatch的方法,來“獲取”並“監聽”這些API對象實例的變化(Informer通過 ListAndWatch,把 APIServer 中的 API 對象緩存在了本地,並負責更新和維護這個緩存。)

在 ListAndWatch 機制下,一旦 APIServer 端有新的API對象實例被創建、刪除或者更新, Reflector 都會收到“事件通知”;
這時,該事件及它對應的 API 對象這個組合,就被稱爲增量 (Delta),它會被放進一個 Delta FIFO Queue(即:增量先進先出隊列)中;

Informe 會不斷地從 Delta FIFO Queue 裏讀取(Pop)增量。每拿到一個增量,Informer 就會判斷這個增量裏的事件類型,然後創建或者更新本地對象的緩存;
這個緩存,在 Kubernetes 裏一般被叫作 Store

Informer 的第二個職責,則是根據這些事件的類型,觸發事先註冊好的 ResourceEventHandler;
這些 Handler,需要在創建控制器的時候註冊給它對應的 Informer

2 Informer 與要編寫的控制循環之間,則使用了一個工作隊列來進行協同,防止控制循環執行過慢把Informer拖死

3 接下來就是熟悉的控制循環的邏輯了

總結起來就是根據Informer來拉取API對象,通過WorkQueue與控制訊護進行交互

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章