初識 Kubernetes API 的組織結(jié)構(gòu)

前言
話說自己入坑云原生也有好幾年了,但是對 kubernetes 基礎(chǔ)認(rèn)識卻不夠深,導(dǎo)致寫代碼的時候經(jīng)常需要打開 godoc 或者 kubernetes 源碼查看某個接口或者方法的定義。這種快餐式的消費代碼方式可以解決常見的問題,但有時候卻會被一個簡單的問題困擾很久。究其原因,還是沒有對 kubernetes 有比較系統(tǒng)的學(xué)習(xí),特別對于 kubernetes API 的設(shè)計與原理沒有較為深入的認(rèn)識,這也是我們平時擴展 kubernetes 功能繞不開的話題。與此同時,這也是很難講清楚的一個話題,是因為 kubernetes 經(jīng)過多個版本的迭代功能已經(jīng)趨于成熟與復(fù)雜,這一點也可以從 Github 平臺 kubernetes 組織下的多個倉庫也可以看得出來,相信很多人和我一樣,看到 kubernetes、client-go、api、apimachinery 等倉庫就不知道如何下手。事實上,從 API 入手是比較簡單的做法,特別是我們對于 kubernetes 核心組件的功能有了一定的了解之后。
接下來的幾篇筆記,我將由淺入深地學(xué)習(xí) kubernetes API 的設(shè)計以及背后的原理。我的計劃是這樣的:
初識 kubernetes API 的組織結(jié)構(gòu) 深入 kubernetes API 的源碼實現(xiàn) 擴展 kubernetes API 的典型方式
廢話不多說,我們先來認(rèn)識一下 kubernetes API 的基礎(chǔ)結(jié)構(gòu)以及背后的設(shè)計原理。
API-Server
我們知道 kubernetes 控制層面的核心組件包括 API-Server、 Controller Manager、Scheduler,其中 API-Server 對內(nèi)與分布式存儲系統(tǒng) etcd 交互實現(xiàn) kubernetes 資源(例如 pod、namespace、configMap、service 等)的持久化,對外提供通過 RESTFul 的形式提供 kubernetes API[1] 的訪問接口,除此之外,它還負(fù)責(zé) API 請求的認(rèn)證(authN)[2]、授權(quán)(authZ)[3]以及驗證[4]。剛提到的“對外”是相對的概念,因為除了像 kubectl 之類的命令行工具之外,kubernetes 的其他組件也會通過各種客戶端庫來訪問 kubernetes API,關(guān)于官方提供的各種客戶端庫請查看 client-libraries 列表[5],其中最典型的是 Go 語言的客戶端庫 client-go[6]。

API-Server 是 kubernetes 控制層面中唯一一個與 etcd 交互的組件,kubernetes 的其他組件都要通過 API-Server 來更新集群的狀態(tài),所以說 API-Server 是無狀態(tài)的;當(dāng)然也可以創(chuàng)建多個 API-Server 的實例來實現(xiàn)容災(zāi)。API-Server 通過配合 controller 模式來實現(xiàn)聲明式的 API 管理 kubernetes 資源。
既然我們知道了 API-Server 的主要職責(zé)是提供 kubernetes 資源的 RESTFul API,那么客戶端怎么去請求 kubernetes 資源, API-Server 怎么去組織這些 kubernetes 資源呢?
GVK vs GVR
Kubernetes API 通過 HTTP 協(xié)議以 RESTful 的形式提供,API 資源的序列化方式主要是以 JSON 格式進行,但為了內(nèi)部通信也支持 Protocol Buffer 格式。為了方便擴展與演進,kubernetes API 支持分組與多版本,這體現(xiàn)在不同的 API 訪問路徑上。有了分組與多版本支持,即使要在新版本中去掉 API 資源的特定字段或者重構(gòu) API 資源的展現(xiàn)形式,也可以保證版本之間的兼容性。
API-group
將整個 kubernetes API 資源分成各個組,可以帶來很多好處:
各組可以單獨打開或者關(guān)閉[7] 各組可以有獨立的版本,在不影響其他組的情況下單獨向前衍化 同一個資源可以同時存在于多個不同組中,這樣就可以同時支持某個特定資源穩(wěn)定版本與實驗版本
關(guān)于 kubernetes API 資源的分組信息可以在序列化的資源定義中有所體現(xiàn),例如:
apiVersion: apps/v1
kind: Deployment
metadata:
name: example-deploy
spec:
...
其中 apiVersion 字段中 apps 即為 Deployment 資源的分組,實際上,Deployment 不止出現(xiàn)在 apps 分組里,也出現(xiàn)在 extensions 分組中,不同的分組可以實驗不同的特性;另外,kubernetes 中的核心資源如 pod、namespace、configmap、node、service 等存在于 core 分組中,但是由于歷史的原因,core 不出現(xiàn)在 apiVersion 字段中,例如以下定義一個 pod 資源的序列化對象:
apiVersion: v1
kind: Pod
metadata:
name: example-pod
labels:
app: exp
...
API 分組也體現(xiàn)在訪問資源的 RESTful API 路徑上,core 組中的資源訪問路徑一般為 /api/$VERSION,其他命名組的資源訪問路徑則是 /apis/$GROUP_NAME/$VERSION,此外還有一些系統(tǒng)級別的資源,如集群指標(biāo)信息 /metrics,以上這些就基本構(gòu)成了 kubernetes API 的樹結(jié)構(gòu):

