從零開始入門 K8s| K8s 的應用編排與管理

從零開始入門 K8s| K8s 的應用編排與管理作者 | 張振 阿里巴巴高級技術專家

一、資源元信息

1. Kubernetes 資源對象

我們知道,Kubernetes 的資源對象組成:主要包括了 Spec、Status 兩部分。其中 Spec 部分用來描述期望的狀態,Status 部分用來描述觀測到的狀態。

今天我們將爲大家介紹 K8s 的另外一個部分,即元數據部分。該部分主要包括了用來識別資源的標籤:Labels, 用來描述資源的註解;Annotations, 用來描述多個資源之間相互關係的 OwnerReference。這些元數據在 K8s 運行中有非常重要的作用。

2. labels

第一個元數據,也是最重要的一個元數據——資源標籤。資源標籤是一種具有標識型的 Key:Value 元數據,如下圖所示,展示了幾個常見的標籤。

前三個標籤都打在了 Pod 對象上,分別標識了對應的應用環境、發佈的成熟度和應用的版本。從應用標籤的例子可以看到,標籤的名字包括了一個域名的前綴,用來描述打標籤的系統和工具, 最後一個標籤打在 Node 對象上,還在域名前增加了版本的標識 beta 字符串。

標籤主要用來篩選資源和組合資源,可以使用類似於 SQL 查詢 select,來根據 Label 查詢相關的資源。
從零開始入門 K8s| K8s 的應用編排與管理


3. Selector

最常見的 Selector 就是相等型 Selector。現在舉一個簡單的例子:

假設系統中有四個 Pod,每個 Pod 都有標識系統層級和環境的標籤,我們通過 Tie:front 這個標籤,可以匹配左邊欄的 Pod,相等型 Selector 還可以包括多個相等條件,多個相等條件之間是邏輯”與“的關係。

在剛纔的例子中,通過 Tie=front,Env=dev 的 Selector,我們可以篩選出所有 Tie=front,而且 Env=dev 的 Pod,也就是下圖中左上角的 Pod。另外一種 Selector 是集合型 Selector,在例子中,Selector 篩選所有環境是 test 或者 gray 的 Pod。

除了 in 的集合操作外,還有 notin 集合操作,比如 tie notin(front,back),將會篩選所有 tie 不是 front 且不是 back 的 Pod。另外,也可以根據是否存在某 lable 的篩選,如:Selector release,篩選所有帶 release 標籤的 Pod。集合型和相等型的 Selector,也可以用“,”來連接,同樣的標識邏輯”與“的關係。

從零開始入門 K8s| K8s 的應用編排與管理

4. Annotations

另外一種重要的元數據是:annotations。一般是系統或者工具用來存儲資源的非標示的信息,可以用來擴展資源的 spec/status 的描述,這裏給了幾個 annotations 的例子:

第一個例子,存儲了阿里雲負載器的證書 ID,我們可以看到 annotations 一樣可以擁有域名的前綴,標註中也可以包含版本信息。第二個 annotation存儲了 nginx 接入層的配置信息,我們可以看到 annotations 中包括“,”這樣無法出現在 label 中的特殊字符。第三個 annotations 一般可以在 kubectl apply 命令行操作後的資源中看到, annotation 值是一個結構化的數據,實際上是一個 json 串,標記了上一次 kubectl 操作的資源的 json 的描述。

從零開始入門 K8s| K8s 的應用編排與管理

5. Ownereference

最後一個元數據叫做 Ownereference。所謂所有者,一般就是指集合類的資源,比如說 Pod 集合,就有 replicaset、statefulset,這個將在後序的課程中講到。

集合類資源的控制器會創建對應的歸屬資源。比如:replicaset 控制器在操作中會創建 Pod,被創建 Pod 的 Ownereference 就指向了創建 Pod 的 replicaset,Ownereference 使得用戶可以方便地查找一個創建資源的對象,另外,還可以用來實現級聯刪除的效果。

二、操作演示

