歡迎光臨
每天分享高質量文章

Kubernetes架構為什麼是這樣的?

小編序:

在上週釋出的《從“鴻溝理論”看雲原生,哪些技術能夠跨越鴻溝?》一文中,靈雀雲CTO陳愷表示:Kubernetes在雲端計算領域已經成為既定標準,進入主流市場,最新版本主要關註在穩定性、可擴充套件性方面,在開發人員中變得非常流行。Kubernetes會越來越多往下管理所有基礎設施,往上管理所有種類的應用。我們會看到,越來越多的周邊技術向它靠攏,在其之上催化出一個龐大的雲原生技術生態。

那麼,現在最新最流行的 Kubernetes 架構是什麼樣子呢?本文給大家介紹一下 Kubernetes 整體架構,並深入探討其中 2 個比較關鍵的問題。


來源:深入淺出談架構(deep-easy-arch)

作者:邵明岐


Kubernetes 架構解析


 首先,Kubernetes 的官方架構圖是這樣的:



這個架構圖看起來會比較複雜,很難看懂,我把這個官方的架構圖重新簡化了一下,就會非常容易理解了:



  • ETCD :是用來儲存所有 Kubernetes 的叢集狀態的,它除了具備狀態儲存的功能,還有事件監聽和訂閱、Leader選舉的功能,所謂事件監聽和訂閱,各個其他元件通訊,都並不是互相呼叫 API 來完成的,而是把狀態寫入 ETCD(相當於寫入一個訊息),其他元件透過監聽 ETCD 的狀態的的變化(相當於訂閱訊息),然後做後續的處理,然後再一次把更新的資料寫入 ETCD。所謂 Leader 選舉,其它一些元件比如 Scheduler,為了做實現高可用,透過 ETCD 從多個(通常是3個)實體裡面選舉出來一個做Master,其他都是Standby。


  • API Server:剛才說了 ETCD 是整個系統的最核心,所有元件之間通訊都需要透過 ETCD,實際上,他們並不是直接訪問 ETCD,而是訪問一個代理,這個代理是透過標準的RESTFul API,重新封裝了對 ETCD 介面呼叫,除此之外,這個代理還實現了一些附加功能,比如身份的認證、快取等。這個代理就是 API Server。


  • Controller Manager:是實現任務排程的,關於任務排程可以參考之前的文章,簡單說,直接請求 Kubernetes 做排程的都是任務,比如 Deployment 、Deamon Set 或者 Job,每一個任務請求傳送給Kubernetes之後,都是由Controller Manager來處理的,每一個任務型別對應一個Controller Manager,比如 Deployment對應一個叫做 Deployment Controller,DaemonSet 對應一個 DaemonSet Controller。


  • Scheduler:是用來做資源排程的,Controller Manager會把任務對資源要求,其實就是Pod,寫入到ETCD裡面,Scheduler監聽到有新的資源需要排程(新的Pod),就會根據整個叢集的狀態,給Pod分配到具體的節點上。


  • Kubelet:是一個Agent,執行在每一個節點上,它會監聽ETCD中的Pod資訊,發現有分配給它所在節點的Pod需要執行,就在節點上執行相應的Pod,並且把狀態更新回到ETCD。


  • Kubectl: 是一個命令列工具,它會呼叫 API Server傳送請求寫入狀態到ETCD,或者查詢ETCD的狀態。 


如果還覺得不清楚,我們就用部署服務的例子來解釋一下整個過程。假設要執行一個多實體的Nginx,在Kubernetes內部,整個流程是這樣的:

1.透過kubectl命令列,建立一個包含Nginx的Deployment物件,kubectl會呼叫 API Server 往ETCD裡面寫入一個 Deployment物件。


2.Deployment Controller 監聽到有新的 Deployment物件被寫入,就獲取到物件資訊,根據物件資訊來做任務排程,建立對應的 Replica Set 物件。


3.Replica Set Controller監聽到有新的物件被建立,也讀取到物件資訊來做任務排程,建立對應的Pod來。


4.Scheduler 監聽到有新的 Pod 被建立,讀取到Pod物件資訊,根據叢集狀態將Pod排程到某一個節點上,然後更新Pod(內部操作是將Pod和節點系結)。