API-version
為了支持獨立的演進,kubernetes API 也支持不同的版本,不同的版本代表不同的成熟度。注意,這里說的是 API 而非資源支持多版本。因為多版本支持是針對 API 級別,而不是特定的資源或者資源的字段。一般來說,我們根據(jù) API 分組、資源類型、namespace 以及 name 來區(qū)分不同的資源對象,對于同一個資源對象的不同版本,API-Server 負(fù)責(zé)不同版本之間的無損切換,這點對于客戶端來說是完全透明的。事實上,不同版本的同類型的資源在持久化層的數(shù)據(jù)可能是相同的。例如,對于同一種資源類型支持 v1 和 v1beta1 兩個 API 版本,以 v1beta1 版本創(chuàng)建該資源的對象,后續(xù)可以以v1 或者 v1beta1 來更新或者刪除該資源對象。
API 多版本支持一般通過將資源分組置于不同的版本中來實現(xiàn),例如,batch 同時存在 v2alph1 與 v1 版本。一般來說,新的資源分組先出現(xiàn) v1alpha1 版本,隨著穩(wěn)定性的提高被推進到 v1beta1 ,最后從 v1 版本畢業(yè)。
隨著新的用戶場景出現(xiàn),kubernetes API 需要不斷變化,可能是新增一個字段,也可能是刪除舊的字段,甚至是改變資源的展現(xiàn)形式。為了保證兼容性,kubernetes 制定了一系列的策略[8]??偟膩碚f,對于已經(jīng) GA 的 API,API,kubernetes 嚴(yán)格維護其兼容性,終端用戶可以放心食用,beta 版本的 API 則盡量維護,保證不打破版本跨版本之間的交互,而對于 alpha 版本的 API 則很難保證兼容性,不太推薦生產(chǎn)環(huán)境使用。
GVK 與 GVR 映射
在 kubernetes API 宇宙中,我們經(jīng)常使用屬于 GVK 或者 GVR 來區(qū)分特定的 kubernetes 資源。其中 GVK 是 Group Version Kind 的簡稱,而 GVR 則是 Group Version Resource 的簡稱。
通過上面對于 kubernetes API 分組和多版本的介紹中我們已經(jīng)了解了 Group 與 Version,那么 Kind 與 Resource 又分別是指什么呢?
Kind 是 API “頂級”資源對象的類型,每個資源對象都需要 Kind 來區(qū)分它自身代表的資源類型,例如,對于一個 pod 的例子:
apiVersion: v1
kind: Pod
metadata:
name: example-pod
labels:
app: exp
...
其中 kind 字段即代表該資源對象的類型。一般來說,在 kubernetes API 中有三種不同的 Kind:
單個資源對象的類型,最典型的就是剛才例子中提到的 Pod 資源對象的列表類型,例如 PodList 以及 NodeList 等 特殊類型以及非持久化操作的類型,很多這種類型的資源是 subresource, 例如用于綁定資源的 /binding、更新資源狀態(tài)的/status以及讀寫資源實例數(shù)量的/scale
需要注意的是,同 Kind 不止可以出現(xiàn)在同一分組的不同版本中,如 apps/v1beta1 與 apps/v1,它還可能出現(xiàn)在不同的分組中,例如 Deployment 開始以 alpha 的特性出現(xiàn)在 extensions 分組,GA 之后被推進到 apps 組,所以為了嚴(yán)格區(qū)分不同的 Kind,需要組合 API Group、API Version 與 Kind 成為 GVK。
Resource 則是通過 HTTP 協(xié)議以 JSON 格式發(fā)送或者讀取的資源展現(xiàn)形式,可以以單個資源對象展現(xiàn),例如 .../namespaces/default,也可以以列表的形式展現(xiàn),例如 .../jobs。要正確的請求資源對象,API-Server 必須知道 apiVersion 與請求的資源,這樣 API-Server 才能正確地解碼請求信息,這些信息正是處于請求的資源路徑中。一般來說,把 API Group、API Version 以及 Resource 組合成為 GVR 可以區(qū)分特定的資源請求路徑,例如 /apis/batch/v1/jobs 就是請求所有的 jobs 信息。
GVR 常用于組合成 RESTful API 請求路徑。例如,針對應(yīng)用程序 v1 部署的 RESTful API 請求如下所示:
GET /apis/apps/v1/namespaces/{namespace}/deployments/{name}
通過獲取資源的 JSON 或 YAML 格式的序列化對象,進而從資源的類型信息中可以獲得該資源的 GVK;相反,通過 GVK 信息則可以獲取要讀取的資源對象的 GVR,進而構(gòu)建 RESTful API 請求獲取對應(yīng)的資源。這種 GVK 與 GVR 的映射叫做 RESTMapper。Kubernetes 定義了 RESTMapper 接口[9]并帶默認(rèn)帶有實現(xiàn) DefaultRESTMapper[10]。
關(guān)于 kubernetes API 的詳細(xì)規(guī)范請參考 API Conventions[11]
如何儲存
經(jīng)過上一章節(jié)的研究,我們已經(jīng)知道了 kubernetes API 的組織結(jié)構(gòu)以及背后的設(shè)計原理,那么,Kubernetes API 的資源對象最終是怎么提供可靠存儲的。之前也提到了 API-Server 是無狀態(tài)的,它需要與分布式存儲系統(tǒng) etcd[12] 交互來實現(xiàn)資源對象的持久化操作。從概念上講,etcd 支持的數(shù)據(jù)模型是鍵值(key-value)存儲。在 etcd2 中,各個 key 是以層次結(jié)構(gòu)存在,而在 etcd3 中這個就變成了平級模型,但為了保證兼容性也保持了層次結(jié)構(gòu)的方式。
在 Kubernetes 中 etcd 是如何使用的呢?實際上,前面也提到了,etcd 被部署為獨立的部分,甚至多個 etcd 可以組成集群,API-Server 負(fù)責(zé)與 etcd 交互來完成資源對象的持久化。從 1.5.x 之后,Kubernetes 開始全面使用 etcd3。可以在 API-Server 的相關(guān)啟動項參數(shù)中配置使用 etcd 的方式:
$ kube-apiserver -h
...
Etcd flags:
--etcd-cafile string
SSL Certificate Authority file used to secure etcd communication.
--etcd-certfile string
SSL certification file used to secure etcd communication.
...
--etcd-keyfile string
SSL key file used to secure etcd communication.
--etcd-prefix string
The prefix to prepend to all resource paths in etcd. (default "/registry")
...
--storage-backend string
The storage backend for persistence. Options: 'etcd3' (default).
--storage-media-type string
The media type to use to store objects in storage. Some resources or storage backends may only support a specific media type and will ignore this setting. (default
"application/vnd.kubernetes.protobuf")
...
Kubernetes 資源對象是以 JSON 或 Protocol Buffers 格式存儲在 etcd 中,這可以通過配置 kube-apiserver 的啟動參數(shù) --storage-media-type 來決定想要序列化數(shù)據(jù)存入 etcd 的格式,默認(rèn)情況下為 application/vnd.kubernetes.protobuf 格式;另外也可以通過配置 --storage-versions 啟動參數(shù)來配置每個 API 分組的資源對象的持久化存儲的默認(rèn)版本號。
下面通過一個簡單的例子來看,創(chuàng)建一個 pod,然后使用 etcdctl 工具來查看存儲在 etcd 中數(shù)據(jù):
$ cat << EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: webserver
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
EOF
pod/webserver created
$ etcdctl --endpoints=$ETCD_URL \
--cert /etc/kubernetes/pki/etcd/server.crt \
--key /etc/kubernetes/pki/etcd/server.key \
--cacert /etc/kubernetes/pki/etcd/ca.crt \
get /registry/pods/default/webserver --prefix -w simple
/registry/pods/default/webserver
...
10.244.0.5"
使用各種客戶端工具創(chuàng)建資源對象到然后存儲到 etcd 的流程大致如下圖所示:

