《深入剖析Kubernetes》總結五:控制器與編排

在線任務編排

控制器(Controller):操作容器,比如Deployment,是k8s第一個重要的設計思想,叫作控制器模式

kube-controller-manager:一系列控制器的集合

$ ls -d kubernetes/pkg/controller/
deployment/ job/ podautoscaler/
cloud/ disruption/ namespace/
replicaset/ serviceaccount/ volume/
cronjob/ garbagecollector/ nodelifecycle/

該目錄下的控制器遵循同一個通用編排模式:控制循環(control loop)。原理類似CAS操作,即如果對象X的實際狀態等於期望狀態,則可以什麼都不做;如果不等於,則執行編排動作,將實際狀態調整爲期望狀態

實際狀態來源:kubelet 通過心跳彙報的容器狀態和節點狀態、監控系統中保存的應用監控數據,控制器主動收集的它自己感興趣的信息等

期望狀態:一般來自於用戶提交的 YAML 文件

主要編排邏輯就是對比操作,被叫作調諧(Reconcile),這個調諧的過程,則被稱作“Reconcile Loop”(調 諧循環)或者“Sync Loop”(同步循環);
而調諧的最終結果,往往都是對被控制對象的某種寫操作。 比如,增加 Pod,刪除已有的 Pod,或者更新 Pod 的某個字段,這也是 Kubernetes “面向 API 對象編程”的一個直觀體現。

以Deployment爲例:
在這裏插入圖片描述
如上圖所示,類似 Deployment 這樣的一個控制器,實際上都是由上半部分的控制器定義(包括期望狀態),加上下半部分的被控制對象的模板組成的;
Deployment 裏的 replicas=2 這個字段負責定義被管理對象的期望狀態;
被控制對象的定義,則來自於一個“模板”,比如,Deployment 裏的 template 字段, 這個 template 字段裏的內容,跟一個標準的 Pod 對象的 API 定義是一樣的,所有被這個 Deployment 管理的 Pod 實例,其實都是根據這個 template 字段的內容創建出來的。

在所有 API 對象的 Metadata 裏,都有一個字段叫作 ownerReference,用於保存當前這個 API 對象的擁有者(Owner)的信息;
對於一個 Deployment 所管理的 Pod,其ownerReference爲ReplicaSet,詳情請繼續往下看

控制器模式的實現介紹:Deployment

Deployment實現了Pod 的“水平擴展 / 收縮”(horizontal scaling out/in);
比如如果更新了 Deployment 的 Pod 模板(比如,修改了容器的鏡像),那麼 Deployment 就需要遵循一種叫作“滾動更新”(rolling update)的方式,來升級現有的容器。

實現方式:ReplicaSet(API對象),其結構爲:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
    name: nginx-set
    labels:
    	app: nginx
spec:
	replicas: 3
	selector:
		matchLabels:
			app: nginx
    template:
        metadata:
            labels:
            app: nginx
    spec:
        containers:
            - name: nginx
            image: nginx:1.7.9

由副本數目的定義和一個Pod模板組成,其實是Deployment的一個子集,Deployment 控制器實際操縱的就是這樣的 ReplicaSet 對象,而不是 Pod 對 象

對於一個 Deployment 所管理的 Pod,它的 ownerReference 是ReplicaSet

如下圖是一個示例:
在這裏插入圖片描述
“水平擴展 / 收縮”的實現:Deployment Controller 修改它所控制的 ReplicaSet 的 Pod 副本個數

”滾動更新“的實現:修改了 Deployment 裏的 Pod 定義之後,Deployment Controller 會使用這個修改後的 Pod 模板,創建一個新的 ReplicaSet,這個新的 ReplicaSet 的初始 Pod 副本數是:0;
然後,Deployment Controller 開始將這個新的 ReplicaSet 所控制的 Pod 副本數從 0 個變成 1 個,即:“水平擴展”出一個副本;
緊接着,Deployment Controller 又將舊的 ReplicaSet所控制的舊 Pod 副本數減少一個,即:“水平收縮”掉一個副本;
如此交替進行,新 ReplicaSet 管理的 Pod 副本數,從 0 個變成 1 個,再變成 2 個。最後變成x個,而舊的 ReplicaSet 管理的 Pod 副本數則從 x 個最後變成 0 個,就完成了這一組 Pod 的版本升級過程
在這裏插入圖片描述
Deployment 對應用進行版本控制的具體原理也是基於滾動更新來實現的,可以根據不同的版本號進行控制,有種git的感覺

Deployment缺點:無法處理有狀態的應用。

