Kubernetes聲明式API與編程範式

聲明式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. 接下來就是熟悉的控制循環的邏輯了

參考鏈接:

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

//www.pianshen.com/article/40791568713/