標籤:

Kubernetes 在華為全球 IT 系統中的實踐 | 架構師實踐日

編者按:Kubernetes 是 Google 開源的 Docker 容器集群管理系統,是 Docker 生態圈中的重要一員。隨著 Kubernetes 社區及各大廠商的不斷改進、發展,Kuberentes 已經成為容器管理領域的領導者之一。在 ArchSummit 全球架構師峰會七牛雲專場上中,華為高級工程師王澤鋒為大家帶來了「Kubernetes 在華為全球 IT 系統中的實踐」的分享,以下是對他的演講內容的整理。

王澤鋒,華為 PaaS 開發部高級工程師,多年電信領域系統軟體開發和性能調優經驗,對深度報文解析,協議識別頗有研究。華為 PaaS 平台核心開發者,主導負責最初版本的運維繫統設計和開發,華為公有雲 PaaS CCE 服務的多 AZ 支持、親和/反親和性調度設計和開發。目前擔任 Kubernetes 社區的 Committer,主導 Kubernetes 社區親和性調度相關特性開發工作。個人對社區化的項目開源運作模式有濃厚的興趣。

v.qq.com/x/page/a0315kj

▲ 分享實錄

簡單介紹

圖 1

華為作為一個全球性的公司,其 IT 系統規模非常龐大,部署在全球各個地區,業務量大,應用數量急劇增長。隨著規模的愈發龐大,問題也越來越突出:

  1. 資源利用率低,虛擬機增長過快,成本激增。
  2. 跨區域多 DC 的部署維護無法拉伸,運維投入巨大。
  3. 系統重載、應用伸縮周期長。
  4. 難以支持快速迭代,業務上線效率低。

圖 2

面對業務壓力,Cloud Container Engine(以下簡稱 CCE)的出現很好的解決了以上問題。華為的 CCE 可以理解為是華為的 Kubernetes 商業版本(圖 2),整體上是希望通過一套核心技術,去打通 IaaS 和 PaaS 平台,一套技術支撐多種業務場景。

圖 3

2015 年底 CCE 在華為 IT 系統上線(圖 3),資源利用率提升了 3 到 4 倍,當前的規模達到 2000 多個虛機。下面重點講下近期的兩大重點技術實踐: Kubernetes 多集群聯邦、應用間的親和/反親和調度。

Kubernetes 多集群聯邦

圖 4

圖 4 是 Kubernetes 非常簡單的架構圖,圖中可以看到如下組件:

- Pod(容器組)

- Container(容器)

- Label(標籤)

- Replication Controller(副本控制器)

- Service(服務)

- Node(節點)

- Kubernetes Master(Kubernetes 主節點)

其基本調度單位是 Pod ,具體應用下面實例會提到。

圖 5

從華為內部 IT 系統的某項業務估算(圖 5),其機器數量在 2016 年整體容器化之後,會達到 3 萬虛機和 10 萬容器。華為 IT 系統的典型場景是通過多數據中心統一管理部署應用,提供跨域的應用服務發現。但是跨域的網路限制與差異較大,難以通過 k8s 單集群支持。所以如何提供跨域應用的大規模集中部署成為目前需要解決的問題。

圖 6

圖 6 是一個簡單的多集群聯邦架構示意圖,圖中的 3 個黑塊的是在集群聯邦層面加出來的三個關鍵組件,底下白色對應的是一個集群,這個架構跟 Kubernetes 原來的架構非常相似。原來 Kubernetes 的架構也是一個 Master 管理多個節點,這裡是一個 Master 管理多個集群。如果把圖中的集群換成節點,其實就是 Kubernetes 的架構。用戶通過統一的 API 入口創建應用,集群聯邦所做的就是把聯邦級別對象應用拆分到各個子集群分別部署。Cluster controller 會維護各個集群的健康狀態、監控負載情況,而 Service controller 做一個提供跨集群的服務發現的打通。

圖 7

總的來說,現在集群管理架構都比較相似,都有 Controller、Scheduler 和 Agent(Kubelet),這裡是利用 List-Watch(圖 7)機制實現的組件間交互的解耦,體現了 Everything talk to API 的設計理念。比較類似 SOA 架構中的消息匯流排,但跟消息匯流排的差別在於對數據的存儲和事件通知做了統一化處理,當集群收到新的事件的同時,可以拿到這一份新的數據,包括數據的變化是新增還是更新了某個對象。