這裏通過 kubectl 命令去連接我們 ACK 中已經創建好的一個 K8s 集羣,然後來展示一下怎麼查看和修改 K8s 對象中的元數據,主要就是 Pod 的一個標籤、註解,還有對應的 Ownerference。

首先我們看一下集羣裏現在的配置情況:

1.查看 Pod,現在沒有任何的一個 Pod;

  • kubectl get pods

2.然後用事先準備好的一個 Pod 的 yaml,創建一個 Pod 出來;

  • kubectl apply -f pod1.yaml
  • kubectl apply -f pod2.yaml

3.現在查看一下 Pod 打的標籤,我們用 --show-labels 這個選項,可以看到這兩個 Pod 都打上了一個部署環境和層級的標籤;

  • kubectl get pods —show-labels

4.我們也可以通過另外一種方式來查看具體的資源信息。首先查看 nginx1 第一個 Pod 的一個信息,用 -o  yaml 的方式輸出,可以看到這個 Pod 元數據裏面包括了一個 lables 的字段,裏面有兩個 lable;

  • kubectl get pods nginx1 -o yaml | less

5.現在再想一下,怎麼樣對 Pod 已有的 lable 進行修改?我們先把它的部署環境,從開發環境改成測試環境,然後指定 Pod 名字,在環境再加上它的一個值 test ,看一下能不能成功。 這裏報了一個錯誤,可以看到,它其實是說現在這個 label 已經有值了;

  • kubectl label pods nginx1 env=test

6.如果想覆蓋掉它的話,得額外再加上一個覆蓋的選項。加上之後呢,我們應該可以看到這個打標已經成功了;

  • kubectl label pods nginx1 env=test —overwrite

7.我們再看一下現在集羣的 lable 設置情況,首先可以看到 nginx1 的確已經加上了一個部署環境 test 標籤;

  • kubectl get pods —show-labels

8.如果想要對 Pod 去掉一個標籤,也是跟打標籤一樣的操作,但是 env 後就不是等號了。只加上 label 名字,後面不加等號,改成用減號表示去除 label 的 k:v;

  • kubectl label pods nginx tie-

9.可以看到這個 label,去標已經完全成功;

  • kubectl get pods —show-labels

從零開始入門 K8s| K8s 的應用編排與管理

10.下面來看一下配置的 label 值,的確能看到 nginx1 的這個 Pod 少了一個 tie=front 的標籤。有了這個 Pod 標籤之後,可以看一下怎樣用 label Selector 進行匹配?首先 label Selector 是通過 -l 這個選項來進行指定的 ,指定的時候,先試一下用相等型的一個 label 來篩選,所以我們指定的是部署環境等於測試的一個 Pod,我們可以看到能夠篩選出一臺;

  • kubectl get pods —show-labels -l env=test

11.假如說有多個相等的條件需要指定的,實際上這是一個與的關係,假如說 env 再等於 dev,我們實際上是一個 Pod 都拿不到的;

  • kubectl get pods —show-labels -l env=test,env=dev

12.然後假如說 env=dev,但是 tie=front,我們能夠匹配到第二個 Pod,也就是 nginx2;

  • kubectl get pods —show-labels -l env=dev,tie=front

13.我們還可以再試一下怎麼樣用集合型的 label Selector 來進行篩選。這一次我們還是想要匹配出所有部署環境是 test 或者是 dev 的一個 Pod,所以在這裏加上一個引號,然後在括號裏面指定所有部署環境的一個集合。這次能把兩個創建的 Pod 都篩選出來;

  • kubectl get pods —show-labels -l ’env in (dev,test)’

14.我們再試一下怎樣對 Pod 增加一個註解,註解的話,跟打標是一樣的操作,但是把 label 命令改成 annotate 命令;然後,一樣指定類型和對應的名字。後面就不是加上 label 的 k:v 了,而是加上 annotation 的 k:v。這裏我們可以指定一個任意的字符串,比如說加上空格、加上逗號都可以;

  • kubectl annotate pods nginx1 my-annotate=‘my annotate,ok’

