本文已超過一年。較舊的文章可能包含過時的內容。請檢查頁面中的資訊自發布以來是否已變得不正確。

使用 Kubernetes 現代化 Skytap Cloud 微服務架構

Skytap 是一個全球公有雲,為我們的客戶提供在任何給定狀態下儲存和複製複雜虛擬化環境的能力。我們的客戶包括在混合雲中執行應用程式的企業組織、提供虛擬培訓實驗室的教育組織、需要易於維護的開發和測試實驗室的使用者,以及具有各種 DevOps 工作流程的組織。

不久前,我們的業務開始加速成長——我們的使用者群和我們的工程組織持續同步成長。這些都是令人興奮、有價值的挑戰!然而,平穩地擴展應用程式和組織是困難的,我們正在謹慎地處理這項任務。當我們最初開始研究如何改進我們的工具集以進行擴展時,非常清楚傳統的 OS 虛擬化不會是實現我們擴展目標的有效方法。我們發現 VM 的持久性鼓勵工程師建置和維護客製化的「寵物」VM;這與我們希望建置具有穩定、可預測狀態的可重複使用執行階段環境的願望不符。幸運的是,Docker 和 Kubernetes 社群的成長與我們的成長相符,而社群參與度的同時爆發(從我們的角度來看)幫助這些工具成熟。

在本文中,我們將探討 Skytap 如何使用 Kubernetes 作為關鍵組件,在處理不斷成長的 Skytap Cloud 生產工作負載的服務中。

隨著我們增加工程師,我們希望保持我們的敏捷性,並繼續在整個軟體開發生命週期中實現組件的所有權。這需要在我們流程的關鍵方面進行大量模組化和一致性。以前,我們透過 VM 和環境範本透過系統層級封裝來推動重複使用,但隨著我們擴展,容器已變得越來越重要,作為一種封裝機制,因為它們相對輕量級且對執行階段環境具有精確的控制。

除了這種封裝靈活性之外,容器還幫助我們建立更有效率的資源利用率,並且它們避免了團隊將資源混合到大型、高度專業化的 VM 中而產生的日益複雜的問題。例如,我們的營運團隊會安裝用於監控健康狀況和資源利用率的工具,開發團隊會部署服務,而安全團隊可能會安裝流量監控;將所有這些組合到單一 VM 中會大大增加測試負擔,並且經常會導致意外——糟糕,您引入了一個新的系統層級 Ruby gem!

使用 Docker 對服務中的個別組件進行容器化非常簡單。入門很容易,但正如任何使用超過少數組件建置分散式系統的人都知道的那樣,真正的困難在於部署、擴展、可用性、一致性以及叢集中每個單元之間的通訊。

讓我們容器化!

我們已經開始將我們許多深受喜愛的寵物 VM 換成,正如俗話所說,牲畜。

_____
/ Moo \
\---- /
       \   ^__^
        \  (oo)\_______
           (__)\       )\/\
               ||-----w |
               ||     ||

然而,分散式系統的挑戰並不會因為建立大量自由放養的容器而簡化。當我們開始使用容器時,我們意識到需要一個容器管理框架。我們評估了 Docker Swarm、Mesosphere 和 Kubernetes,但我們發現 Mesosphere 的使用模型與我們的需求不符——我們需要管理離散 VM 的能力;這與 Mesosphere 的「分散式作業系統」模型不符——而 Docker Swarm 仍然不夠成熟。因此,我們選擇了 Kubernetes。

啟動 Kubernetes 和建置新的分散式服務相對容易(就這種服務而言:您無法擊敗 CAP 理論)。但是,我們需要將容器管理與我們現有的平台和基礎架構整合。平台的一些組件更適合 VM,我們需要迭代地容器化服務的能力。

我們將這個整合問題分解為四個類別:

  1. 1. 服務控制和部署
  2. 2. 服務間通訊
  3. 3. 基礎架構整合
  4. 4. 工程支援和教育

服務控制和部署

我們使用 Capistrano 的自訂擴展(我們稱之為「Skycap」)來部署服務並在執行階段管理這些服務。對於我們來說,透過單一、完善的框架管理容器化和傳統服務非常重要。我們還需要將 Skycap 與 Kubernetes 等積極開發的工具中固有的不可避免的重大變更隔離開來。

為了處理這個問題,我們在我們的服務控制框架中使用包裝函式,將 kubectl 隔離在 Skycap 後面,並處理諸如忽略虛假日誌訊息等問題。

