QCon演講實錄(下):多雲管理關鍵能力實現與解析-AppManager

語言: CN / TW / HK

在上篇中,我們已經基本瞭解了多雲管理。現在,我們將深入探討多雲管理關鍵能力實現:AppManager

什麼是AppManager?

上面我們講了理論、我們自己使用的交付流程和整體架構,下面我們進入關鍵能力實現與解析的環節,看看我們是如何實現上述這些能力的。

image.png

回到 AppManager 這個服務本身,它就是一個基於 OAM 的幾種分離的角色,能夠實現應用管理及交付的一個服務。

它因大數據側業務訴求而生長,注重擴展能力、網絡隔離環境交付、資源管理和版本管理。

擴展能力

首先來看最重要的擴展能力。不管在一開始的平台設計的時候有多麼完善,都很難滿足後續持續演進的業務需求。所以擴展性是一個 PaaS 平台的重中之重。

image.png

依託於 OAM 的設計,我們將所有的 Component / Addon / Trait / Policy / Workflow 等均做成了可插拔動態加載的 Groovy 腳本,並在此之上提供了插件管理及市場分發的能力。

圖中的所有綠色的地方都是可根據業務需要自行擴展的,所有能力都可以在整個流程中動態引入、插拔和替換。

最上面基礎設施研發的同學負責編寫支持的組件類型、工作流類型、資源類型的 Groovy 腳本,並註冊到 AppManager 內部。

中間的 SRE 同學負責編寫 Trait、Policy 的 Groovy 腳本註冊到 AppManager 內部,並負責配置環境及部署約束條件等信息。

下面的用户只需要把自己的應用代碼寫好,然後按照預先定義的 CI、CD、Watch 流程運行即可。

三類角色各司其職,共同高效的完成多雲環境下整體應用的管理與交付過程。

圖中所有綠色的地方擴展的 Groovy 腳本都是去實現預先定義的 Interface 抽象接口的,任何實現了這些 Interface 的 Groovy 腳本,都可以像搭積木一樣被組裝和替換到各個應用的生命週期中發揮自己的能力。包括構建、部署、銷燬、狀態監測。

在服務內部,我們為每個腳本定義了自己的 kind (類型) / name (名稱) / revision (版本) 三元組,全局唯一。對於相同的 kind + name 組合,僅加載指定的單個版本的代碼版本。通過這樣的設計,實現不同用途,不同名稱的腳本動態加載,且可以自由切換腳本代碼版本。

擴展能力 – 插件包

雖然單個 Groovy 擴展方便且靈活,但不易分發,而開源版本 SREWorks 中社區用户的需求又千差萬別,所以在此之上,我們增加了插件包的機制,插件包通過對一系列 Groovy 腳本的組合,實現對應功能的封裝。用户只需要一鍵下載及安裝即可實現對應的能力加載。

image.png

對於插件包本身,我們也定義了一套規範,用於社區用户進行二次開發編寫自己需要的插件並上傳到插件市場。

image.png

構建打包

如果我們回顧過去這些年來的交付介質演進,可以看到抽象層次是越來越高的。從最開始的 RPM 等系統層面的軟件包,到 Docker 出現後的鏡像,再到後面以鏡像為基礎,進行各種包裝而產生的交付物,如 Helm Chart/Kustomize 等。

那麼我們也不例外,在物理機時代,我們作為 SRE 去維護各種物理機的軟件一致性以及在此之上交付各類服務,甚至還研發了配套的流程平台、作業平台、配置平台等等。在 Docker 普及之後,這項工作就簡單了很多,只需要處理鏡像即可,但鏡像本身的管理及交付仍然是頭疼的問題。

好在現在是雲原生的時代,我們有各類 CRD+Operator,有完善及可靠的 Kubernetes 原語,更有事實上的打包交付的標準 Helm/Kustomize 來幫助我們更好的完成交付物的封裝。接下來我們就看一下,我們在整個應用交付的流程中,如何處理好構建打包、製品管理及外部 CI 對接的。

image.png

通過圖上我們可以看到,首先用户自定義的構建配置被提交,之後會被 AppManager 解析為應用包任務以及一個個的組件包任務,每個組件包任務會根據自己的組件類型選擇對應的構建邏輯進行構建,這裏可以有不同的數據源,也可以通過不同的方式產出鏡像,不管是 Docker Daemon 還是 Kaniko 的方式都支持,也會推送到指定的鏡像倉庫中。最終完成後生成一個個組件包,以及拼裝為一個應用包,賦予版本及標籤後,最後上傳到 OSS 或 MinIO 上進行製品存儲。一個常見的用户場景是,通過標籤聲明當前製品的用途,在實際使用的時候通過標籤進行過濾,並進而選擇自己需要的版本號以確認唯一的製品。

當存在外部 CI 系統需要對接的時候,只需要將外部 CI 系統產出的鏡像及其他 commit 信息通過 Trigger API 觸發即可,後面的流程和上述人工主動觸發一致。

應用部署