15.然後,我們再看一下這個 Pod 的一些元數據,我們這邊能夠看到這個 Pod 的元數據裏面 annotations,這是有一個 my-annotate 這個 Annotations;

  • kubectl get pods nging1 -o yaml | less

然後我們這裏其實也能夠看到有一個 kubectl apply 的時候,kubectl 工具增加了一個 annotation,這也是一個 json 串。

從零開始入門 K8s| K8s 的應用編排與管理

16.然後我們再演示一下看 Pod 的 Ownereference 是怎麼出來的。原來的 Pod 都是直接通過創建 Pod 這個資源方式來創建的,這次換一種方式來創建:通過創建一個 ReplicaSet 對象來創建 Pod 。首先創建一個 ReplicaSet 對象,這個 ReplicaSet 對象可以具體查看一下;

  • kubectl apply -f rs.yaml
  • kubectl get replicasets  nginx-replicasets -o yaml |less

從零開始入門 K8s| K8s 的應用編排與管理

17.我們可以關注一下這個 ReplicaSet 裏面 spec 裏面,提到會創建兩個 Pod,然後 selector 通過匹配部署環境是 product 生產環境的這個標籤來進行匹配。所以我們可以看一下,現在集羣中的 Pod 情況;

  • kubectl get pods

從零開始入門 K8s| K8s 的應用編排與管理

18.將會發現多了兩個 Pod,仔細查看這兩個 Pod,可以看到 ReplicaSet 創建出來的 Pod 有一個特點,即它會帶有 Ownereference,然後 Ownereference 裏面指向了是一個 replicasets 類型,名字就叫做 nginx-replicasets;

  • kubectl get pods nginx-replicasets-rhd68 -o yaml | less

從零開始入門 K8s| K8s 的應用編排與管理

三、控制器模式

1、控制循環

控制型模式最核心的就是控制循環的概念。在控制循環中包括了控制器、被控制的系統,以及能夠觀測系統的傳感器,三個邏輯組件。

當然這些組件都是邏輯的,外界通過修改資源 spec 來控制資源,控制器比較資源 spec 和 status,從而計算一個 diff,diff 最後會用來決定執行對系統進行什麼樣的控制操作,控制操作會使得系統產生新的輸出,並被傳感器以資源 status 形式上報,控制器的各個組件將都會是獨立自主地運行,不斷使系統向 spec 表示終態趨近。

從零開始入門 K8s| K8s 的應用編排與管理

2、Sensor

控制循環中邏輯的傳感器主要由 Reflector、Informer、Indexer 三個組件構成。

Reflector 通過 List 和 Watch K8s server 來獲取資源的數據。List 用來在 Controller 重啓以及 Watch 中斷的情況下,進行系統資源的全量更新;而 Watch 則在多次 List 之間進行增量的資源更新;Reflector 在獲取新的資源數據後,會在 Delta 隊列中塞入一個包括資源對象信息本身以及資源對象事件類型的 Delta 記錄,Delta 隊列中可以保證同一個對象在隊列中僅有一條記錄,從而避免 Reflector 重新 List 和 Watch 的時候產生重複的記錄。

Informer 組件不斷地從 Delta 隊列中彈出 delta 記錄,然後把資源對象交給 indexer,讓 indexer 把資源記錄在一個緩存中,緩存在默認設置下是用資源的命名空間來做索引的,並且可以被 Controller Manager 或多個 Controller 所共享。之後,再把這個事件交給事件的回調函數

從零開始入門 K8s| K8s 的應用編排與管理

控制循環中的控制器組件主要由事件處理函數以及 worker 組成,事件處理函數之間會相互關注資源的新增、更新、刪除的事件,並根據控制器的邏輯去決定是否需要處理。對需要處理的事件,會把事件關聯資源的命名空間以及名字塞入一個工作隊列中,並且由後續的 worker 池中的一個 Worker 來處理,工作隊列會對存儲的對象進行去重,從而避免多個 Woker 處理同一個資源的情況。

