資深專家深度剖析Kubernetes API Server第2章(共3章)

程式猿da哥發表於2018-09-07

歡迎來到深入學習Kubernetes API Server的系列文章的第二部分。在上一部分中我們對APIserver總體,相關術語及request請求流進行探討說明。在本部分文章中,我們主要聚焦於探究如何對Kubernetes 物件的狀態以一種可靠,持久的方式進行管理。之前的文章中提到過API Server自身是無狀態的,並且它是唯一能夠與分散式儲存etcd直接通訊的元件。

etcd的簡要說明

在*nix作業系統中,我們一般使用/etc來儲存相關配置資料。實際上etcd的名字就是由此發展而來,在etc後面加上個”d”表示”distributed”分散式。任何分散式系統都需要有像etcd這樣能夠儲存系統資料的東西,使其能夠以一致和可靠的方式檢索相關資料。為了能實現分散式的資料訪問,etcd使用Raft 協議。從概念上講,etcd支援的資料模型是鍵值(key-value)儲存。在etcd2中,各個key是以層次結構存在,而在etcd3中這個就變成了遍佈模型,但同時也保持了層次結構方式的相容性。

資深專家深度剖析Kubernetes API Server第2章(共3章)

使用容器化版本的etcd,我們可以建立上面的樹,然後按如下方式檢索它:

$ docker run --rm -d -p 2379:2379 \

--name test-etcd3 quay.io/coreos/etcd:v3.1.0 /usr/local/bin/etcd \

--advertise-client-urls http://0.0.0.0:2379 --listen-client-urls http://0.0.0.0:2379

$ curl localhost:2379/v2/keys/foo -XPUT -d value="some value"

$ curl localhost:2379/v2/keys/bar/this -XPUT -d value=42

$ curl localhost:2379/v2/keys/bar/that -XPUT -d value=take

$ http localhost:2379/v2/keys/?recursive=true

HTTP/1.1 200 OK

Content-Length: 327

Content-Type: application/json

Date: Tue, 06 Jun 2017 12:28:28 GMT

X-Etcd-Cluster-Id: 10e5e39849dab251

X-Etcd-Index: 6

X-Raft-Index: 7

X-Raft-Term: 2

{

"action": "get",

"node": {

"dir": true,

"nodes": [

{

"createdIndex": 4,

"key": "/foo",

"modifiedIndex": 4,

"value": "some value"

},

{

"createdIndex": 5,

"dir": true,

"key": "/bar",

"modifiedIndex": 5,

"nodes": [

{

"createdIndex": 5,

"key": "/bar/this",

"modifiedIndex": 5,

"value": "42"

},

{

"createdIndex": 6,

"key": "/bar/that",

"modifiedIndex": 6,

"value": "take"

}

]

}

]

}

}

現在我們已經大致瞭解了etcd是如何工作的,接下去我們繼續討論etcd在Kubernetes是如何被使用的。

叢集中的etcd

在Kubernetes中,etcd是控制平面中的一耳光獨立組成部分。在Kubernetes1.5.2版本之前,我們使用的是etcd2版本,而在Kubernetes1.5.2版本之後我們就轉向使用etcd3版本了。值得注意的是在Kubernetes1.5.x版本中etcd依舊使用的是v2的API模型,之後這將開始變為v3的API模型,包括使用的資料模型。站在開發者角度而言這個似乎沒什麼直接影響,因為API Server與儲存之前是抽象互動,而並不關心後端儲存的實現是etcd v2還是v3。但是如果是站在叢集管理員的角度來看,還是需要知道etcd使用的是哪個版本,因為叢集管理員需要日常對資料進行一些備份,恢復的維護操作。

你可以API Server的相關啟動項中配置使用etcd的方式,API Server的etcd相關啟動項引數如下所示:

$ kube-apiserver -h

...

--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-quorum-read If true, enable quorum read.

