談談 Kubernetes 架構

運維之美2019-07-11 22:19:56


關於調度系統架構的文章,這是系列的第二篇,之前的第一篇請移步這這裏閲讀:
集羣調度系統的演進。在文章裏面,通過介紹 Hadoop MRv1、YARN和Mesos調度系統,瞭解了調度系統的架構和演進過程,那麼現在最新最流行的 Kubernetes 的架構是什麼樣子的呢?這篇文章就給大家介紹一下 Kubernetes 整體架構,並且會深入探討其中 2 個比較深入的問題。


01
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. 為什麼所有調度系統都是無法橫向擴展的?


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


02
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。 


03
為什麼不支持橫向擴展?


看到可能注意到了,幾乎所有的集羣調度系統都無法橫向擴展(Scale Out),比如早期的 Hadoop MRv1 的管理節點是單節點,管理的集羣上線是 5000 台機器,YARN 資源管理節點同時也只能有一個節點工作,其他都是備份節點,能夠管理的機器的上限1萬個節點,Mesos通過優化,一個集羣能夠管理 8 萬個節點,Kubernetes 目前的 1.13 版本,集羣管理節點的上限是 5000 個節點。


所有的集羣調度系統的架構都是無法橫向擴展的,如果需要管理更多的服務器,唯一的辦法就是創建多個集羣。集羣調度系統的架構看起來都是這個樣子的: 



中間的 Scheduler(資源調度器)是最核心的組件,雖然通常是由多個(通常是3個)實例組成,但是都是單活的,也就是説只有一個節點工作,其他節點都處於 Standby 的狀態。為什麼會這樣呢?看起來不符合互聯網應用的架構設計原則,現在大部分互聯網的應用通過一些分佈式的技術,能夠很容易的實現橫向擴展,比如電商的應用,在促銷的時候,通過往集羣裏面添加服務器,就能夠提升服務的吞吐量。如果是按照互聯網應用的架構,看起來應該是這樣的:




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

04
橫向擴展架構的前提條件


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

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

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

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

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


這樣一個系統,從模型上看這個電商系統和集羣調度系統沒啥區別,這個裏面有資源提供者(賣家),提供某種資源(二手書),組成一個資源池(所有二手書),也有資源消費者(買家),提交自己對資源的需求,然後資源調度器(訂單系統)根據算法自動匹配一個資源(一本二手書),但是很顯然,這個電商系統是可以設計成橫向擴展架構的,這是為什麼呢,這個電商系統和集羣調度系統的區別到底在什麼地方 我想在回答這個問題之前,我們先來回答另外一個問題:這個電商系統橫向擴展的節點數是否有上限,上限是多少,這個上限是有什麼因素決定的?


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


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


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


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


在訂單系統在匹配需求的時候,實際上應該是這樣運行的,在訂單請求來了之後,根據訂單請求中的購買的商品來排隊,購買同一個商品的請求被放在一個隊列裏面,然後訂單的調度系統開始從隊列裏面依次處理請求,每次做訂單匹配的時候,都需根據當前商品的所有庫存,從裏面挑選一個最佳匹配的庫存。雖然在實現這個系統的時候,這個隊列不見得是一個消息隊列,可能會是一個關係型數據庫的鎖,比如一個購買《喬布斯傳》的一個訂單,系統在處理的時候需要先要從所有庫存裏面查詢出《喬布斯傳》的庫存,將庫存記錄鎖住,並且做訂單匹配並且更新庫存(將生成訂單的庫存商品設置為”不可用”狀態)之後,才會將數據庫鎖釋放,這個時候實際上所有後續購買《喬布斯傳》的訂單請求都在隊列中等待,也有些系統在實現的時候採用“樂觀鎖”,就是在每一次訂單處理的時候,並不會在第一開始就鎖住庫存信息,而是在最後一步更新庫存的時候才會鎖住,如果發生兩個訂單匹配到了同一個庫存物品,那麼其中一個訂單處理就需要完全放棄然後重試。這兩種實現方式不太一樣,但是本質都是相同的。


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


05
集羣調度系統的“獨立資源池”數量是 1


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


  1. 調度系統的職責就是要在全局內找到最優的資源匹配。

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


正是因為這個原因,“獨立資源池”數量是 1,所以集羣調度系統無法做到橫向擴展。 

來源:DockOne
原文:http://t.cn/EKMykpT
題圖:
來自谷歌圖片搜索 
版權:
本文版權歸原作者所有
投稿:歡迎投稿,投稿郵箱: [email protected]


今日思想

人生最終的價值在於覺醒和思考的能力,而不只在於生存。


—— 亞里士多德




推薦閲讀

  • 大話高併發架構

  • 圖解 Kubernetes 架構

  • 談談 TCP 的 TIME_WAIT

  • 淺談幾種常用負載均衡架構

  • 在 Kubernetes 中部署微服務架構 Istio


https://hk.wxwenku.com/d/201124607