因爲 Deployment 對應用做了一個簡單化假設,它認爲一個應用的所有 Pod是完全一樣的,所以它們互相之間沒有順序,也無所謂運行在哪臺宿主機上;
需要的時候,Deployment 就可以通過 Pod 模板創建新的 Pod;
不需要的時候,Deployment 就可以“殺掉”任意一個 Pod

但是實際場景並非如此,比如分佈式應用中,它的多個實例之間有依賴關係,比如:主從關係、主備關係,這種實例關係是不對等的,以及實例對外部數據有依賴關係的應用(數據存儲類應用的多個實例往往都會在本地磁盤上保存一份數據,而這些實例一 旦被殺掉,即便重建出來,實例與數據之間的對應關係也已經丟失,從而導致應用失敗),就被稱爲“有狀態 應用”(Stateful Application)。

StatefulSet

拓撲狀態

支持有狀態應用,比如拓撲狀態和存儲狀態,其核心功能就是通過某種方式記錄這些狀態,然後在 Pod 被重新創建時, 能夠爲新 Pod 恢復這些狀態

Headless Service:

Service 是 Kubernetes 項目中用來將 一組 Pod 暴露給外界訪問的一種機制,可以 定義一個 Service,然後,用戶只要能訪問到這個 Service,它就能訪問到某個具體的 Pod

Service被訪問的方式:

1 虛擬ip:訪問Service的ip,它會把請求轉發到該 Service 所代理 的某一個 Pod 上

2 Service 的 DNS:訪問DNS記錄,就可以訪問到指定的Service所代理的某一個Pod
A Normal Service:訪問DNS記錄,解析到的是Service的ip,後面的流程就跟1一樣
B Headless Service:訪問DNS記錄,解析到的是某一個Pod的ip,不需要再經過Service的代理了

StatefulSet 通過 Headless Service 的方式,爲每個 Pod 創建了一個固定並且穩定 的 DNS 記錄,來作爲它的訪問入口(ip可能不同,但是經過這個DNS記錄一定可以找到對應的Pod就行);
在部署“有狀態應用”的時候,應用的每個實例擁有唯一併且穩定的“網絡標識”,是 一個非常重要的假設,用ip的話,是不穩定的

StatefulSet 的主要作用之一就是使用 Pod 模板創建 Pod 的時候, 對它們進行編號,並且按照編號順序逐一完成創建工作;
當 StatefulSet 的“控 制循環”發現 Pod 的“實際狀態”與“期望狀態”不一致,需要新建或者刪除 Pod 進行“調諧”的時候,它會嚴格按照這些 Pod 編號的順序,逐一完成這些操作

存儲狀態

先講講Persistent Volume Claim(PVC)和Persistent Volume(PV)

要在一個 Pod 裏聲明 Volume,只要在 Pod 里加上 spec.volumes 字段,然後就可以在這個字段裏定義一個具體類型的 Volume 了,比 如:hostPath;
但是如果不知道有哪些Volume可以使用,那麼就可以使用PVC,會使用PV對象裏配置的Volume;
PVC 和 PV 的設計,實際上類似於“接口”和“實現”的思想,開發者只要知道並會使用“接口”,即:PVC;而運維人員則負責給“接口”綁定具體的實現,即: PV

存儲狀態實現:爲 StatefulSet 額外添加一個 volumeClaimTemplates 字段,該字段表示凡是被這 個 StatefulSet 管理的 Pod,都會聲明一個對應的 PVC,PVC的名字會被分配一個與對應Pod完全一致的編號,等於是將PVC和Pod綁定起來,這樣就可以實現存儲狀態了

總結

首先,StatefulSet 的控制器直接管理的是 Pod;
因爲StatefulSet 裏的不同 Pod 實例, 不再像 ReplicaSet 中那樣都是完全一樣的,而是有了細微區別的;
比如,每個 Pod 的 hostname、名字等都是不同的、攜帶了編號的;
而 StatefulSet 區分這些實例的方式,就是通過在 Pod 的名字裏加上事先約定好的編號。

其次,Kubernetes 通過 Headless Service,爲這些有編號的 Pod,在 DNS 服務器中生成帶有同樣編號的 DNS 記錄,只要 StatefulSet 能夠保證這些 Pod 名字裏的編號不變,那麼 Service 裏的 DNS 記錄也就不會變;
記錄解 析出來的 Pod 的 IP 地址,則會隨着後端 Pod 的刪除和再創建而自動更新,這是 Service 機制本身的能力,不需要 StatefulSet 操心。