實例說明:這時要創建一個 ReplicaSet ,第一步是集群起來後,Controller-manager(包含多個 Controller)、Scheduler 和 Kubelet 都會去發起 watch,但是 watch 的對象都是不一樣的。ReplicaSet Controller 會 watch ReplicaSet,Scheduler watch 的是一個 Pod(也可以理解為一個應用的實例)。這裡的 watch 是帶一個條件的,destNode 為空,就是未調度的 Pod ;而 Kubelet watch 的 Pod 只是 destNode 為 Node 自身的 Pod,也就是這個 Pod 調度到相應的節點才會做相應的處理。在創建應用的時候,k8s 會先將對象保存到 etcd 中,同時存完之後會上報已創建的事件。與此同時 ReplicaSet Controller 已經事先 watch 這個事件,它就會收到通知。ReplicaSet Controller 發現有一個新的 RS 被創建,這個時候它會去做拆分,因為 RS 對應的就是一個多實例無狀態的應用,所以 RS 有多少個副本,Controller 就創建多少個相同的 Pod 。

這樣第一階段創建的過程已經完成。因為 Scheduler watch 了 Pod,新創建的 Pod 是沒有被調度過的,destNode 是空的,這個時候它會收到 Pod 創建的通知,並執行調度演算法(把這個集群中所有可用節點的數據根據 Pod 的調度需求做一個綜合計算,選出一個 Node),然後將 Pod 綁定到這個 Node 上(更新這個 Pod 的 destNode),這樣 Scheduler 的流程就結束了。然後在相對應被綁定的 Node 上,Kubelet 會發現有新的 Pod 調度過來,就會按照 Pod 的定義創建並啟動容器。

經過上述的流程可知,各個組件通過 List-Watch 不同的對象(即便是相同的對象,狀態也是不一樣的),實現天然的解耦,各個組件處理一個應用生命周期的不同階段,同時保證了在一個非同步的分散式系統裡面多組件間處理流程的先後順序問題。同時 List-Watch 每次只是做事件的監控,所以每次 watch 比如創建一個 Pod 獲取的是增量信息,獲取的數量交互是非常少的。我們知道通過消息匯流排傳一個消息/事件,是有可能丟失的,這就需要有一個容錯機制,來保證最終一致性。Kubernetes 內部是通過 List 來解決的。比如 ReplicaSet Controller 會周期性地對 ReplicaSet 做全量 List ,拿到的數據就是當前系統需要起的應用數據,它會檢查當前各個應用的實例運行情況,做相應處理,多餘的刪除,不足的補齊。

圖 8

圖 8 是集群聯邦下的應用創建流程。如圖所示,用戶向聯邦 API Server 發請求創建應用,它會首先將對象保存起來。同時 Controller 會周期性獲取各集群的監控數據,同步到 API Server。在 Scheduler watch 到創建應用之後,它會根據調度演算法,按照聯邦裡面所有的集群監控信息包括健康情況等指標篩選出合適的集群,對應用做拆分(當前主要是各個集群的容量和監控負載情況)。這個例子裡面應用被拆分到了兩個集群,cluster A 是 2 個實例、cluster B 是 3 個實例,拆分後仍是把這個對象保存到 API Server,跟 Kubernetes 原生在單集群下創建的流程是一致的。這個時候 Controller 會 watch 到 sub RS 創建(實際上是 RS 對象的一個屬性更新),它會去對應的集群創建實際的應用實體,實例數量為前面應用拆分時的運算值。

圖 9

圖 9 呈現了聯邦調度器的關鍵機制,它 watch 的是兩類對象,一個是副本集,一個是 Cluster(聯邦裡面有多少可用集群,它們負載的情況等)。當 RC/RS 有變化時,會將它們保存到一個本地隊列,worker 會從這個本地隊列中逐個讀取 RC/RS 並處理。處理時根據載入的調度策略篩選出合適的目標集群,然後去計算出分別應該在這幾個集群創建多少個實例。最後寫回到 API Server 的數據格式如圖所示,是更新 RS 的一個欄位 destClusters,未調度時是空值,現在變成 [{cluster: "clusterA", replicas: 6}, ... ]這樣的內容。

圖 10

