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

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

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

不久前,我們開始以加速的步伐發展業務——我們的使用者群和工程組織同時持續增長。這些是令人興奮且富有挑戰性的挑戰!然而,平穩地擴充套件應用程式和組織是困難的,我們正在謹慎地處理這項任務。當我們第一次開始尋找改進以擴充套件我們的工具集時,很明顯傳統的作業系統虛擬化不會是實現我們擴充套件目標的有效方法。我們發現虛擬機器永續性的特性鼓勵工程師構建和維護定製的“寵物”虛擬機器;這與我們構建具有穩定、可預測狀態的可重用執行時環境的願望不符。幸運的是,Docker 和 Kubernetes 社群的增長與我們的增長相吻合,社群參與度的同步爆發(從我們的角度來看)幫助這些工具成熟。

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

隨著我們工程師數量的增加,我們希望保持敏捷性,並繼續在軟體開發生命週期的關鍵方面實現元件的所有權。這需要我們流程中關鍵方面的大量模組化和一致性。以前,我們透過虛擬機器和環境模板使用系統級打包來推動重用,但隨著我們規模的擴大,容器作為一種打包機制變得越來越重要,因為它們對執行時環境的控制相對輕量且精確。

除了這種打包靈活性之外,容器還幫助我們建立更高效的資源利用,並避免了團隊將資源混合到大型、高度專業化的虛擬機器中這種自然傾向所帶來的日益增長的複雜性。例如,我們的運營團隊會安裝用於監控健康和資源利用率的工具,開發團隊會部署服務,安全團隊可能會安裝流量監控;將所有這些結合到一個虛擬機器中會大大增加測試負擔,並且經常導致意外情況——哎呀,您引入了一個新的系統級 Ruby gem!

使用 Docker 將服務中的單個元件容器化非常簡單。入門很容易,但任何構建過具有少量以上元件的分散式系統的人都知道,真正的困難在於部署、擴充套件、可用性、一致性以及叢集中每個單元之間的通訊。

讓我們容器化吧!

我們已經開始將許多備受喜愛的寵物虛擬機器換成了,正如俗話所說,牛。

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

然而,建立一大群散養容器並不能簡化分散式系統的挑戰。當我們開始使用容器時,我們認識到需要一個容器管理框架。我們評估了 Docker Swarm、Mesosphere 和 Kubernetes,但我們發現 Mesosphere 的使用模型不符合我們的需求——我們需要管理離散虛擬機器的能力;這與 Mesosphere 的“分散式作業系統”模型不符——而且 Docker Swarm 還不成熟。所以,我們選擇了 Kubernetes。

啟動 Kubernetes 並構建一個新的分散式服務相對容易(就這種服務而言:您無法擊敗CAP 定理)。但是,我們需要將容器管理與我們現有的平臺和基礎設施整合。平臺的一些元件透過虛擬機器更好地服務,我們需要能夠迭代地容器化服務。

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

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

服務控制和部署

我們使用 Capistrano 的自定義擴充套件(我們稱之為“Skycap”)來部署服務並在執行時管理這些服務。對我們來說,透過一個單一的、完善的框架來管理容器化服務和傳統服務非常重要。我們還需要將 Skycap 與 Kubernetes 這種活躍開發的工具中固有的不可避免的破壞性變更隔離開來。

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

部署為我們增加了一層複雜性。Docker 映象是一種很好的打包軟體的方式,但從歷史上看,我們是從原始碼而不是軟體包部署的。我們的工程團隊期望修改原始碼足以釋出他們的工作;開發人員不期望處理額外的打包步驟。我們沒有為容器化而重建我們整個部署和編排框架,而是為我們的容器化服務使用持續整合管道。我們為專案的每次提交自動構建新的 Docker 映象,然後使用該提交的 Mercurial (Hg) 變更集號對其進行標記。在 Skycap 端,來自特定 Hg 修訂版的部署將拉取標記有相同修訂版號的 Docker 映象。

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

服務間通訊

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

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

這減少了我們的配置負擔——服務只需要知道如何與訊息佇列通訊,而不是與它們需要互動的每個其他服務通訊。我們在管理 Pod 執行狀態等方面擁有額外的靈活性;當節點重啟時,訊息會在佇列中緩衝,並且我們避免了每次從叢集中新增或移除 Pod 時重新配置 TCP 端點的開銷。此外,MQ 模型允許我們使用更準確的“拉取”方法來管理負載均衡,其中接收方決定何時準備好處理新訊息,而不是使用像“最少連線”這樣簡單地計算開放套接字數量來估計負載的啟發式方法。

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

基礎設施整合

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

因此,我們使用 Puppet 來供應和配置虛擬機器,這些虛擬機器反過來執行 Skytap 平臺。我們編寫了自定義部署指令碼來在這些虛擬機器上安裝 Kubernetes,並與我們的運營團隊協調進行 Kube-master 和 Kube-node 主機的容量規劃。

在上一節中,我們提到了基於點對點 TCP 的通訊。對於面向客戶的服務,Pod 需要一種方式來與 Skytap 的第 3 層網路基礎設施介面。Skytap 的示例包括我們的 Web 應用程式和透過 HTTPS 的 API、透過 Web Sockets 的遠端桌面、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 連線對於在傳統虛擬機器基礎設施上執行的服務或已遷移到容器的服務來說,行為應該保持一致。

kubernetes_f5_messaging.png

透過網路的動態重新配置,Kubernetes 的複製機制使得橫向擴充套件和(大多數)故障轉移/恢復變得非常簡單。我們尚未達到反應式擴充套件的里程碑,但我們已經為 Kubernetes 和 Calico 基礎設施奠定了基礎,使其成為實現它的直接途徑。

  • 配置服務複製的上限和下限
  • 構建負載分析和擴充套件服務(簡單,對嗎?)
  • 如果負載模式與擴充套件服務中配置的觸發器匹配(例如,請求速率或流量超過特定限制),則執行:kubectl scale --replicas=COUNT rc NAME

這將允許我們在平臺層面進行細粒度的自動擴縮控制,而不是從應用程式本身進行控制——但我們也將評估 Kubernetes 中的 Horizontal Pod Autoscaling;這可能無需自定義服務就能滿足我們的需求。

請關注我們的 GitHub 帳戶Skytap 部落格;隨著我們解決這些問題的方案成熟,我們希望能與開源社群分享我們的成果。

工程支援

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

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

我們持續整理一系列文件,提供關於將傳統服務過渡到 Kubernetes、建立新服務以及操作容器化服務的指導。文件並非適用於所有人,有時儘管我們盡了最大的努力,它仍然缺失或不完整,因此我們還運營一個內部的 #kube-help Slack 頻道,任何人都可以在其中尋求幫助或安排更深入的面對面討論。

我們還有一個強大的支援工具:我們自動構建並測試包含 Kubernetes 基礎設施的類生產環境,這讓工程師有很大的自由度來親身體驗和使用 Kubernetes。我們在這篇文章中詳細探討了自動化環境交付的細節

最終思考

我們透過 Kubernetes 和容器化總體上取得了巨大成功,但我們確實發現與現有全棧環境整合帶來了許多挑戰。儘管從企業生命週期的角度來看並非即插即用,但 Kubernetes 的靈活性和可配置性仍然是構建我們模組化服務生態系統的強大工具。

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