當獲取到製品之後,就來到了整個系統中最核心也是最複雜的部分:應用部署。此處暫時先忽略多環境下的製品下發及相關細節,後面會單獨講到。現在先假設我們已經將製品傳輸到了指定環境中,並且鏡像也已經在對應環境中可用,只是單純的執行一次部署流程,會有哪些事情發生。

首先看一下整個 Application 的 Yaml,它完整定義了一個應用部署的模型,描述了一個應用應如何可靠而又靈活的交付到目標環境中。如前面所説,Component/Addon/Trait 為面向終態設計,Policy/Workflow 為面向過程設計。

image.png

之後來看一下整體的部署層級關係,從大到小分別是 Workflow 部署、Application 部署、Component 部署(含 Trait 部署)。當然並不侷限於這三層,Workflow 是可以按業務需求進行各種嵌套的,後面會單獨講到。接下來我們用相反的順序來介紹實現細節,自底向上,逐步拼出來我們最後的實現方案。

應用部署 - Trait

Trait 我們會歸類為三種功能:

  • 根據配置修改綁定的 Component Workload Yaml。
  • 新增 / 修改資源 / 做你想做的事,並在完成後產出數據給自身對應的組件或後續其他組件使用。
  • 持續監聽目標集羣中當前組件的事件(不只是部署過程中),並實時根據事件做出反應(Groovy 實現),常用於根據組件的當前狀態做出一些外部反應,比如 Pod 漂移 IP 變動後需要重新做一些信息註冊或維護類的工作。

image.png

所有的 Trait 也都是 Groovy 腳本實現,並封裝為插件包註冊到系統中。每個 Trait Groovy 的 class 説明如上圖,其中會包括兩個 interface:

  • 一個是 execute,所有類型的 Trait 在部署過程中均會執行該方法,併產出數據供下游使用,當然產出數據是可選的
  • 另外一個是 reconcile,只有第三類需要持續監聽目標集羣事件的 Trait 才會使用,本質上是目標集羣運行着我們開發的一個 Trait Operator,在 execute 執行的時候下發一個監聽 CR 到該集羣,Trait Operator 檢測到該 CR 後會啟動一個新的 Controller 來監聽指定組件事件,並在事件到來後直接調用 AppManager 的 Trait Reconcile API,該 API 會直接同步調用對應 Trait Groovy 腳本中的 reconcile 方法,完成業務自定義的事件觸發動作。這裏其實等價於我們把常見的 K8S Operator 的 Reconcile 鏈路延長到了 AppManager 自身,並通過 Groovy 來實現業務邏輯,簡化了常規 Operator 的開發流程,並且中心化也更好維護一些,尤其是面對茫茫多的目標集羣的時候。

應用部署 - 組件

對於組件而言,我們定義了幾個固定類型,分別是構建、部署和狀態感知。其中構建前面介紹過了,狀態感知是可選的,會在最後進行介紹。這裏主要介紹下部署邏輯。

組件部署的時候,一樣會執行對應的 Groovy 腳本,並且系統會在進入腳本執行前自動將選定的製品解壓到本地目錄供腳本使用,以執行業務自定義的部署邏輯。目前我們已經根據各個業務情況編寫了 6 類內部使用的組件,這些因為和阿里雲業務相關無法開源。在開源場景下,我們默認提供了微服務、Job、Helm 幾種類型,並開放了擴展機制,供有需要的用户自行編寫新的組件類型來控制自己的組件如何部署。這裏的組件類型是抽象的,也可以完全和 K8S 無關,比如是自己公司內部系統的某些特定配置導入導出。

image.png

由上面的圖可以看到,在部署過程中,Groovy 腳本中的 launch 方法會首先被調用,並完成對應的部署流程,這裏可通過 fabric8 進行 K8S 遠程調用 apply 下去對應的 Yaml,當完成後立即返回即可。之後,AppManager 服務會不停輪詢當前 Groovy 腳本中的 get 方法,期望查詢到成功的返回值,否則就會一直等下去,直到超時或失敗。

這裏在檢查業務組件終態的時候其實還可以和組件自身的狀態感知的能力產生互動,比如一直沒有終態,但是沒有終態的原因是什麼呢?是可以通過後面説的狀態感知來分析失敗原因並返回給用户的。後面我們再細講。

應用部署 – 應用 & 組件 & Trait 間依賴關係

説完了組件和 Trait 的部署,我們上升到應用層面,來看下在應用的視角下,自身的組件、Trait 在整個部署流程中的數據關係。

因為實際的業務場景中,每個組件和 Trait 都有可能產生數據輸出,並且會被別的組件和 Trait 引用到,在這種依賴關係下,產出數據的組件不跑完,引用數據的組件是不能跑的。但如果兩個組件間沒有任何依賴關係,那麼他們是需要並行跑來提速的。

image.png

所以按照 OAM spec 的描述,我們在 Application Yaml 中為每個組件和 Trait 都提供了 dataInputs 和 dataOutputs 能力,如圖上的 Yaml 所示。

在 AppManager 系統收到上面的 Yaml 後,在整個應用部署的過程中就需要對針對所有的組件和 Trait 之間的關係建圖連邊了。