部署為我們增加了一層複雜性。Docker 鏡像是封裝軟體的絕佳方式,但從歷史上看,我們是從原始碼而不是套件部署的。我們的工程團隊期望對原始碼進行變更就足以發布他們的工作;開發人員不希望處理額外的封裝步驟。我們沒有為了容器化而重建我們整個部署和協調框架,而是為我們的容器化服務使用了持續整合管道。我們為專案的每次提交自動建置新的 Docker 映像,然後使用該提交的 Mercurial (Hg) 變更集編號對其進行標記。在 Skycap 端,從特定 Hg 修訂版本的部署將會提取以相同修訂版本號標記的 Docker 映像。

我們在多個環境中重複使用容器映像。這需要將環境特定的組態注入到每個容器實例中。直到最近,我們還使用類似的基於原始碼的原則來注入這些組態值:每個容器都會透過從執行階段從 repo 中 cURL 原始檔案來複製相關的組態檔案。但是,最好避免網路可用性和變異性帶來的挑戰,因此我們現在將組態載入到 Kubernetes 的 ConfigMap 功能中。這不僅簡化了我們的 Docker 映像,而且還使 Pod 啟動更快、更可預測(因為容器不必從 Hg 下載檔案)。

服務間通訊

我們的服務使用兩種主要方法進行通訊。第一種是訊息代理,這對於 Skytap 平台內的程序到程序通訊很典型。第二種是透過直接點對點 TCP 連線,這對於與外部世界通訊的服務(例如 Web 服務)很典型。我們將在下一節中討論 TCP 方法,作為基礎架構整合的組件。

以服務可以理解的方式管理 Pod 之間的直接連線很複雜。此外,我們的容器化服務需要與傳統的基於 VM 的服務進行通訊。為了減輕這種複雜性,我們主要使用我們現有的訊息佇列系統。這幫助我們避免編寫基於 TCP 的服務發現和負載平衡系統來處理 Pod 和非 Kubernetes 服務之間的流量。

這減少了我們的組態負載——服務只需要知道如何與訊息佇列交談,而不是與它們需要互動的每個其他服務交談。對於管理 Pod 的執行狀態等事情,我們具有額外的靈活性;訊息在佇列中緩衝,而節點正在重新啟動,並且我們避免了每次從叢集中新增或移除 Pod 時重新組態 TCP 端點的開銷。此外,MQ 模型允許我們使用更精確的「拉取」式方法來管理負載平衡,其中接收者決定何時準備好處理新訊息,而不是使用諸如「最少連線」之類的啟發式方法,這些方法僅計算開啟的 Socket 數量來估計負載。

與遷移使用複雜的基於 TCP 的直接或負載平衡連線的服務相比,將啟用 MQ 的服務遷移到 Kubernetes 相對簡單。此外,訊息代理提供的隔離意味著從傳統服務切換到基於容器的服務對於任何其他啟用 MQ 的服務基本上是透明的。

基礎架構整合

作為基礎架構提供商,我們在組態 Kubernetes 以與我們的平台一起使用時面臨一些獨特的挑戰。AWSGCP 提供了開箱即用的解決方案,簡化了 Kubernetes 佈建,但對底層基礎架構做出了假設,而這些假設與我們的現實不符。有些組織擁有專門建置的資料中心。這個選項將要求我們放棄現有的負載平衡基礎架構、基於 Puppet 的佈建系統以及我們圍繞這些工具建立的專業知識。我們對放棄這些工具或我們既有的經驗不感興趣,因此我們需要一種可以與我們的世界整合而不是重建它的方式來管理 Kubernetes。

因此,我們使用 Puppet 來佈建和組態 VM,這些 VM 反過來執行 Skytap 平台。我們編寫了自訂部署腳本以在這些 VM 上安裝 Kubernetes,並與我們的營運團隊協調以進行 Kube-master 和 Kube-node 主機的容量規劃。

在上一節中,我們提到了基於點對點 TCP 的通訊。對於面向客戶的服務,Pod 需要一種與 Skytap 的第 3 層網路基礎架構介接的方式。Skytap 的範例包括我們的 Web 應用程式和透過 HTTPS 的 API、透過 Web Socket 的遠端桌面、FTP、TCP/UDP 埠轉發服務、完整公有 IP 等。我們需要仔細管理此外部流量的網路進入和離開,並且從歷史上看,我們一直使用 F5 負載平衡器。內部服務的 MQ 基礎架構不足以處理此工作負載,因為各種客戶端(如 Web 瀏覽器)使用的協定非常具體,而 TCP 是最低公分母。