5.Kubelet 監聽到當前的節點被指定了新的Pod,就根據物件資訊執行Pod。 


這就是Kubernetes內部如何實現整個 Deployment 被建立的過程。這個過程只是為了向大家解釋每一個元件的職責,以及他們之間是如何相互協作的,忽略掉了一些繁瑣的細節。


目前為止,我們有已經研究過幾個非常有代表性的排程系統:Hadoop MRv1、YARN、Mesos和Kubernetes。當時學習完這些排程系統的架構後,腦子裡面形成2個大大的疑問:

1.Kubernetes是二次排程的架構麼?和Mesos相比它的擴充套件性如何?

2.為什麼所有排程系統都是無法橫向擴充套件的?

後面我們就針對這兩個問題深入討論一下。 


Kubernetes 是否是二層排程?

在 Google 的一篇關於內部 Omega 排程系統的論文中,將排程系統分成三類:單體、二層排程和共享狀態三種,按照它的分類方法,通常Google的 Borg被分到單體這一類,Mesos被當做二層排程,而Google自己的Omega被當做第三類“共享狀態”。

論文的作者實際上之前也是Mesos的設計者之一,後來去了Google設計新的 Omega 系統,併發表了論文,論文的主要目的是提出一種全新的“Shard State”的樣式,來同時解決排程系統的效能和擴充套件性問題。但我覺得 Shared State 模型太過理想化,根據這個模型開發的Omega系統,似乎在Google內部並沒有被大規模使用,也沒有任何一個大規模使用的排程系統採用 Shared State 模型。



因為Kubernetes的大部分設計是延續 Borg的,而且Kubernetes的核心元件(Controller Manager和Scheduler)預設也都是系結部署在一起,狀態也都是儲存在ETCD裡面的,所以通常大家會把Kubernetes也當做“單體”排程系統,實際上我並不贊同。


我認為 Kubernetes 的排程模型也完全是二層排程的,和 Mesos 一樣,任務排程和資源的排程是完全分離的,Controller Manager承擔任務排程的職責,而Scheduler則承擔資源排程的職責。 



實際上Kubernetes和Mesos排程的最大區別在於資源排程請求的方式:

  • 主動 Push 方式。是 Mesos 採用的方式,就是 Mesos 的資源排程元件(Mesos Master)主動推送資源 Offer 給 Framework,Framework 不能主動請求資源,只能根據 Offer 的資訊來決定接受或者拒絕。


  • 被動 Pull 方式。是 Kubernetes 的方式,資源排程元件 Scheduler 被動的響應 Controller Manager的資源請求。


這兩種方式帶來的不同,我主要從一下 5 個方面來分析。另外註意,我所比較兩者的優劣,都是從理論上做的分析,工程實現上會有差異,一些指標我也並沒有實際測試過。 


1.資源利用率:Kubernetes 勝出

理論上,Kubernetes 應該能實現更加高效的叢集資源利用率,原因資源排程的職責完全是由Scheduler一個元件來完成的,它有充足的資訊能夠從全域性來調配資源,然後而Mesos 卻做不到,因為資源排程的職責被切分到Framework和Mesos Master兩個元件上,Framework 在挑選 Offer 的時候,完全沒有其他 Framework 工作負載的資訊,所以也不可能做出最優的決策。

舉個例子,比如我們希望把對耗費 CPU的工作負載和耗費記憶體的工作負載盡可能排程到同一臺主機上,在Mesos裡面不太容易做到,因為他們分屬不同的 Framework。


2.擴充套件性:Mesos勝出

從理論上講,Mesos 的擴充套件性要更好一點。原因是Mesos的資源排程方式更容易讓已經存在的任務排程遷移上來。舉個例子,假設已經有了一個任務排程系統,比如 Spark,現在要遷移到叢集排程平臺上,理論上它遷移到 Mesos 比 Kubernetes 上更加容易。

如果遷移到 Mesos ,沒有改變原來的工作流程和邏輯,原來的邏輯是:來了一個作業請求,排程系統把任務拆分成小的任務,然後從資源池裡面挑選一個節點來執行任務,並且記錄挑選的節點 IP 和埠號,用來跟蹤任務的狀態。遷移到 Mesos 之後,還是一樣的邏輯,唯一需要變化的是那個資源池,原來是自己管理的資源池,現在變成 Mesos 提供的Offer 串列。