客戶端工具(例如 kubectl)提供一個期望狀態(tài)的資源對象的序列化表示,該例子使用 YAML 格式提供 kubectl 將 YAML 轉(zhuǎn)換為 JSON 格式,并發(fā)送給 API-Server 對應(yīng)同類型對象的不同版本,API-Server 執(zhí)行無損轉(zhuǎn)換。對于老版本中不存在的字段則存儲在 annotations 中 API-Server 將接收到的對象轉(zhuǎn)換為規(guī)范存儲版本,這個版本由 API-Server 啟動參數(shù)指定,一般是最新的穩(wěn)定版本 最后將資源對象通過 JSON 或 protobuf 方式解析并通過一個特定的 key 存入 etcd 當(dāng)中
上面提到的無損轉(zhuǎn)換是如何進行的?下面使用 Kubernetes 資源對象對象 Horizontal Pod Autoscaling (HPA) 來舉例說明:
$ kubectl proxy --port=8080 &
$ cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: webserver
spec:
selector:
matchLabels:
app: webserver
template:
metadata:
labels:
app: webserver
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
EOF
$ kubectl autoscale deployment webserver --min=2 --max=5 --cpu-percent=80
$ curl http://127.0.0.1:8001/apis/autoscaling/v2beta1/namespaces/default/horizontalpodautoscalers/webserver > hpa-v2beta1.json
$ curl http://127.0.0.1:8001/apis/autoscaling/v2beta2/namespaces/default/horizontalpodautoscalers/webserver > hpa-v2beta2.json
$ diff hpa-v2beta1.json hpa-v2beta2.json
3c3
< "apiVersion": "autoscaling/v2beta1",
---
> "apiVersion": "autoscaling/v2beta2",
42c42,45
< "targetAverageUtilization": 80
---
> "target": {
> "type": "Utilization",
> "averageUtilization": 80
> }
通過上面命令的輸出能夠看出,即使 HorizontalPodAutoscale 的版本從 v2beta1 變?yōu)榱?v2beta2,API-Server 也能夠在不同的版本之前無損轉(zhuǎn)換,不論在 etcd 中實際存的是哪個版本。實際上,API-Server 將所有已知的 Kubernetes 資源類型保存在名為 Scheme 的注冊表(registry)中。在此注冊表中,定義了每種 Kubernetes 資源的類型、分組、版本以及如何轉(zhuǎn)換它們,如何創(chuàng)建新對象,以及如何將對象編碼和解碼為 JSON 或 protobuf 格式的序列化形式。
參考資料
kubernetes API: https://kubernetes.io/docs/concepts/overview/kubernetes-api/
[2]認(rèn)證(authN): https://kubernetes.io/docs/reference/access-authn-authz/authentication/
[3]授權(quán)(authZ): https://kubernetes.io/docs/reference/access-authn-authz/authorization/
[4]驗證: https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/
[5]client-libraries 列表: https://kubernetes.io/docs/reference/using-api/client-libraries/
[6]client-go: https://github.com/kubernetes/client-go/
[7]打開或者關(guān)閉: https://kubernetes.io/docs/reference/using-api/#enabling-or-disabling
[8]策略: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api_changes.md
[9]RESTMapper 接口: https://github.com/kubernetes/apimachinery/blob/master/pkg/api/meta/interfaces.go
[10]DefaultRESTMapper: https://github.com/kubernetes/apimachinery/blob/master/pkg/api/meta/restmapper.go
[11]API Conventions: https://github.com/kubernetes/community/blob/master/contributors/devel/sig-architecture/api-conventions.md
[12]etcd: https://etcd.io/
原文鏈接:https://morven.life/posts/the_k8s_api-1/


你可能還喜歡
點擊下方圖片即可閱讀

云原生是一種信仰 ??
關(guān)注公眾號
后臺回復(fù)?k8s?獲取史上最方便快捷的 Kubernetes 高可用部署工具,只需一條命令,連 ssh 都不需要!


點擊 "閱讀原文" 獲取更好的閱讀體驗!
發(fā)現(xiàn)朋友圈變“安靜”了嗎?