最後,StatefulSet 還爲每一個 Pod 分配並創建一個同樣編號的 PVC;
這樣,Kubernetes 就可以通過 Persistent Volume 機制爲這個 PVC 綁定上對應的 PV,從而保證了每一個 Pod 都擁有 一個獨立的 Volume

DaemonSet

作用:在 Kubernetes 集羣裏運行一個 Daemon Pod

特徵:

  1. 這個 Pod 運行在 Kubernetes 集羣裏的每一個節點(Node)上
  2. 每個節點上只有一個這樣的 Pod 實例
  3. 當有新的節點加入 Kubernetes 集羣后,該 Pod 會自動地在新節點上被創建出來;而當舊 節點被刪除後,它上面的 Pod 也相應地會被回收掉

場景:

  1. 各種網絡插件的 Agent 組件,都必須運行在每一個節點上,用來處理這個節點上的容器網絡
  2. 各種存儲插件的 Agent 組件,也必須運行在每一個節點上,用來在這個節點上掛載遠程存儲目錄,操作容器的 Volume 目錄
  3. 各種監控組件和日誌組件,也必須運行在每一個節點上,負責這個節點上的監控信息和日誌 蒐集

DaemonSet 開始運行的時機,很多時候比整個 Kubernetes 集羣出現的時機都要早,原因:

DaemonSet 會給 Pod 自動加上一個與調度相關的字段,叫作 tolerations,這個字段意味着這個 Pod,會“容忍”(Toleration)某些 Node 的“污點”(Taint),“容忍”所有被標記爲 unschedulable“污點”的 Node;“容忍”的效果是允許調度;
在正常情況下,被標記了 unschedulable“污點”的 Node,是不會有任何 Pod 被調度上去 的(effect: NoSchedule),可是,DaemonSet 自動地給被管理的 Pod 加上了這個特殊的 Toleration,就使得這些 Pod 可以忽略這個限制,繼而保證每個節點上都會被調度一個 Pod

DaemonSet Controller,首先從 Etcd 裏獲取所有的 Node 列表,然後遍歷所有的 Node,然後進行相應的操作:
1 刪除節點(Node)上多餘的 Pod:直接調用 Kubernetes API
2 在指定的 Node 上創建新 Pod:使用nodeSelector,選擇 Node 的名字(nodeAffinity用來替代nodeSelector,可以支持更豐富的語法)

原理總結:
在控制循環中遍歷所有節點,然後根據節點上是否有被管理 Pod 的情況,來決定是否要創建或者刪除一個 Pod;
在創建每個 Pod 的時候,DaemonSet 會自動給這個 Pod 加上一個 nodeAffinity,從 而保證這個 Pod 只會在指定節點上啓動;
同時還會自動給這個 Pod 加上一個 Toleration,從而忽略節點的 unschedulable“污點”,也可以在Pod模板里加上更多種類的Toleration

DaemonSet 控制器操作的是 Pod,不想Deployment有 ReplicaSet 這樣的對象參與其中,所以根據“面向API對象”思路,就設計出了ControllerRevision,專門用來記錄某種 Controller 對象的版本;
其Data 字段保存了該版本對應的完整的 DaemonSet 的 API 對象,Annotation 字段保存了創建這個對象所使用的 kubectl 命令。

離線任務編排

Deployment、StatefulSet、DaemonSet主要編排的對象是在線業務,即Long Running Task(長作業)

Job用來編排離線業務

Job 對象在創建後,它的 Pod 模板被自動加上了一個 ”controller-uid=< 一 個隨機字符串 > “這樣的 Label;
而這個 Job 對象本身,則被自動加上了這個 Label 對應的 Selector,從而保證了 Job 與它所管理的 Pod 之間的匹配關係;
而 Job Controller 之所以要使用這種攜帶了 UID 的 Label,就是爲了避免不同 Job 對象所管理 的 Pod 發生重合。需

restartPolicy字段用來定義離線任務失敗後的操作,Never表示重新創建Pod, Job 對象的 spec.backoffLimit 字段裏定義了重試次數

Job Controller也可以用並行的方式去運行。控制的參數爲:

  1. spec.parallelism:一個 Job 在任意時間最多可以啓動多少個 Pod 同時運行;
  2. spec.completions: Job 至少要完成的 Pod 數目,即 Job 的最小完成數。

Cronjob:定時任務,CronJob 是一個 Job 對象的控制器(Controller),CronJob 與 Job 的關係,正如同 Deployment 與 Pod 的關係一樣,CronJob 是一個專門用來管理 Job 對象的控制器;
Cronjob會定義一個時間,每到這個時間,就會產生一個Job來執行任務

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