如果遷移到 Kubernetes,則需要修改原來的基本邏輯來適配 Kubernetes,資源的排程完全需要呼叫外部的元件來完成,並且這個變成非同步的。


3.靈活的任務排程策略:Mesos 勝出

Mesos 對各種任務的排程策略也支援的更好。舉個例子,如果某一個作業,需要 All or Nothing 的策略,Mesos 是能夠實現的,但是 Kubernetes 完全無法支援。所以All or Nothing 的意思是,價格整個作業如果需要執行 10 個任務,這 10個任務需要能夠全部有資源開始執行,否則就一個都不執行。


4.效能:Mesos 勝出

Mesos 的效能應該更好,因為資源排程元件,也就是 Mesos Master 把一部分資源排程的工作甩給 Framework了,承擔的排程工作更加簡單,從資料來看也是這樣,在多年之前 Twitter 自己的 Mesos 叢集就能夠管理超過 8萬個節點,而 Kubernetes 1.3 只能支援 5千個節點。


5.排程延遲:Kubernetes 勝出

Kubernetes排程延遲會更好。因為Mesos的輪流給Framework提供Offer機制,導致會浪費很多時間在給不需要資源的 Framework 提供Offer。 

為什麼不支援橫向擴充套件?


可能大家已經註意到了,幾乎所有的叢集排程系統都無法橫向擴充套件(Scale Out),比如早期的 Hadoop MRv1 的管理節點是單節點,管理的叢集上限是 5000 臺機器,YARN 資源管理節點同時也只能有一個節點工作,其他都是備份節點,能夠管理的機器的上限1萬個節點,Mesos透過最佳化,一個叢集能夠管理 8 萬個節點,Kubernetes 目前的 1.13 版本,叢集管理節點的上限是 5000 個節點。


所有的叢集排程系統的架構都是無法橫向擴充套件的,如果需要管理更多的伺服器,唯一的辦法就是建立多個叢集。叢集排程系統的架構看起來都是這個樣子的: 


中間的 Scheduler(資源排程器)是最核心的元件,雖然通常是由多個(通常是3個)實體組成,但是都是單活的,也就是說只有一個節點工作,其他節點都處於 Standby 的狀態。為什麼會這樣呢?看起來不符合網際網路應用的架構設計原則,現在大部分網際網路的應用透過一些分散式的技術,能夠很容易的實現橫向擴充套件,比如電商應用,促銷時,透過往叢集裡面新增伺服器,就能夠提升服務的吞吐量。如果是按照網際網路應用的架構,看起來應該是這樣的:




Scheduler 應該是可以多活的,有任意多的實體一起對外提供服務,無論是資源的消費者,還是資源的提供者在訪問 Scheduler 的時候,都需要經過一個負載均衡的元件或者裝置,負責把請求分配給某一個 Scheduler 實體。為什麼這種架構在叢集排程系統裡面變得不可行麼?為了理解這件事情,我們先透過一個網際網路應用的架構的例子,來探討一下具備橫向擴充套件需要哪些前提條件。

橫向擴充套件架構的前提條件


假設我們要實現這樣一個電商系統:

1.這是一個二手書的交易平臺,有非常多的賣家在平臺上提供二手書,我們暫且把每一本二手書叫做庫存;

2.賣家的每一個二手書庫存,根據書的條碼,都可以找到圖書目錄中一本書,我們把這本書叫做商品;

3.賣家在錄入二手書庫存的時候,除了錄入是屬於哪一個商品,同時還需要錄入其他資訊,比如新舊程度、價錢、發貨地址等等。

4.買家瀏覽圖書目錄,選中一本書,然後下單,訂單系統根據買家的要求(價格偏好、送貨地址等),用演演算法從這本書背後的所有二手書庫存中,匹配一本符合要求的書完成匹配,我們把這個過程叫訂單匹配好了。


這樣一個系統,從模型上看這個電商系統和叢集排程系統沒啥區別,這個裡面有資源提供者(賣家),提供某種資源(二手書),組成一個資源池(所有二手書),也有資源消費者(買家),提交自己對資源的需求,然後資源排程器(訂單系統)根據演演算法自動匹配一個資源(一本二手書)。