image.png

在構圖的時候,Component 和 Trait 是一視同仁的,這種對等關係會降低複雜度,同時保證數據流依賴關係在兩種類型上保持一致。

對於每個 Component,會單獨再構造一個鏡像節點,用於確保前置組件的後置 Trait 節點的運行在當前組件之前。這樣整體構圖之後,只需要按照這個 DAG 無腦執行即可滿足約束順序。

應用部署 -Workflow

在早期的業務支撐中,其實是沒有 Workflow & Policy 這一層的,但是上面講到的應用部署只是針對單目標的,如果是批量目標環境的部署,就需要在更高的緯度發起多個應用部署單子來解決。

OAM 針對這一情況也在 0.3.1 的草案中提出瞭解決方案,也就是 Workflow & Policy。這裏簡要説明一下:

  • 一個 Workflow 包含多個步驟,執行順序可以順次,也可以併發,如果併發的話就是按照上面説的 dataInputs/dataOutputs 的 DAG 構造方式。
  • 每個 Workflow Step 也都是 Groovy 腳本來實現,所以每個步驟執行什麼,怎樣執行全部交給平台方來定義。
  • 每個 Workflow Step 的 Groovy 腳本中,可以調用 Policy 對全局 Application 進行自定義修改,並且將修改後的 Application 提交應用部署單,也就是前面説的邏輯,這樣就可以實現不同目標環境有不同的覆寫配置。
  • Workflow 在執行的過程中可以有更多的控制及人工介入,如暫停、恢復、設置上下文等等,在灰度發佈場景、涉及回滾動作的時候尤其有用。
  • Workflow Step 可以繼續產出一個 Workflow,實現複雜場景實現套娃效果。

資源管理(Addon)

秉承着所有可擴展的東西都通過 Groovy 腳本來插拔,資源 Addon 也不例外,其實也可以把它看做是 Component 組件。

對於 Addon,會有和 Component 不同的語義。資源需要申請和釋放,也就是 apply 和 release,所以這兩個方法是 Addon 層面的 interface。同時 Addon 也會存在自己的規格 Schema 定義,前端會在資源申請的時候識別這個定義,讓用户在頁面選擇需要申請的資源規格(比如 8c16g 的數據庫)。

在我們的實踐過程中,直接寫了一個對接 Terraform 的 Groovy 腳本就完成了大部分的內部業務訴求。對於所有的 tfstate 文件也會透出以 Addon Schema 的形式存儲到數據庫中供後續其他組件使用或共享。

image.png

注意資源的特性是應用下根據 Namespace 隔離的,但 Namespace 下的同名資源會進行共享。一個典型的場景是生產的 Namespace 下申請的數據庫會共享到預發和生產兩個環境的服務中。這裏在下一頁的多環境支持中進行介紹。

如果你對於其他第三方的各類異構資源有需求,比如有的資源需要通過特定的 API 申請的,完全可以再擴展一個插件註冊進去即可實現。

多環境支持

image.png

我們在 AppManager 中定義了四種環境層級,如上圖。從上到下分別是:

  • Unit(單元):單元間網絡隔離。每個單元需要一個 AppManager 實例進行管控。一個單元可包括多個 Cluster。
  • Cluster(集羣):一個獨立 K8S 集羣,集羣間網絡可達。集羣直接註冊 kubeconfig 到當前單元下的 AppManager 即可使用。
  • Namespace(命名空間): 對應 K8S 的 Namespace 概念,用於一個 Cluster 下的資源及應用的隔離,一個 Namespace 對應了一個信息孤島。
  • Stage(階段/環境): 一個 Namespace 可包含多個 Stage,每個 Stage 共享了當前 Namespace 下的所有資源。

應用狀態感知

一個應用被交付到目標環境之後,運行的狀態怎麼樣?這塊其實有很多種外部開源方案或監控手段可以來搞的,不展開。這裏只介紹下 AppManager 內置的一個簡單的狀態感知方案。

image.png

由上圖可以看到,一個應用被部署到了一個目標環境中,就變成了一個應用實例 Application Instance。一個組件被部署後也一樣會變成組件實例 Component Instance。應用實例的狀態是由組件實例的狀態匯聚而來的,那麼組件實例的狀態需要怎麼來呢?

首先來看 Watch 的方案,時效性高,但支撐的集羣數量只有幾百個,小規模下使用。本質上是在組件註冊的時候通過 Informer 機制 List/Watch 集羣事件,然後將 Event Handler 的實現交由組件開發者自己自行判斷,並根據內容返回業務自定義的狀態結果。

image.png

接下來是 Pull 方案,可以支撐最高 10 萬量級的應用及集羣,但時效性在應用狀態正常時會逐步降低到 5min。只有在發生異常時會快速提升檢測頻率,直到再次恢復正常。當然具體的狀態判定的腳本也是交給組件開發者來寫的,可以根據業務需要自行擴展,靈活性非常高。

image.png

以上為本次分享全部內容,謝謝大家的聆聽。