--etcd-servers List of etcd servers to connect with (scheme://ip:port) …

...

Kubernetes儲存在etcd中的資料,是以JSON字串或Protocol Buffers 格式儲存。下面我們來看一個具體的例子:在apiserver-sandbox的名稱空間中建立一個webserver的pod。然後我們使用etcdctl工具來檢視相關etcd(在本環節中etcd版本為3.1.0)資料。

$ cat pod.yaml

apiVersion: v1

kind: Pod

metadata:

name: webserver

spec:

containers:

- name: nginx

image: tomaskral/nonroot-nginx

ports:

- containerPort: 80


$ kubectl create -f pod.yaml


$ etcdctl ls /

/kubernetes.io

/openshift.io


$ etcdctl get /kubernetes.io/pods/apiserver-sandbox/webserver

{

"kind": "Pod",

"apiVersion": "v1",

"metadata": {

"name": "webserver",

...

下面我們來看一下這個pod物件是如何最終儲存到etcd中,通過kubectl create -f pod.yaml的方式。下圖描繪了這個總體流程:

資深專家深度剖析Kubernetes API Server第2章(共3章)

1.客戶端(比如kubectl)提供一個理想狀態的物件,比如以YAML格式,v1版本提供。

2.Kubectl將YAML轉換為JSON格式,併傳送。

3.對應同型別物件的不同版本,API Server執行無損耗轉換。對於老版本中不存在的欄位則儲存在annotations中。

4.API Server將接受到的物件轉換為規範儲存版本,這個版本由API Server指定,一般是最新的穩定版本,比如v1。

5.最後將物件通過JSON 或protobuf方式解析為一個value,通過一個特定的key存入etcd當中。

我們可以通過配置 kube-apiserver的啟動引數--storage-media-type來決定想要序列化資料存入etcd的格式,預設情況下為application/vnd.kubernetes.protobuf格式。我們也可以通過配置--storage-versions啟動引數,來確定存入etcd的每個群組Group物件的預設版本號。

現在讓我們來看看無損轉換是如何進行的,我們將使用Kubernetes 物件Horizontal Pod Autoscaling (HPA)來列舉說明。HPA顧名思義是指通過監控資源的使用情況結合ReplicationController控制Pod的伸縮。

首先我們期待一個API代理(以便於我們能夠在本地直接訪問它),並啟動ReplicationController,以及HPA 。

$ kubectl proxy --port=8080 &

$ kubectl create -f https://raw.githubusercontent.com/mhausenblas/kbe/master/specs/rcs/rc.yaml

kubectl autoscale rc rcex --min=2 --max=5 --cpu-percent=80

kubectl get hpa/rcex -o yaml

現在,你能夠使用httpie ——當然你也能夠使用curl的方式——向API server 請求獲取HPA物件使用當前的穩定版本(autoscaling/v1),或者使用之前的版本(extensions/v1beta1),獲取的兩個版本的區別如下所示:

$ http localhost:8080/apis/extensions/v1beta1/namespaces/api-server-deepdive/horizontalpodautoscalers/rcex > hpa-v1beta1.json

$ http localhost:8080/apis/autoscaling/v1/namespaces/api-server-deepdive/horizontalpodautoscalers/rcex > hpa-v1.json

$ diff -u hpa-v1beta1.json hpa-v1.json

{

"kind": "HorizontalPodAutoscaler",

- "apiVersion": "extensions/v1beta1",

+ "apiVersion": "autoscaling/v1",

"metadata": {

"name": "rcex",

"namespace": "api-server-deepdive",

- "selfLink": "/apis/extensions/v1beta1/namespaces/api-server-deepdive/horizontalpodautoscalers/rcex",

+ "selfLink": "/apis/autoscaling/v1/namespaces/api-server-deepdive/horizontalpodautoscalers/rcex",

"uid": "ad7efe42-50ed-11e7-9882-5254009543f6",

"resourceVersion": "267762",

"creationTimestamp": "2017-06-14T10:39:00Z"

},

"spec": {

- "scaleRef": {

+ "scaleTargetRef": {

"kind": "ReplicationController",

"name": "rcex",

- "apiVersion": "v1",

- "subresource": "scale"

+ "apiVersion": "v1"

},

"minReplicas": 2,

"maxReplicas": 5,

- "cpuUtilization": {

- "targetPercentage": 80

- }

+ "targetCPUUtilizationPercentage": 80

我們能夠看到HorizontalPodAutoscale的版本從v1beta1變為了v1。API server能夠在不同的版本之前無損耗轉換,不論在etcd中實際存的是哪個版本。

在瞭解整個儲存流程之後,我們下面來探究一下API server如何將資料進行編碼,解碼存入etcd中以JSON或protobuf的方式,同時也考慮到etcd的版本。

API Server將所有已知的Kubernetes物件型別儲存在名為Scheme的Go型別登錄檔(registry)中。在此登錄檔中,定義每種了Kubernetes物件的型別以及如何轉換它們,如何建立新物件,以及如何將物件編碼和解碼為JSON或protobuf。

資深專家深度剖析Kubernetes API Server第2章(共3章)

當API Server從客戶端接收到一個物件時,比如kubectl,通過HTTP路徑,能夠知道這個物件的具體版本號。首先會為這個物件使用對應的版本Scheme建立一個空物件,然後通過JSON或protobuf將HTTP傳過來的物件內容進行解碼轉換。解碼完成後建立物件,存入etcd中。

在API中可能會有很多版本,如果要支援每個版本之間的直接轉換,這樣往往處理起來比較麻煩。比如某個API下面有三個版本,那麼它就要支援一個版本到另兩個版本的直接轉換(比如v1 ⇔ v1alpha1, v1 ⇔ v1beta1, v1beta1 ⇔ v1alpha1)。為了避免這個問題,在API server中有一個特別的“internal”版本。當兩個版本之間需要轉換時,先轉換為internal版本,再轉換為相應轉換的版本。這樣的話,每個版本只要支援能夠轉換為internal版本,那麼就能夠與其它任何版本進行間接的轉換。所以一個物件先轉換為internal版本,然後在轉換為穩定的v1版本,然後在存入etcd中。

v1beta1 ⇒ internal ⇒ v1

在轉換的第一步中,如果某些欄位使用者沒有賦值指定,那麼這些會被賦為一個預設值。比如在v1beta1 中肯定沒有在v1版本新增的一個欄位。在這種情況下,使用者肯定無法在v1beta1 版本為這個欄位賦值。這時候,在轉換的第一步中,我們會為這個欄位賦一個預設值以生成一個有效的internal。

校驗及准入

在轉換過程中有兩個重要的步驟,如下圖所示:

v1beta1 ⇒ internal ⇒ | ⇒ | ⇒ v1 ⇒ json/yaml ⇒ etcd

admission validation

准入和校驗是建立和更新物件存入etcd之前必須通過的步驟。它們的一些規則如下所示:

1.准入(Admission):檢視叢集中的一些約束條件是否允許建立或更新此物件,並根據此叢集的相關配置為物件設定一些預設值。在Kubernetes有很多這種約束條件,下面列舉一些例子:

NamespaceLifecycle:如果名稱空間不存在,則拒絕該名稱空間下的所有傳入請求。

LimitRanger:強制限制名稱空間中資源的使用率。

ServiceAccount:為pod建立 service account 。

DefaultStorageClass:如果使用者沒有為PersistentVolumeClaims賦值,那麼將它設定為一個預設值。

ResourceQuota:對群集上的當前使用者強制執行配額約束,如果配額不足,可能會拒絕請求。

2.校驗(Validation):檢查傳入物件(在建立和更新期間)是否格式是否合法以及相關值是否有效。比如:

1)檢查必填欄位是否已填。

2)檢查字串格式是否正確(比如只允許小寫形式)。

3)是否有些欄位存在衝突(比如,有兩個容器的名字一樣)。

校驗(Validation)並不關心其它型別的物件例項,換言之,它只關心每個物件的靜態檢查,無關叢集配置。

准入(Admission)可以用flag --admission-control=來啟動或禁用。它們中的大多數可以有叢集管理配置。此外,在Kubernetes 1.7中可以用webhook機制來擴充套件准入機制,使用控制器來實現對物件的傳統的校驗。

遷移儲存物件

關於儲存物件遷移的最後說明:當Kubernetes需要升級到新的版本時,根據每個版本的相關文件步驟備份相關叢集的資料是至關重要的。這一方面是由於etcd2到etcd3的轉變,另一方面是由於Kubernetes 物件的Kind及version的不斷髮展。

在etcd中,每個物件是首選儲存版本(preferred storage version)存在的。但是,隨著時間的推移,etcd儲存中的物件可能以一個非常老的版本存在。如果在將來某個時間這個物件版本被廢棄了,那麼將無法再解碼它的protobuf 或JSON。因此,在叢集升級之前需要重寫,遷移這些資料。下面這些資料能夠對version切換提供一些幫助:

請參閱叢集管理文件升級API版本部分。Upgrading to a different API version

下一次,在深入學習Kubernetes APIServer的第三部分中,我們將討論如何使用Custom Resource Definitions擴充套件和自定義API資源。


相關文章