Kubernetes宣告式API與程式設計正規化

尹瑞星發表於2021-04-04

宣告式API vs 命令時API

計算機系統是分層的,也就是下層做一些支援的工作,暴露介面給上層用。注意:語言的本質是一種介面。

計算機的最下層是CPU指令,其本質就是用“變數定義+順序執行+分支判斷+迴圈”所表達的邏輯過程。計算機應用的最上層是實現人類社會的某種功能。所以所有計算機編碼的過程,就是用邏輯表達現實的過程。層與層之間定義的藉口,越接近現實的表達就叫越“宣告式”(declarative),越接近計算機的執行過程就叫越“命令式”(imperative)。注意這不是絕對的概念,而是相對的概念。

當介面越是在表達“要什麼”,就是越宣告式;越是在表達“要怎樣”,就是越命令式。SQL就是在表達要什麼(資料),而不是表達怎麼弄出我要的資料,所以它就很“宣告式”。C++就比C更宣告式,因為物件導向本身就是一種宣告式的體現。HTML也很宣告式,它只描述我要一張什麼樣的表,並不表達怎麼弄出一張表。

越是宣告式,意味著下層要做更多的東西,或者說能力越強。也意味著效率的損失。越是命令式,意味著上層對下層有更多的操作空間,可以按照自己特定的需求要求下層按照某種方式來處理。

Kubernetes宣告式API

想要使用Kubernetes 的 API 物件,需要編寫一個對應的 YAML 檔案交給 Kubernetes,而宣告式API,則為kubectl apply 命令。而先 kubectl create,再 replace 的操作,稱為命令式配置檔案操作,並不是宣告式API。

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

宣告式 API是 Kubernetes 專案編排能力“賴以生存”的核心所在:

  • 首先,“宣告式”就是提交一個定義好的API物件來宣告所期望的狀態是什麼

  • 其次,宣告式API允許有多個API寫端,以PATCH的方式對API物件進行修改,而無需關心本地原始YAML檔案的內容

    RESTful 使用POST來建立一個資源,使用PUT或者PATCH來更新一個資源
    
    區別是:
    – PUT用來整體更新一個資源,所以請求中必須包含完整的資源資訊。如果缺少部分資訊,會導致這部分資料被更新為NULL。
    – PATCH則是部分更新。僅更新提供的欄位,請求中缺少的欄位仍保持不變
    
  • 最後,也是最重要的,有了上述兩個能力,Kubernetees專案才可以基於對API物件的增、刪、改、查在完全無需外界干預的情況下,完成對“實際狀態”和“期望狀態”的調諧(Reconcile)過程。

工作原理

首先知道一下一個 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的資訊就被提交給api-server

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

  3. 請求進入mux和routes流程,mux和routees是api-server完成url和handler繫結的場所,api-server的Handler要做的事情就是按照層層匹配的過程,找到對應的CronJob型別定義

  4. Api-server根據crontJob型別定義,使用使用者提交的YAML檔案裡的欄位,建立一個CrontJob物件

    Api-server會進行一個convert工作,即把使用者提交的YAML檔案轉換成一個叫做Super Version的物件,它正是該API資源型別所有版本的欄位全集,這樣使用者提交的不同版本的YAML就都可以用這個Super Version物件來進行處理了

  5. 先後進行admission()和validation()操作

    Admission Controller和Initializer都屬於Admission的內容,Validation負責驗證這個物件裡的各個欄位是否合法,這個被驗證過的API物件,都儲存在來api-server裡一個叫做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. 接下來就是熟悉的控制迴圈的邏輯了

參考連結:

https://www.zhihu.com/question/22285830/answer/469177185

https://www.pianshen.com/article/40791568713/

相關文章