為了讓我們的負載平衡器與我們的 Kubernetes Pod 通訊,我們在每個節點上執行 kube-proxy。負載平衡器路由到節點,而 kube-proxy 處理最終的交遞到適當的 Pod。

我們絕不能忘記 Kubernetes 需要在 Pod 之間路由流量(對於基於 TCP 和基於 MQ 的訊息傳遞)。我們使用 Calico 外掛程式進行 Kubernetes 網路連線,並使用專門的服務在 Kubernetes 啟動或回收 Pod 時重新組態 F5。Calico 使用 BGP 處理路由宣告,這簡化了與 F5 的整合。

當 Pod 進入或離開叢集時,F5 也需要重新組態其 負載平衡池。F5 設備維護一個負載平衡後端池;進入容器化服務的流量會透過此池定向到託管服務 Pod 的其中一個節點。這對於靜態網路組態很簡單——但是由於我們使用 Kubernetes 來管理 Pod 複製和可用性,因此我們的網路狀況變得動態。為了處理變更,我們有一個「負載平衡器」Pod,用於監控 Kubernetes svc 物件的變更;如果移除或新增 Pod,「負載平衡器」Pod 將透過 svc 物件偵測到此變更,然後透過設備的 Web API 更新 F5 組態。這樣,Kubernetes 透明地處理複製和故障轉移/恢復,而動態負載平衡器組態讓此過程對發起請求的服務或使用者保持不可見。同樣,Calico 虛擬網路和 F5 負載平衡器的組合意味著 TCP 連線對於在傳統 VM 基礎架構上執行的服務或已遷移到容器的服務應表現一致。

kubernetes_f5_messaging.png

透過網路的動態重新組態,Kubernetes 的複製機制使水平擴展和(大多數)故障轉移/恢復非常簡單。我們尚未達到反應式擴展里程碑,但我們已經透過 Kubernetes 和 Calico 基礎架構奠定了基礎,使實作它的一種途徑變得簡單明瞭

  • 設定服務複製的上限和下限
  • 建置負載分析和擴展服務(簡單,對吧?)
  • 如果負載模式符合擴展服務中組態的觸發器(例如,請求速率或容量超過某些界限),則發出:kubectl scale --replicas=COUNT rc NAME

這將允許我們在平台層級而不是從應用程式本身對自動擴展進行細緻的控制——但我們也將評估 Kubernetes 中的 水平 Pod 自動擴展;這可能適合我們的需求,而無需自訂服務。

請關注 我們的 GitHub 帳戶Skytap 部落格;隨著我們解決這些問題的方案日趨成熟,我們希望與開放原始碼社群分享我們所建置的內容。

工程支援

像我們的容器化專案這樣的轉型需要參與維護和貢獻平台的工程師改變他們的工作流程,並學習建立和疑難排解服務的新方法。

由於各種學習風格需要多方面的方法,我們透過三種方式處理這個問題:文件、直接接觸工程師(即,午餐研討會或指導團隊)以及提供易於存取的臨時支援。

我們繼續策劃文件集合,這些文件提供有關將傳統服務轉移到 Kubernetes、建立新服務以及運作容器化服務的指南。文件不適合所有人,有時儘管我們盡了最大努力,但文件仍然遺失或不完整,因此我們還運行了一個內部 #kube-help Slack 頻道,任何人都可以隨時加入以尋求協助或安排更深入的面對面討論。

我們還有一個更強大的支援工具:我們自動建構和測試類似生產環境的環境,其中包括此 Kubernetes 基礎架構,這讓工程師可以自由地親身實驗和使用 Kubernetes。我們在 這篇文章中更詳細地探討了自動化環境交付的細節。

最後的想法

我們在使用 Kubernetes 和整體容器化方面取得了巨大的成功,但我們當然發現與現有的全堆疊環境整合提出了許多挑戰。雖然從企業生命週期的角度來看並非完全隨插即用,但 Kubernetes 的靈活性和可組態性仍然是建置我們的模組化服務生態系統的非常強大的工具。

我們熱愛應用程式現代化挑戰。Skytap 平台非常適合這些類型的遷移工作——當然,我們在 Skytap 中運行 Skytap,這在我們的 Kubernetes 整合專案中為我們提供了極大的幫助。如果您正在規劃自己的現代化工作,請與我們聯繫,我們很樂意提供協助。