但是很顯然,這個電商系統是可以設計成橫向擴充套件架構的,為什麼呢?這個電商系統和叢集排程系統的區別到底在什麼地方? 在回答這個問題之前,我們先來回答另外一個問題:這個電商系統橫向擴充套件的節點數是否有上限,上限是多少,這個上限是有什麼因素決定的?


系統理論上的併發數量決定了橫向擴充套件的節點數


假設系統架構設計的時候,不考慮任何物理限制(比如機器的資源大小,頻寬等),能夠併發處理 1000個請求,那麼很顯然,橫向擴充套件的節點數量上限就是1000,因為就算部署了 1001個節點,在任何時候都有一個節點是處於空閑狀態,部署更多的節點已經完全無法提高系統的效能。我們下麵需要想清楚的問題其實就變成了:系統理論上能夠併發處理請求的數量是多少,是有什麼因素決定的。


系統的併發數量是由“獨立資源池”的數量決定的


“獨立資源池”是我自己造出來的一個詞,因為實在想不到更加合適的。還是以上面的電商系統為例,這個訂單系統的理論上能夠處理的併發請求(訂購商品請求)數量是由什麼來決定的呢?先看下麵的圖: 


在訂單系統在匹配需求的時候,實際上應該是這樣執行的,在訂單請求來了之後,根據訂單請求中的購買的商品來排隊,購買同一個商品的請求被放在一個佇列裡面,然後訂單的排程系統開始從佇列裡面依次處理請求,每次做訂單匹配的時候,都需根據當前商品的所有庫存,從裡面挑選一個最佳匹配的庫存。

雖然在實現這個系統的時候,這個佇列不見得是一個訊息佇列,可能會是一個關係型資料庫的鎖,比如一個購買《喬布斯傳》的訂單,系統在處理的時候需要先從所有庫存裡面查詢出《喬布斯傳》的庫存,將庫存記錄鎖住,並做訂單匹配且更新庫存(將生成訂單的庫存商品設定為”不可用”狀態)之後,才會將資料庫鎖釋放,這時候所有後續購買《喬布斯傳》的訂單請求都在佇列中等待。

也有些系統在實現的時候採用“樂觀鎖”,就是每次訂單處理時,並不會在一開始就鎖住庫存資訊,而是在最後一步更新庫存的時候才會鎖住,如果發生兩個訂單匹配到了同一個庫存物品,那麼其中一個訂單處理就需要完全放棄然後重試。這兩種實現方式不太一樣,但是本質都是相同的。


所以從上面的討論可以看出來,之所以所有購買《喬布斯傳》的訂單需要排隊處理,是因為每一次做訂單匹配的時候,需要《喬布斯傳》這個商品的所有庫存資訊,並且最後會修改(佔用)一部分庫存資訊的狀態。在該訂單匹配的場景裡面,我們就把《喬布斯傳》的所有庫存資訊叫做一個“獨立資源池”,訂單匹配這個“排程系統”的最大併發數量就完全取決於獨立資源池的數量,也就是商品的數量。我們假設一下,如果這個二手書的商城只賣《喬布斯傳》一本書,那麼最後所有的請求都需要排隊,這個系統也幾乎是無法橫向擴充套件的。

叢集排程系統的“獨立資源池”數量是 1


我們再來看一下叢集排程系統,每一臺伺服器節點都是一個資源,每當資源消費者請求資源的時候,排程系統用來做排程演演算法的“獨立資源池”是多大呢?答案應該是整個叢集的資源組成的資源池,沒有辦法在切分了,因為:

1.排程系統的職責就是要在全域性內找到最優的資源匹配。


2.另外,哪怕不需要找到最優的資源匹配,資源排程器對每一次資源請求,也沒辦法判斷應該從哪一部分資源池中挑選資源。


正是因為這個原因,“獨立資源池”數量是 1,所以叢集排程系統無法做到橫向擴充套件。


原文地址:https://mp.weixin.qq.com/s/ps34qFlEzQNYbp6ughkrOA


.NET社群新聞,深度好文,歡迎訪問公眾號文章彙總 http://www.csharpkit.com