做者 | 張振 阿里巴巴高級技術專家nginx
咱們知道,Kubernetes 的資源對象組成:主要包括了 Spec、Status 兩部分。其中 Spec 部分用來描述指望的狀態,Status 部分用來描述觀測到的狀態。json
今天咱們將爲你們介紹 K8s 的另一個部分,即元數據部分。該部分主要包括了用來識別資源的標籤:Labels, 用來描述資源的註解;Annotations, 用來描述多個資源之間相互關係的 OwnerReference。這些元數據在 K8s 運行中有很是重要的做用。api
第一個元數據,也是最重要的一個元數據——資源標籤。資源標籤是一種具備標識型的 Key:Value 元數據,以下圖所示,展現了幾個常見的標籤。緩存
前三個標籤都打在了 Pod 對象上,分別標識了對應的應用環境、發佈的成熟度和應用的版本。從應用標籤的例子能夠看到,標籤的名字包括了一個域名的前綴,用來描述打標籤的系統和工具, 最後一個標籤打在 Node 對象上,還在域名前增長了版本的標識 beta 字符串。微信
標籤主要用來篩選資源和組合資源,可使用相似於 SQL 查詢 select,來根據 Label 查詢相關的資源。
併發
最多見的 Selector 就是相等型 Selector。如今舉一個簡單的例子:app
假設系統中有四個 Pod,每一個 Pod 都有標識系統層級和環境的標籤,咱們經過 Tie:front 這個標籤,能夠匹配左邊欄的 Pod,相等型 Selector 還能夠包括多個相等條件,多個相等條件之間是邏輯」與「的關係。less
在剛纔的例子中,經過 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,也能夠用「,」來鏈接,一樣的標識邏輯」與「的關係。異步
另一種重要的元數據是:annotations。通常是系統或者工具用來存儲資源的非標示性信息,能夠用來擴展資源的 spec/status 的描述,這裏給了幾個 annotations 的例子:
第一個例子,存儲了阿里雲負載器的證書 ID,咱們能夠看到 annotations 同樣能夠擁有域名的前綴,標註中也能夠包含版本信息。第二個 annotation存儲了 nginx 接入層的配置信息,咱們能夠看到 annotations 中包括「,」這樣沒法出如今 label 中的特殊字符。第三個 annotations 通常能夠在 kubectl apply 命令行操做後的資源中看到, annotation 值是一個結構化的數據,其實是一個 json 串,標記了上一次 kubectl 操做的資源的 json 的描述。
最後一個元數據叫作 Ownereference。所謂全部者,通常就是指集合類的資源,好比說 Pod 集合,就有 replicaset、statefulset,這個將在後序的課程中講到。
集合類資源的控制器會建立對應的歸屬資源。好比:replicaset 控制器在操做中會建立 Pod,被建立 Pod 的 Ownereference 就指向了建立 Pod 的 replicaset,Ownereference 使得用戶能夠方便地查找一個建立資源的對象,另外,還能夠用來實現級聯刪除的效果。** **
這裏經過 kubectl 命令去鏈接咱們 ACK 中已經建立好的一個 K8s 集羣,而後來展現一下怎麼查看和修改 K8s 對象中的元數據,主要就是 Pod 的一個標籤、註解,還有對應的 Ownerference。
首先咱們看一下集羣裏如今的配置狀況:
1.查看 Pod,如今沒有任何的一個 Pod;
2.而後用事先準備好的一個 Pod 的 yaml,建立一個 Pod 出來;
3.如今查看一下 Pod 打的標籤,咱們用 --show-labels 這個選項,能夠看到這兩個 Pod 都打上了一個部署環境和層級的標籤;
4.咱們也能夠經過另一種方式來查看具體的資源信息。首先查看 nginx1 第一個 Pod 的一個信息,用 -o yaml 的方式輸出,能夠看到這個 Pod 元數據裏面包括了一個 lables 的字段,裏面有兩個 lable;
5.如今再想一下,怎麼樣對 Pod 已有的 lable 進行修改?咱們先把它的部署環境,從開發環境改爲測試環境,而後指定 Pod 名字,在環境再加上它的一個值 test ,看一下能不能成功。 這裏報了一個錯誤,能夠看到,它實際上是說如今這個 label 已經有值了;
6.若是想覆蓋掉它的話,得額外再加上一個覆蓋的選項。加上以後呢,咱們應該能夠看到這個打標已經成功了;
7.咱們再看一下如今集羣的 lable 設置狀況,首先能夠看到 nginx1 的確已經加上了一個部署環境 test 標籤;
8.若是想要對 Pod 去掉一個標籤,也是跟打標籤同樣的操做,可是 env 後就不是等號了。只加上 label 名字,後面不加等號,改爲用減號表示去除 label 的 k:v;
9.能夠看到這個 label,去標已經徹底成功;
10.下面來看一下配置的 label 值,的確能看到 nginx1 的這個 Pod 少了一個 tie=front 的標籤。有了這個 Pod 標籤以後,能夠看一下怎樣用 label Selector 進行匹配?首先 label Selector 是經過 -l 這個選項來進行指定的 ,指定的時候,先試一下用相等型的一個 label 來篩選,因此咱們指定的是部署環境等於測試的一個 Pod,咱們能夠看到可以篩選出一臺;
11.假如說有多個相等的條件須要指定的,實際上這是一個與的關係,假如說 env 再等於 dev,咱們其實是一個 Pod 都拿不到的;
12.而後假如說 env=dev,可是 tie=front,咱們可以匹配到第二個 Pod,也就是 nginx2;
13.咱們還能夠再試一下怎麼樣用集合型的 label Selector 來進行篩選。這一次咱們仍是想要匹配出全部部署環境是 test 或者是 dev 的一個 Pod,因此在這裏加上一個引號,而後在括號裏面指定全部部署環境的一個集合。此次能把兩個建立的 Pod 都篩選出來;
14.咱們再試一下怎樣對 Pod 增長一個註解,註解的話,跟打標是同樣的操做,可是把 label 命令改爲 annotate 命令;而後,同樣指定類型和對應的名字。後面就不是加上 label 的 k:v 了,而是加上 annotation 的 k:v。這裏咱們能夠指定一個任意的字符串,好比說加上空格、加上逗號均可以;
15.而後,咱們再看一下這個 Pod 的一些元數據,咱們這邊可以看到這個 Pod 的元數據裏面 annotations,這是有一個 my-annotate 這個 Annotations;
而後咱們這裏其實也可以看到有一個 kubectl apply 的時候,kubectl 工具增長了一個 annotation,這也是一個 json 串。
16.而後咱們再演示一下看 Pod 的 Ownereference 是怎麼出來的。原來的 Pod 都是直接經過建立 Pod 這個資源方式來建立的,此次換一種方式來建立:經過建立一個 ReplicaSet 對象來建立 Pod 。首先建立一個 ReplicaSet 對象,這個 ReplicaSet 對象能夠具體查看一下;
17.咱們能夠關注一下這個 ReplicaSet 裏面 spec 裏面,提到會建立兩個 Pod,而後 selector 經過匹配部署環境是 product 生產環境的這個標籤來進行匹配。因此咱們能夠看一下,如今集羣中的 Pod 狀況;
18.將會發現多了兩個 Pod,仔細查看這兩個 Pod,能夠看到 ReplicaSet 建立出來的 Pod 有一個特色,即它會帶有 Ownereference,而後 Ownereference 裏面指向了是一個 replicasets 類型,名字就叫作 nginx-replicasets;
控制型模式最核心的就是控制循環的概念。在控制循環中包括了控制器、被控制的系統,以及可以觀測系統的傳感器,三個邏輯組件。
固然這些組件都是邏輯的,外界經過修改資源 spec 來控制資源,控制器比較資源 spec 和 status,從而計算一個 diff,diff 最後會用來決定執行對系統進行什麼樣的控制操做,控制操做會使得系統產生新的輸出,並被傳感器以資源 status 形式上報,控制器的各個組件將都會是獨立自主地運行,不斷使系統向 spec 表示終態趨近。
控制循環中邏輯的傳感器主要由 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 所共享。以後,再把這個事件交給事件的回調函數
控制循環中的控制器組件主要由事件處理函數以及 worker 組成,事件處理函數之間會相互關注資源的新增、更新、刪除的事件,並根據控制器的邏輯去決定是否須要處理。對須要處理的事件,會把事件關聯資源的命名空間以及名字塞入一個工做隊列中,而且由後續的 worker 池中的一個 Worker 來處理,工做隊列會對存儲的對象進行去重,從而避免多個 Woker 處理同一個資源的狀況。
Worker 在處理資源對象時,通常須要用資源的名字來從新得到最新的資源數據,用來建立或者更新資源對象,或者調用其餘的外部服務,Worker 若是處理失敗的時候,通常狀況下會把資源的名字從新加入到工做隊列中,從而方便以後進行重試。
這裏舉一個簡單的例子來講明一下控制循環的工做原理。
ReplicaSet 是一個用來描述無狀態應用的擴縮容行爲的資源, ReplicaSet controler 經過監聽 ReplicaSet 資源來維持應用但願的狀態數量,ReplicaSet 中經過 selector 來匹配所關聯的 Pod,在這裏考慮 ReplicaSet rsA 的,replicas 從 2 被改到 3 的場景。
首先,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。
而後 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 達成一致。
Kubernetes 控制器模式依賴聲明式的 API。另一種常見的 API 類型是命令式 API。爲何 Kubernetes 採用聲明式 API,而不是命令式 API 來設計整個控制器呢?
首先,比較兩種 API 在交互行爲上的差異。在生活中,常見的命令式的交互方式是家長和孩子交流方式,由於孩子欠缺目標意識,沒法理解家長指望,家長每每經過一些命令,教孩子一些明確的動做,好比說:吃飯、睡覺相似的命令。咱們在容器編排體系中,命令式 API 就是經過向系統發出明確的操做來執行的。
而常見的聲明式交互方式,就是老闆對本身員工的交流方式。老闆通常不會給本身的員工下很明確的決定,實際上可能老闆對於要操做的事情自己,還不如員工清楚。所以,老闆經過給員工設置可量化的業務目標的方式,來發揮員工自身的主觀能動性。好比說,老闆會要求某個產品的市場佔有率達到 80%,而不會指出要達到這個市場佔有率,要作的具體操做細節。
相似的,在容器編排體系中,咱們能夠執行一個應用實例副本數保持在 3 個,而不用明確的去擴容 Pod 或是刪除已有的 Pod,來保證副本數在三個。
在理解兩個交互 API 的差異後,能夠分析一下命令式 API 的問題。
在大規模的分佈式系統中,錯誤是無處不在的。一旦發出的命令沒有響應,調用方只能經過反覆重試的方式來試圖恢復錯誤,然而盲目的重試可能會帶來更大的問題。
假設原來的命令,後臺實際上已經執行完成了,重試後又多執行了一個重試的命令操做。爲了不重試的問題,系統每每還須要在執行命令前,先記錄一下須要執行的命令,而且在重啓等場景下,重作待執行的命令,並且在執行的過程當中,還須要考慮多個命令的前後順序、覆蓋關係等等一些複雜的邏輯狀況。
然而,由於巡檢邏輯和平常操做邏輯是不同的,每每在測試上覆蓋不夠,在錯誤處理上不夠嚴謹,具備很大的操做風險,所以每每不少巡檢系統都是人工來觸發的。
假若有多方併發的對一個資源請求進行操做,而且一旦其中有操做出現了錯誤,就須要重試。那麼最後哪個操做生效了,就很難確認,也沒法保證。不少命令式系統每每在操做前會對系統進行加鎖,從而保證整個系統最後生效行爲的可預見性,可是加鎖行爲會下降整個系統的操做執行效率。
不須要額外的操做數據。另外由於狀態的冪等性,能夠在任意時刻反覆操做。在聲明式系統運行的方式裏,正常的操做實際上就是對資源狀態的巡檢,不須要額外開發巡檢系統,系統的運行邏輯也可以在平常的運行中獲得測試和錘鍊,所以整個操做的穩定性可以獲得保證。
最後,由於資源的最終狀態是明確的,咱們能夠合併屢次對狀態的修改。能夠不須要加鎖,就支持多方的併發訪問。
最後咱們總結一下:
1.Kubernetes 所採用的控制器模式,是由聲明式 API 驅動的。確切來講,是基於對 Kubernetes 資源對象的修改來驅動的;
2.Kubernetes 資源以後,是關注該資源的控制器。這些控制器將異步的控制系統向設置的終態驅近;
3.這些控制器是自主運行的,使得系統的自動化和無人值守成爲可能;
4.由於 Kubernetes 的控制器和資源都是能夠自定義的,所以能夠方便的擴展控制器模式。特別是對於有狀態應用,咱們每每經過自定義資源和控制器的方式,來自動化運維操做。這個也就是後續會介紹的 operator 的場景。
這裏爲你們簡單總結一下本文的主要內容:
阿里巴巴雲原生微信公衆號(ID:Alicloudnative)關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,作最懂雲原生開發者的技術公衆號。