需求來源#
首先我們先來看一下 API 編程範式的需求來源。
在 Kubernetes 裡面, API 編程範式也就是 Custom Resources Definition(CRD)。我們常講的 CRD,其實指的就是用戶自定義資源。
為什麼會有用戶自定義資源問題呢?
隨著 Kubernetes 使用的越來越多,用戶自定義資源的需求也會越來越多。而 Kubernetes 提供的聚合各個子資源的功能,已經不能滿足日益增長的廣泛需求了。用戶希望提供一種用戶自定義的資源,把各個子資源全部聚合起來。但 Kubernetes 原生資源的擴展和使用比較複雜,因此誕生了用戶自定義資源這麼一個功能。
用例解讀#
CRD 的一個實例#
我們首先具體地介紹一下 CRD 是什麼。
CRD 功能是在 Kubernetes 1.7 版本被引入的,用戶可以根據自己的需求添加自定義的 Kubernetes 對象資源。值得注意的是,這裡用戶自己添加的 Kubernetes 對象資源都是 native 的、都是一等公民,和 Kubernetes 中自帶的、原生的那些 Pod、Deployment 是同樣的對象資源。在 Kubernetes 的 API Server 看來,它們都是存在於 etcd 中的一等資源。
同時,自定義資源和原生內置的資源一樣,都可以用 kubectl 來去創建、查看,也享有 RBAC、安全功能。用戶可以開發自定義控制器來感知或者操作自定義資源的變化。
下面我們來看一個簡單的 CRD 實例。下圖是一個 CRD 的定義。
<code>CopyapiVersion
: apiextension.k8s.io/v1beta1kind
: CustomResourceDefinitionmetadata
:name
: foos.samplecontroller.k8s.iospec
:group
: samplecontroller.k8s.ioversion
: v1alpha1names
:kind
: Fooplural
: foosscope
: Namespaced/<code>
首先最上面的 apiVersion 就是指 CRD 的一個 apiVersion 聲明,聲明它是一個 CRD 的需求或者說定義的 Schema。
kind 就是 CustomResourcesDefinition,指 CRD。name 是一個用戶自定義資源中自己自定義的一個名字。一般我們建議使用“頂級域名.xxx.APIGroup”這樣的格式,比如這裡就是
foos.samplecontroller.k8s.io。
spec 用於指定該 CRD 的 group、version。比如在創建 Pod 或者 Deployment 時,它的 group 可能為 apps/v1 或者 apps/v1beta1 之類,這裡我們也同樣需要去定義 CRD 的 group。
- 圖中的 group 為 samplecontroller.k8s.io;
- verison 為 v1alpha1;
- names 指的是它的 kind 是什麼,比如 Deployment 的 kind 就是 Deployment,Pod 的 kind 就是 Pod,這裡的 kind 被定義為了 Foo;
- plural 字段就是一個暱稱,比如當一些字段或者一些資源的名字比較長時,可以用該字段自定義一些暱稱來簡化它的長度;
- scope 字段表明該 CRD 是否被命名空間管理。比如 ClusterRoleBinding 就是 Cluster 級別的。再比如 Pod、Deployment 可以被創建到不同的命名空間裡,那麼它們的 scope 就是 Namespaced 的。這裡的 CRD 就是 Namespaced 的。
下圖就是上圖所定義的 CRD 的一個實例。
<code>CopyapiVersion:
samplecontroller.k8s.io/v1alpha1
kind:
Foo
metadata:
name:
example-foo
spec:
deploymentName:
example-foo
replicas:
1
/<code>
- 它的 apiVersion 就是我們剛才所定義的 samplecontroller.k8s.io/v1alpha1;
- kind 就是 Foo;
- metadata 的 name 就是我們這個例子的名字;
- 這個實例中 spec 字段其實沒有在 CRD 的 Schema 中定義,我們可以在 spec 中根據自己的需求來寫一寫,格式就是 key:value 這種格式,比如圖中的 deploymentName: example-foo, replicas: 1。當然我們也可以去做一些檢驗或者狀態資源去定義 spec 中到底包含什麼。
帶有校驗的 CRD#
我們來看一個包含校驗的 CRD 定義:
<code>CopyapiVersion:
apiextensions.k8s.io/v1beta1
kind:
CustomResourceDefinition
metadata:
name:
foos.samplecontroller.k8s.io
spec:
group:
samplecontroller.k8s.io
version:
v1alpha1
names:
kind:
Foo
plural:
foos
scope:
Namespaced
validation:
openAPIV3Schema:
propersies:
spec:
properties:
replicas:
type:
integer
minimum:
1
maximum:
10
/<code>
可以看到這個定義更加複雜了,validation 之前的字段我們就不再贅述了,單獨看校驗這一段。
它首先是一個 openAPIV3Schema 的定義,spec 中則定義了有哪些資源,以 replicas 為例,這裡將 replicas 定義為一個 integer 的資源,最小值為 1,最大值是 10。那麼,當我們再次使用這個 CRD 的時候,如果我們給出的 replicas 不是 int 值,或者去寫一個 -1,或者大於 10 的值,這個 CRD 對象就不會被提交到 API Server,API Server 會直接報錯,告訴你不滿足所定義的參數條件。
帶有狀態字段的 CRD#
再來看一下帶有狀態字段的 CRD 定義。
<code>CopyapiVersion
: apiextensions.k8s.io/v1beta1kind
: CustomResourceDefinitionmetadata
:name
: foos.samplecontroller.k8s.iospec
:group
: samplecontroller.k8s.ioversion
: v1alpha1names
:kind
: Foosplural
: foosscope
: Namespacedsubresources
:status
: {} /<code>
我們在使用一些 Deployment 或 Pod 的時候,部署完成之後可能要去查看當前部署的狀態、是否更新等等。這些都是通過增加狀態字段來實現的。另外,Kubernetes 在 1.12 版本之前,還沒有狀態字段。
狀態實際上是一個自定義資源的子資源,它的好處在於,對該字段的更新並不會觸發 Deployment 或 Pod 的重新部署。我們知道對於某些 Deployment 和 Pod,只要修改了某些 spec,它就會重新創建一個新的 Deployment 或者 Pod 出來。但是狀態資源並不會被重新創建,它只是用來回應當前 Pod 的整個狀態。上圖中的 CRD 聲明中它的子資源的狀態非常簡單,就是一個 key:value 的格式。在 "{}" 裡寫什麼,都是自定義的。
以一個 Deployment 的狀態字段為例,它包含 availableReplicas、當前的狀態(比如更新到第幾個版本了、上一個版本是什麼時候)等等這些信息。在用戶自定義 CRD 的時候,也可以進行一些複雜的操作來告訴別的用戶它當前的狀態如何。
操作演示#
下面我們來具體演示一下 CRD。
我們這裡有兩個資源:crd.yaml 和 example-foo.yaml。
首先創建一下這個 CRD 的 Schema 讓我們的 Kubernetes Server 知道該 CRD 到底是什麼樣的。創建的方式非常簡單,就是 kuberctl create -f crd.yaml。
通過 kuberctl get crd 可以看到剛才的 CRD 已經被創建成功了。
這個時候我們就可以去創建對應的資源 kuberctl create -f example-foo.yaml:
下面來看一下它裡面到底有什麼東西 kubectl get foo example-foo -o yaml:
可以看到它是一個 Foo 的資源,spec 就是我們剛才所定義的,被選中的部分是基本上所有的 Kubernetes 的 metadata 資源中都會有的。因此,創建該資源和我們正常創建一個 Pod 的區別並不大,但是這個資源不是一個 Pod,也不是 Kubernetes 本身內置的資源,這就是一個我們自己創建的資源。從使用方式和使用體驗上來說,和 Kubernetes 內置資源的使用幾乎一致。
架構設計#
控制器概覽#
只定義一個 CRD 其實沒有什麼作用,它只會被 API Server 簡單地計入到 etcd 中。如何依據這個 CRD 定義的資源和 Schema 來做一些複雜的操作,則是由 Controller,也就是控制器來實現的。
Controller 其實是 Kubernetes 提供的一種可插拔式的方法來擴展或者控制聲明式的 Kubernetes 資源。它是 Kubernetes 的大腦,負責大部分資源的控制操作。以 Deployment 為例,它就是通過 kube-controller-manager 來部署的。
比如說聲明一個 Deployment 有 replicas、有 2 個 Pod,那麼 kube-controller-manager 在觀察 etcd 時接收到了該請求之後,就會去創建兩個對應的 Pod 的副本,並且它會去實時地觀察著這些 Pod 的狀態,如果這些 Pod 發生變化了、回滾了、失敗了、重啟了等等,它都會去做一些對應的操作。
所以 Controller 才是控制整個 Kubernetes 資源最終表現出來的狀態的大腦。
用戶聲明完成 CRD 之後,也需要創建一個控制器來完成對應的目標。比如之前的 Foo,它希望去創建一個 Deployment,replicas 為 1,這就需要我們創建一個控制器用於創建對應的 Deployment 才能真正實現 CRD 的功能。
控制器工作流程概覽#
這裡以 kube-controller-manager 為例。
如上圖所示,左側是一個 Informer,它的機制就是通過去 watch kube-apiserver,而 kube-apiserver 會去監督所有 etcd 中資源的創建、更新與刪除。Informer 主要有兩個方法:一個是 ListFunc;一個是 WatchFunc。
- ListFunc 就是像 kuberctl get pods 這類操作,把當前所有的資源都列出來;
- WatchFunc 會和 apiserver 建立一個長鏈接,一旦有一個新的對象提交上去之後,apiserver 就會反向推送回來,告訴 Informer 有一個新的對象創建或者更新等操作。
Informer 接收到了對象的需求之後,就會調用對應的函數(比如圖中的三個函數 AddFunc, UpdateFunc 以及 DeleteFunc),並將其按照 key 值的格式放到一個隊列中去,key 值的命名規則就是 "namespace/name",name 就是對應的資源的名字。比如我們剛才所說的在 default 的 namespace 中創建一個 foo 類型的資源,那麼它的 key 值就是 default/example-foo。Controller 從隊列中拿到一個對象之後,就會去做相應的操作。
下圖就是控制器的工作流程。
首先,通過 kube-apiserver 來推送事件,比如 Added、Updated、Deleted;然後進入到 Controller 的 ListAndWatch() 循環中;ListAndWatch 中有一個先入先出的隊列,在操作的時候就將其 Pop() 出來;然後去找對應的 Handler。Handler 會將其交給對應的函數(比如 Add()、Update()、Delete())。
一個函數一般會有多個 Worker。多個 Worker 的意思是說比如同時有好幾個對象進來,那麼這個 Controller 可能會同時啟動五個、十個這樣的 Worker 來並行地執行,每個 Worker 可以處理不同的對象實例。
工作完成之後,即把對應的對象創建出來之後,就把這個 key 丟掉,代表已經處理完成。如果處理過程中有什麼問題,就直接報錯,打出一個事件來,再把這個 key 重新放回到隊列中,下一個 Worker 就可以接收過來繼續進行相同的處理。
本節總結(對本節內容進行分點概括)#
本節課的主要內容就到此為止了,這裡為大家簡單總結一下:
- CRD 是 Custom Resources Definition 的縮寫,也就是用戶自定義資源,用戶可以使用這個功能擴展自己的Kubernetes 原生資源信息。
- CRD 和普通的 Kubernetes 資源一樣,都可以受 RBAC 權限控制,並且支持 status 狀態字段。
- CRD-controller 也就是 CRD 控制器,能夠實現用戶自行編寫,並且解析 CRD 並把它變成用戶期望的狀態。
關鍵字: 自定義 狀態字 Deployment