从零开始入门 K8s| K8s 的应用编排与管理
作者 | 張振 阿里巴巴高級技術專家
一、資源元信息
1. Kubernetes 資源對象
我們知道,Kubernetes 的資源對象組成:主要包括了 Spec、Status 兩部分。其中 Spec 部分用來描述期望的狀態,Status 部分用來描述觀測到的狀態。
今天我們將為大家介紹 K8s 的另外一個部分,即元數據部分。該部分主要包括了用來識別資源的標簽:Labels, 用來描述資源的注解;Annotations, 用來描述多個資源之間相互關系的 OwnerReference。這些元數據在 K8s 運行中有非常重要的作用。
2. labels
第一個元數據,也是最重要的一個元數據——資源標簽。資源標簽是一種具有標識型的 Key:Value 元數據,如下圖所示,展示了幾個常見的標簽。
前三個標簽都打在了 Pod 對象上,分別標識了對應的應用環境、發布的成熟度和應用的版本。從應用標簽的例子可以看到,標簽的名字包括了一個域名的前綴,用來描述打標簽的系統和工具, 最后一個標簽打在 Node 對象上,還在域名前增加了版本的標識 beta 字符串。
標簽主要用來篩選資源和組合資源,可以使用類似于 SQL 查詢 select,來根據 Label 查詢相關的資源。
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,也可以用“,”來連接,同樣的標識邏輯”與“的關系。
4. Annotations
另外一種重要的元數據是:annotations。一般是系統或者工具用來存儲資源的非標示性信息,可以用來擴展資源的 spec/status 的描述,這里給了幾個 annotations 的例子:
第一個例子,存儲了阿里云負載器的證書 ID,我們可以看到 annotations 一樣可以擁有域名的前綴,標注中也可以包含版本信息。第二個 annotation存儲了 nginx 接入層的配置信息,我們可以看到 annotations 中包括“,”這樣無法出現在 label 中的特殊字符。第三個 annotations 一般可以在 kubectl apply 命令行操作后的資源中看到, annotation 值是一個結構化的數據,實際上是一個 json 串,標記了上一次 kubectl 操作的資源的 json 的描述。
5. Ownereference
最后一個元數據叫做 Ownereference。所謂所有者,一般就是指集合類的資源,比如說 Pod 集合,就有 replicaset、statefulset,這個將在后序的課程中講到。
集合類資源的控制器會創建對應的歸屬資源。比如:replicaset 控制器在操作中會創建 Pod,被創建 Pod 的 Ownereference 就指向了創建 Pod 的 replicaset,Ownereference 使得用戶可以方便地查找一個創建資源的對象,另外,還可以用來實現級聯刪除的效果。** **
二、操作演示
這里通過 kubectl 命令去連接我們 ACK 中已經創建好的一個 K8s 集群,然后來展示一下怎么查看和修改 K8s 對象中的元數據,主要就是 Pod 的一個標簽、注解,還有對應的 Ownerference。
首先我們看一下集群里現在的配置情況:
- kubectl get pods
- kubectl apply -f pod1.yaml
- kubectl apply -f pod2.yaml
- kubectl get pods —show-labels
- kubectl get pods nginx1 -o yaml | less
- kubectl label pods nginx1 env=test
- kubectl label pods nginx1 env=test —overwrite
- kubectl get pods —show-labels
- kubectl label pods nginx tie-
- kubectl get pods —show-labels
- kubectl get pods —show-labels -l env=test
- kubectl get pods —show-labels -l env=test,env=dev
- kubectl get pods —show-labels -l env=dev,tie=front
- kubectl get pods —show-labels -l ’env in (dev,test)’
- kubectl annotate pods nginx1 my-annotate=‘my annotate,ok’
- kubectl get pods nging1 -o yaml | less
然后我們這里其實也能夠看到有一個 kubectl apply 的時候,kubectl 工具增加了一個 annotation,這也是一個 json 串。
- kubectl apply -f rs.yaml
- kubectl get replicasets ?nginx-replicasets -o yaml |less
- **kubectl get pods **
- kubectl get pods nginx-replicasets-rhd68 -o yaml | less
三、控制器模式
1、控制循環
控制型模式最核心的就是控制循環的概念。在控制循環中包括了控制器、被控制的系統,以及能夠觀測系統的傳感器,三個邏輯組件。
當然這些組件都是邏輯的,外界通過修改資源 spec 來控制資源,控制器比較資源 spec 和 status,從而計算一個 diff,diff 最后會用來決定執行對系統進行什么樣的控制操作,控制操作會使得系統產生新的輸出,并被傳感器以資源 status 形式上報,控制器的各個組件將都會是獨立自主地運行,不斷使系統向 spec 表示終態趨近。
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 所共享。之后,再把這個事件交給事件的回調函數
控制循環中的控制器組件主要由事件處理函數以及 worker 組成,事件處理函數之間會相互關注資源的新增、更新、刪除的事件,并根據控制器的邏輯去決定是否需要處理。對需要處理的事件,會把事件關聯資源的命名空間以及名字塞入一個工作隊列中,并且由后續的 worker 池中的一個 Worker 來處理,工作隊列會對存儲的對象進行去重,從而避免多個 Woker 處理同一個資源的情況。
Worker 在處理資源對象時,一般需要用資源的名字來重新獲得最新的資源數據,用來創建或者更新資源對象,或者調用其他的外部服務,Worker 如果處理失敗的時候,一般情況下會把資源的名字重新加入到工作隊列中,從而方便之后進行重試。
3、控制循環例子-擴容
這里舉一個簡單的例子來說明一下控制循環的工作原理。
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 達成一致。
四、控制器模式總結
1、兩種 API 設計方法
Kubernetes 控制器模式依賴聲明式的 API。另外一種常見的 API 類型是命令式 API。為什么 Kubernetes 采用聲明式 API,而不是命令式 API 來設計整個控制器呢?
首先,比較兩種 API 在交互行為上的差別。在生活中,常見的命令式的交互方式是家長和孩子交流方式,因為孩子欠缺目標意識,無法理解家長期望,家長往往通過一些命令,教孩子一些明確的動作,比如說:吃飯、睡覺類似的命令。我們在容器編排體系中,命令式 API 就是通過向系統發出明確的操作來執行的。
而常見的聲明式交互方式,就是老板對自己員工的交流方式。老板一般不會給自己的員工下很明確的決定,實際上可能老板對于要操作的事情本身,還不如員工清楚。因此,老板通過給員工設置可量化的業務目標的方式,來發揮員工自身的主觀能動性。比如說,老板會要求某個產品的市場占有率達到 80%,而不會指出要達到這個市場占有率,要做的具體操作細節。
類似的,在容器編排體系中,我們可以執行一個應用實例副本數保持在 3 個,而不用明確的去擴容 Pod 或是刪除已有的 Pod,來保證副本數在三個。
2、命令式 API 的問題
在理解兩個交互 API 的差別后,可以分析一下命令式 API 的問題。
- 命令 API 最大的一個問題在于錯誤處理;
在大規模的分布式系統中,錯誤是無處不在的。一旦發出的命令沒有響應,調用方只能通過反復重試的方式來試圖恢復錯誤,然而盲目的重試可能會帶來更大的問題。
假設原來的命令,后臺實際上已經執行完成了,重試后又多執行了一個重試的命令操作。為了避免重試的問題,系統往往還需要在執行命令前,先記錄一下需要執行的命令,并且在重啟等場景下,重做待執行的命令,而且在執行的過程中,還需要考慮多個命令的先后順序、覆蓋關系等等一些復雜的邏輯情況。
- 實際上許多命令式的交互系統后臺往往還會做一個巡檢的系統,用來修正命令處理超時、重試等一些場景造成數據不一致的問題;
然而,因為巡檢邏輯和日常操作邏輯是不一樣的,往往在測試上覆蓋不夠,在錯誤處理上不夠嚴謹,具有很大的操作風險,因此往往很多巡檢系統都是人工來觸發的。
- 最后,命令式 API 在處理多并發訪問時,也很容易出現問題;
假如有多方并發的對一個資源請求進行操作,并且一旦其中有操作出現了錯誤,就需要重試。那么最后哪一個操作生效了,就很難確認,也無法保證。很多命令式系統往往在操作前會對系統進行加鎖,從而保證整個系統最后生效行為的可預見性,但是加鎖行為會降低整個系統的操作執行效率。
- 相對的,聲明式 API 系統里天然地記錄了系統現在和最終的狀態。
不需要額外的操作數據。另外因為狀態的冪等性,可以在任意時刻反復操作。在聲明式系統運行的方式里,正常的操作實際上就是對資源狀態的巡檢,不需要額外開發巡檢系統,系統的運行邏輯也能夠在日常的運行中得到測試和錘煉,因此整個操作的穩定性能夠得到保證。
最后,因為資源的最終狀態是明確的,我們可以合并多次對狀態的修改。可以不需要加鎖,就支持多方的并發訪問。
3、控制器模式總結
最后我們總結一下:
本文總結
這里為大家簡單總結一下本文的主要內容:
- Kubernetes 資源對象中的元數據部分,主要包括了用來識別資源的標簽:Labels, 用來描述資源的注解;Annotations, 用來描述多個資源之間相互關系的 OwnerReference。這些元數據在 K8s 運行中有非常重要的作用;
- 控制型模式中最核心的就是控制循環的概念;
- 兩種 API 設計方法:聲明式 API 和命令式 API ;Kubernetes 所采用的控制器模式,是由聲明式 API 驅動的;
阿里巴巴云原生微信公眾號(ID:Alicloudnative)關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦云原生流行技術趨勢、云原生大規模的落地實踐,做最懂云原生開發者的技術公眾號。
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的从零开始入门 K8s| K8s 的应用编排与管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 降低 80% 的读写响应延迟!我们测评了
- 下一篇: Knative 实战:三步走!基于 Kn