集群聯邦中包含多個 Controller,這個例子(圖 10)里主要涉及兩個,一個是 Cluster Controller,一個是 Replication Controller。Cluster Controller watch 的是 cluster 對象,維護聯邦中所有 Cluster 狀態信息,包括周期性的健康檢查、負載情況等。如果配置了安全訪問,還需要確保配置(比如訪問集群所用的證書文件)是正確可用的。Replication Controller 則 watch RS,剛才提到聯邦調度器也會 watch RS,這裡 watch 的是已經過調度器拆分處理的 RS,並負責到各個集群創建指定數量的實例。

圖 11

接著前面的例子,拆分到 Cluster 的 A 是 2 個實例,拆分到 Cluster 的 B 是 3 個實例,這個實際上可以理解為一個控制面的處理,解決了一個應用在聯邦下部署到多個集群的問題。接下來要解決數據面的問題,部屬下去之後如何實現應用跨集群的訪問(圖 11)。我們知道在單集群裡面是全通的網路,但是跨集群的我們還希望適配混合雲的場景,這個時候底層的網路不一定是通的,因此我們直接按照集群間網路不通的場景來設計。從外部流量來說,用戶請求會通過全球分散式路由被分攤到各個集群,每個集群有一個負載均衡,再把流量導到各個 Node 上去。跨集群訪問的時候,集群內的訪問仍可以按原來的服務發現機制處理。跨集群的時候因為是跨網路訪問,網路是不通的,這裡有個約束是每個集群的負載均衡器需要有 public IP,可以被所有集群內的所有 Node 訪問。當應用的訪問請求需要跨集群的時候,流量會被轉發到對端集群的負載均衡器上,由它再做反向代理,再轉到後端應用實例所在的 Node 上去。

圖 12

圖 12 是 Service Controller 的關鍵機制:一個是 watch Service 變化、一個是 watch Cluster 變化。如果某個 Cluster 掛了流量就不應該轉發過去,因此要 watch 它的變化,在服務裡面刷新每個服務後端(Cluster)的聯通性,保證它的每一個後端都是通的。同時要 watch 服務的變化,可能是新創建服務,服務後端的實例被刷新,或者說是服務後端可能有 LB 掛掉,對於這個服務來說都是有變化的,這個時候要去做相應處理。

Cluster 處理的就是 Cluster 的狀態發生變化的情況。這裡簡單介紹下新增服務時的處理流程。在聯邦 Service Controller 中,每個集群都有服務對象,服務包含了 Endpoint,這裡有兩類,一個是當前集群內服務的 Pod 實例所在的 Node,另外跨集群的時候,需要訪問到其它集群所在的 LB,它是把這兩組信息都寫到 kube-proxy 裡面去。同時為了對外提供訪問支持,需要按集群分別將本集群中服務實例的 Endpoint 寫到對應集群的 LB 中,做反向代理。這樣就支持了單集群內的通信和跨集群通信。對於外部流量,因為最前面是全球分散式路由,用戶在訪問的時候,可以根據用戶的地域或訪問時延等因素,選擇一個就近的集群的 LB,以獲得較為理想的訪問速度。

應用間的親和/反親和調度

圖 13

容器化和微服務化的改造會出現親和性和反親和性(圖 13)的疑問,原來一個虛機上會裝多個組件,進程間會有通信。但是在做容器化拆分的時候,往往直接按進程拆分容器,比如業務進程一個容器,監控日誌處理或者本地數據放在另一個容器,並且有獨立的生命周期。這時如果他們分布在網路中兩個較遠的點,請求經過多次轉發,性能會很差。所以希望通過親和性實現就近部署,然後增強網路能力實現通信上的就近路由,減少網路的損耗。反親和性主要是出於高可靠性考慮,盡量分散實例,某個節點故障的時候,對應用的影響只是 N 分之一或者只是一個實例。

圖 14

先來看一下單應用的情況(圖 14)。在這個例子里,我們希望所有的實例部署到一個 AZ 裡面(即在 AZ 級別互相親和),同時在 Node 級別互相反親和,每個 Node 部署一個實例。這樣某個 Node 掛的時候,只會影響一個實例。但是每個不同的企業,在不同的平台上,對於這種 AZ,Rack,Node 等 failure domain 的理解和命名都不一樣。比如有人會希望應用在機框級別做反親和,因為有可能一個機框都會 down 掉。但本質上它們都是 Node 上的一個 label,調度時只需要按這個特殊的 label 進行動態分組,處理親和反親和的關係即可。

圖 15

