如何擴展Kubernetes API?
Django是一個通用的Web框架,而Kubernetes則是一個容器編排器。顯然,不同的項目根本不應該進行比較。然而,在本系列文章中,我試圖揭開Kubernetes的神秘面紗,并展示它的API是一個非常普通的HTTP API,并且可以以相當熟悉的方式進行擴展。
有很多方法可以用自定義功能擴展Kubernetes,從編寫kubectl插件到實現調度器擴展。詳細的擴展點列表可以在官方文檔中找到,但如果有一個基于這種方法的排名,我敢打賭開發自定義控制器或操作符,如果你愿意的話,會勝出。
Kubernetes控制器背后的思想很簡單,但很強大——你描述系統的理想狀態,將其持久化到Kubernetes,然后等待控制器完成它們的工作,使集群的實際狀態足夠接近理想狀態(或報告故障)。
然而,雖然控制器得到了很多媒體的關注,但在我看來,編寫自定義控制器大多數時候應該被視為擴展Kubernetes API更廣泛任務的一部分(可能是可選的)。但是要注意到這一點,需要對典型的工作流相當的熟悉。
自定義控制器
雖然Kubernetes社區提供了一個更廣泛、更通用的控制器定義,但在與Kubernetes控制器打交道一年多后,我提出了以下解釋,涵蓋了迄今為止我見過的大多數自定義控制器:
- 控制器實際上是一個主動協調過程(讀取:無限循環),它讀取所需的狀態并相應地更新實際狀態。
- 然而,一個控制器通常被綁定到單一的Kubernetes資源類型。我們稱它為控制器的主要資源。
- 控制器偵聽系統事件:最重要的是,創建或修改主資源對象,但也改變其他(次要或擁有)資源、計時器事件,等等。
- 無論事件的性質如何,總是可以將事件歸因于一個或多個主資源類型的對象。
事件發生后,控制器會從API中逐一讀取相應的主要資源對象,檢查各對象的規范屬性(即所需狀態),應用變更來讓系統更接近于所需狀態,再使用此狀態反過來更新各個對象。
控制器可以將任何資源類型作為其主要資源,包括pods、jobs或services等內置資源。問題是,大多數(如果不是所有的話)內置資源已經有相應的內置控制器。因此,定制控制器通常是為定制資源編寫的,以避免多個控制器更新共享對象的狀態。
從本質上講,什么是資源?用Kubernetes自己的話說:
資源是Kubernetes API中的一個端點,它存儲特定類型的API對象集合;例如,內置的Pods資源包含一個Pod對象的集合。
因此,如果資源僅僅是Kubernetes API端點,那么為資源編寫控制器只是一種將請求處理程序綁定到API端點的奇特方式!
每當有對主要資源端點的創建或修改請求時,(特別是)控制器的邏輯就會被觸發。觸發控制循環迭代的主資源類型的實例作為請求參數(對象的規格字段)和響應狀態(對象的狀態字段)的數據傳輸對象。
基于控制器的處理程序與更傳統的請求處理程序之間的主要區別在于處理與實際的API請求是異步發生的。創建或修改Kubernetes對象的API請求(如POST、PUT、PATCH)只是為控制器調度工作(通過記錄意圖),而獲取對象的API請求(GET、WATCH)用于返回處理狀態。
自定義資源
如果向Kubernetes API添加請求處理程序是通過編寫控制器進行的,那么如何添加新的API端點呢?
在回答這個問題之前,重要的是要理解Kubernetes API中有兩種類型的端點:
- 第一種類型是服務于Kubernetes對象集合(即持久的Kubernetes實體)的端點,如Pods、ConfigMaps、Services等。絕大多數API端點都屬于這種類型。
- 第二種基本上是其他所有東西。像/metrics、/logs或/apis這樣的端點是其他類型端點的最突出的例子。這些端點要么被嵌入到Kubernetes API服務器中,要么使用API聚合層實現。
控制器通常使用第一種類型的端點。那么,如何將服務于用戶定義對象類型的新端點添加到API中呢?
- 首先,需要編寫CustomResourceDefinition(CRD)。CRD本身是一個描述新的自定義資源的對象。最重要的是,CRD應該包含新資源類型的名稱和版本化對象模式(即字段)。
- 然后,需要將CRD提交給集群。將CRD應用到集群會創建一個服務于自定義資源類型的新的Kubernetes API端點。就這么簡單!
自定義資源類型的對象的外觀和行為很像內置的Kubernetes對象,它們受益于常見的API特性(CRUD、字段驗證、發現等),同時,它們具有解決自定義用例所需的屬性。
自定義資源本身可能很有用。通過注冊一個新的資源,你立即獲得(一些有限的)持久性,開箱即用的字段驗證,RBAC,等等。然而,大多數情況下,自定義資源的創建伴隨著自定義控制器。
準入鉤子(Webhooks)
回到請求處理……
Kubernetes控制器的超能力歸因于它們的異步特性,但這也是它們最大的局限性。對Kubernetes API的創建、修改或刪除對象的請求作為意圖的記錄工作——實際的處理邏輯被延遲到下一次控制循環迭代。但是如果需要同步請求處理呢?
這在Kubernetes也是可能的!但為此,你需要介入Kubernetes API服務器的資源請求處理。
當請求到達API服務器時,在更改持久化到etcd(或類似的)之前,會經過以下幾個階段:
- 身份驗證和授權
- 準入控制
- 對象模式驗證
- 驗證許可?
上面的大部分(或者全部?)階段都可以用自定義邏輯進行擴展!?
因此,配置一個許可webhook將使Kubernetes API服務器在實際持久化它之前,將資源實例(包裝在一個稱為AdmissionReview的信封中)發送到一個自定義HTTPS端點。
調用一個許可webhook端點會阻塞Kubernetes API服務器的請求處理。準入webhook的實現可以執行任意的驗證邏輯,用非平凡的默認值填充對象的屬性,對對象進行標簽或注釋,甚至修改其他Kubernetes資源或對外部系統進行更改!
一般來說,應該避免webhook處理程序中的副作用。在webhook中,不可能知道對象實際上是會被處理鏈持久化還是拒絕。如果對資源的操作被其中一個檢查拒絕,則需要以某種方式恢復前面步驟所做的任何更改。
因此,webhook是將同步請求處理程序綁定到Kubernetes API端點的一種簡單方法。這就完成了Kubernetes API與任何其他傳統HTTP API在特性上的同一性。
總結
讓我們試著把所有東西都放在一張圖上。下面是Kubernetes API擴展工作流的描述:
希望大家現在已經清楚,自定義控制器只是擴展Kubernetes API這一更大任務的一部分。
我希望,在以上的解釋之后,你也注意到Kubernetes與我們都熟悉的老式技術沒有什么不同:
- Kubernetes自定義資源只是一種向API添加新的HTTP端點的方法。
- Kubernetes自定義控制器是一種將異步處理程序綁定到API端點的方法。
- Kubernetes Admission Webhooks是一種將同步處理程序綁定到相同API端點的方法。
所以,Kubernetes和Django并沒有太大的不同。
不過,認真地說,用熟悉的東西做類比通常能幫助我更快地理解新概念。但是,當僅僅理解是不夠的,需要流利的表達時,練習通常會幫助我將概念內化為真正的概念。然而,這是另一篇文章的主題。請繼續關注!