Worker 在處理資源對象時,一般需要用資源的名字來重新獲得最新的資源數據,用來創建或者更新資源對象,或者調用其他的外部服務,Worker 如果處理失敗的時候,一般情況下會把資源的名字重新加入到工作隊列中,從而方便之後進行重試。

3、控制循環例子-擴容

這裏舉一個簡單的例子來說明一下控制循環的工作原理。

ReplicaSet 是一個用來描述無狀態應用的擴縮容行爲的資源, ReplicaSet controler 通過監聽 ReplicaSet 資源來維持應用希望的狀態數量,ReplicaSet 中通過 selector 來匹配所關聯的 Pod,在這裏考慮 ReplicaSet rsA 的,replicas 從 2 被改到 3 的場景。

從零開始入門 K8s| K8s 的應用編排與管理

首先,Reflector 會 watch 到 ReplicaSet 和 Pod 兩種資源的變化,爲什麼我們還會 watch pod 資源的變化稍後會講到。發現 ReplicaSet 發生變化後,在 delta 隊列中塞入了對象是 rsA,而且類型是更新的記錄。

Informer 一方面把新的 ReplicaSet 更新到緩存中,並與 Namespace nsA 作爲索引。另外一方面,調用 Update 的回調函數,ReplicaSet 控制器發現 ReplicaSet 發生變化後會把字符串的 nsA/rsA 字符串塞入到工作隊列中,工作隊列後的一個 Worker 從工作隊列中取到了 nsA/rsA 這個字符串的 key,並且從緩存中取到了最新的 ReplicaSet 數據。

Worker 通過比較 ReplicaSet 中 spec 和 status 裏的數值,發現需要對這個 ReplicaSet 進行擴容,因此 ReplicaSet 的 Worker 創建了一個 Pod,這個 pod 中的 Ownereference 取向了 ReplicaSet rsA。

從零開始入門 K8s| K8s 的應用編排與管理

然後 Reflector Watch 到的 Pod 新增事件,在 delta 隊列中額外加入了 Add 類型的 deta 記錄,一方面把新的 Pod 記錄通過 Indexer 存儲到了緩存中,另一方面調用了 ReplicaSet 控制器的 Add 回調函數,Add 回調函數通過檢查 pod ownerReferences 找到了對應的 ReplicaSet,並把包括 ReplicaSet 命名空間和字符串塞入到了工作隊列中。

ReplicaSet 的 Woker 在得到新的工作項之後,從緩存中取到了新的 ReplicaSet 記錄,並得到了其所有創建的 Pod,因爲 ReplicaSet 的狀態不是最新的,也就是所有創建 Pod 的數量不是最新的。因此在此時 ReplicaSet 更新 status 使得 spec 和 status 達成一致。

從零開始入門 K8s| K8s 的應用編排與管理

四、控制器模式總結

1、兩種 API 設計方法

Kubernetes 控制器模式依賴聲明式的 API。另外一種常見的 API 類型是命令式 API。爲什麼 Kubernetes 採用聲明式 API,而不是命令式 API 來設計整個控制器呢?

首先,比較兩種 API 在交互行爲上的差別。在生活中,常見的命令式的交互方式是家長和孩子交流方式,因爲孩子欠缺目標意識,無法理解家長期望,家長往往通過一些命令,教孩子一些明確的動作,比如說:吃飯、睡覺類似的命令。我們在容器編排體系中,命令式 API 就是通過向系統發出明確的操作來執行的。

而常見的聲明式交互方式,就是老闆對自己員工的交流方式。老闆一般不會給自己的員工下很明確的決定,實際上可能老闆對於要操作的事情本身,還不如員工清楚。因此,老闆通過給員工設置可量化的業務目標的方式,來發揮員工自身的主觀能動性。比如說,老闆會要求某個產品的市場佔有率達到 80%,而不會指出要達到這個市場佔有率,要做的具體操作細節。