在親和性、反親和性的支持上面,會實現硬性和軟性兩種支持(圖 15),因為如果是全硬性條件,很容易因為配置不恰當導致應用部署失敗。這裡實質上是兩類演算法,但是在實現邏輯上比較接近。硬性演算法做過濾,不滿足的節點都全部過濾掉,保證最後留下來的一定是滿足條件的。在軟性的情況下,不需要這麼苛刻地要求,只是 best effort,條件不滿足時仍希望應用調度部署成功,這裡用的是評分排序,根據親和性和反親和性的符合情況,符合程度高的給高分,符合程度低的給低分,選 Node 時按分數從高到低進行選擇。

圖 16

親和性是相互的,這個時候就會考慮對稱性的問題(圖 16)。兩個應用互相親和,在應用的定義裡面,比如應用 B 親和應用 A,實際是在應用 B 裡面寫一條親和的規則去親和 A,但是 A 不知情。如果 A 先創建 B 後創建,是沒有問題的,因為 B 會去找 A,但是反過來 A 不會去找 B。因此我們在做演算法實現的時候給它加了一個默認行為,在調度的時候,需要自己親和/反親和哪些 Pod(前面的單向思路);同時又要去檢查哪些 Pod 親和/反親和自己,以此實現對稱性 。

另外一個常見的問題是被親和的應用發生遷移的情況。需要說明的有兩點,一是在演算法設計上做了對稱性的考慮,不管是先部署還是後部署,即便這個應用掛了,它在重建並被調度時,依然會檢查當前系統里親和哪些 Pod 或者被哪些 Pod 親和,優先跟他們部到一起去。另外,目前 RC/RS (副本集無狀態應用)只有 Node 掛掉的時候才會發生重建 Pod 的情況,Node 沒有掛,異常退出是在原地自己重啟的。這個從兩個層面上,能夠保證親和的應用不在一起、反親和的應用分開這種需求。

圖 17

這時做了硬性和軟性兩種實現,在做軟性的時候沒問題,因為即便不滿足也能調度成功,按照完全對稱的思路就可以做了。這裡最主要的問題就是硬性親和的對稱性。親和其他應用的場景,如果被親和的應用不存在(未調度),調度失敗是合理的。但是如果因為不存在其他的 Pod 親和當前待調度的 Pod 而導致失敗就不合理。因此硬親和不是完全對稱的,它的反向是一個軟親和。

圖 18

儘管我們在親和性/反親和性的實現上做了對稱性考慮,但它只能在涉及調度的階段起作用,調度後,一個 Pod 的生命周期內,不會再根據實際的情況進行調整。因此後面我們會從兩個方面進一步解決前面的問題(圖 18)。

第一個是限制異地重啟,即 Forgiveness 。假如 Node 故障或重啟導致Pod被遷移( Pod 重建後被調度到其他 Node ),假如 Pod 原先保存了一些本地數據,但是因為被遷移到了別的節點,這些數據相當於丟失。所以這個時候給它配一個 Forgiveness 策略,指定 Pod 綁定在它所運行的 Node 上,如果這個 Node 掛了,不遷移 Pod,而是等待 Node 自動恢復的時候,把這個 Pod 原地拉起。這樣原來存在本地硬碟里的數據,就可以被處理。對於有實效性的本地數據,我們還引入了超時機制。比如本地暫存的日誌,超過兩天三天之後,這個日誌再做後續處理也沒有意義。這個時候 Pod 需要在兩三天之內綁定在這個 Node 上,等到 Node 恢復再處理任務。如果超過兩三天 Node 還沒有恢復,但這些本地數據已經失去意義,這時候,我們希望 Pod 可以被遷移。在做驅逐檢查的時候,沒有超時的 Forgiveness Pod 就繼續綁定在 Node 上,超時的正常驅逐進行遷移就可以了。

第二個是運行時遷移。在運行的過程中,集群負載、應用間親和性的滿足程度、Node 的健康度或者服務質量時時刻刻都在變化,系統應該能夠根據這個當前的情況去動態地調整應用實例的分布,這就是 Rescheduling 的意義。Rescheduling 最終體現的行為是周期性地檢查集群狀態,並做遷移的調整。但在遷移之前,有許多要考慮的因素,比如應用的可用性問題(不能因為遷移導致某個應用的所有實例在某個時刻不可用),遷移的單向性(被遷移的 Pod 如果回到原來的 Node,就變成多此一舉)和收斂性(不能因為遷移某個 Pod 而引發大量其他 Pod 被遷移)。

推薦閱讀:

攜程容器雲實踐

TAG:架构 | 容器云 |