類似的,在容器編排體系中,我們可以執行一個應用實例副本數保持在 3 個,而不用明確的去擴容 Pod 或是刪除已有的 Pod,來保證副本數在三個。

從零開始入門 K8s| K8s 的應用編排與管理

2、命令式 API 的問題

在理解兩個交互 API 的差別後,可以分析一下命令式 API 的問題。

  • 命令 API 最大的一個問題在於錯誤處理;

在大規模的分佈式系統中,錯誤是無處不在的。一旦發出的命令沒有響應,調用方只能通過反覆重試的方式來試圖恢復錯誤,然而盲目的重試可能會帶來更大的問題。

假設原來的命令,後臺實際上已經執行完成了,重試後又多執行了一個重試的命令操作。爲了避免重試的問題,系統往往還需要在執行命令前,先記錄一下需要執行的命令,並且在重啓等場景下,重做待執行的命令,而且在執行的過程中,還需要考慮多個命令的先後順序、覆蓋關係等等一些複雜的邏輯情況。

  • 實際上許多命令式的交互系統後臺往往還會做一個巡檢的系統,用來修正命令處理超時、重試等一些場景造成數據不一致的問題;

然而,因爲巡檢邏輯和日常操作邏輯是不一樣的,往往在測試上覆蓋不夠,在錯誤處理上不夠嚴謹,具有很大的操作風險,因此往往很多巡檢系統都是人工來觸發的。

  • 最後,命令式 API 在處理多併發訪問時,也很容易出現問題;

假如有多方併發的對一個資源請求進行操作,並且一旦其中有操作出現了錯誤,就需要重試。那麼最後哪一個操作生效了,就很難確認,也無法保證。很多命令式系統往往在操作前會對系統進行加鎖,從而保證整個系統最後生效行爲的可預見性,但是加鎖行爲會降低整個系統的操作執行效率。

  • 相對的,聲明式 API 系統裏天然地記錄了系統現在和最終的狀態。

不需要額外的操作數據。另外因爲狀態的冪等性,可以在任意時刻反覆操作。在聲明式系統運行的方式裏,正常的操作實際上就是對資源狀態的巡檢,不需要額外開發巡檢系統,系統的運行邏輯也能夠在日常的運行中得到測試和錘鍊,因此整個操作的穩定性能夠得到保證。

最後,因爲資源的最終狀態是明確的,我們可以合併多次對狀態的修改。可以不需要加鎖,就支持多方的併發訪問。

從零開始入門 K8s| K8s 的應用編排與管理

3、控制器模式總結

最後我們總結一下:

  1. Kubernetes 所採用的控制器模式,是由聲明式 API 驅動的。確切來說,是基於對 Kubernetes 資源對象的修改來驅動的;
  2. Kubernetes 資源之後,是關注該資源的控制器。這些控制器將異步的控制系統向設置的終態驅近;
  3. 這些控制器是自主運行的,使得系統的自動化和無人值守成爲可能;
  4. 因爲 Kubernetes 的控制器和資源都是可以自定義的,因此可以方便的擴展控制器模式。特別是對於有狀態應用,我們往往通過自定義資源和控制器的方式,來自動化運維操作。這個也就是後續會介紹的 operator 的場景。

從零開始入門 K8s| K8s 的應用編排與管理

本文總結

這裏爲大家簡單總結一下本文的主要內容:

  • Kubernetes 資源對象中的元數據部分,主要包括了用來識別資源的標籤:Labels, 用來描述資源的註解;Annotations, 用來描述多個資源之間相互關係的 OwnerReference。這些元數據在 K8s 運行中有非常重要的作用;
  • 控制型模式中最核心的就是控制循環的概念;
  • 兩種 API 設計方法:聲明式 API 和命令式 API ;Kubernetes 所採用的控制器模式,是由聲明式 API 驅動的;

阿里巴巴雲原生微信公衆號(ID:Alicloudnative)關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,做最懂雲原生開發者的技術公衆號。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章