一、背景
現有的 ServiceMesh 框架有很多,如 Istio、linkerd等。對於使用者而言,在測試環境下,需要達到的效果是快、開箱即用。但在生產環境下,可能又有熔斷、延時注入等需求。那麼單一的 ServiceMesh 框架無法同時滿足使用者不同的需求。
在之前的 Rainbond 版本中,Rainbond 支援了多種不同的應用治理模式,作為應用級的外掛,實現了Istio 治理模式的切換。
本文將對Rainbond 實現Istio治理模式進行原理解析。
二、基本原理
動態准入控制
首先我們需要了解一個知識,Istio 是如何實現自動注入的。實際上,Istio、linkerd 等不同的 ServiceMesh 框架都使用到了 Kubernetes 中的一個非常重要的功能,叫作 Dynamic Admission Control,也叫作:Initializer。
這個功能會在 API 物件建立之後會被立刻呼叫到。在 API 物件被正式處理前,完成一些初始化的準備工作。所以在部署了 Istio 控制平面以後,當你提交了 API 物件的 Yaml 檔案,會被 Istio 的准入控制器捕獲,完成一些 PATCH 操作,比如加上對應的 Sidecar 容器欄位。最終將這個 PATCH 過後的 API 物件交給 Kubernetes 處理。接下來就詳細介紹下 ServiceMesh 框架的注入機制。
如何自動注入
使用者需要先在叢集中部署 Istio 的控制平面。它會包含一個用來為 Pod 自動注入 Envoy 容器的 Initializer。
首先, Istio 會將 Envoy 容器本身的定義,以 ConfigMap 的方式儲存在 Kubernetes 當中。當 Initializer 的控制器,通過 Admission-Webhooks 監聽到符合規則【此處指對應的 Annoations】的 API 物件被建立後,讀取對應的 ConfigMap 獲取到 Envoy 容器的配置。並將相關的欄位,自動新增到使用者提交的 Pod 的 API 物件裡。詳見下圖和說明。
上圖為提交yaml檔案到Kubernetes叢集后,叢集所做的處理,大概分為以下幾步:
-
Yaml 檔案提交到 APIServer,APIServer 會過濾這個請求,並完成一些前置性的工作,比如授權、超時處理、審計等。
-
APIServer 會找到該 Pod 對應的型別定義,如果存在,則會將這個 Pod 轉換為物件。
-
接下來進行 Admission 操作,Admission 操作是在建立之後會被立刻呼叫到的一組程式碼,它可以完成一些初始化操作,比如在物件建立前加上某些 Labels,但是由於它本身是被編譯到 APIServer 中的,所以使用者修改後,需要重新編譯並重啟 APIServer。幸運的是:Kubernetes 提供了一種“熱插拔”式的 Admission 機制,即 Initializer。
-
目前 istio、linkerd 等專案均實現了 Initializer 機制,也就是說,當提交的 Yaml 檔案包含其指定的Annoations 欄位時,那麼它們部署的准入控制器則會捕獲到對應的 API 物件,在這個階段為 Pod 進行初始化操作,即新增上 Sidecar 容器的相關配置。
-
接下來會進行 Validation,驗證 API 物件的欄位是否合法。
-
驗證完畢後,會將對應的資訊儲存到etcd中,一次API物件的建立就此完成。
三、擴充套件應用治理模式
在瞭解了現有的 ServiceMesh 框架的注入機制後,我們就可以基於此開發 Rainbond 的應用級外掛,用於擴充套件應用的治理能力。我們知道由於現有的 ServiceMesh 框架大多采用了標準的 Initializer 實現。所以我們只需要完成以下兩步即可。
-
部署對應 ServiceMesh 框架的 Initializer 控制器,通常情況下意味著部署它們的控制平面,此處基於 Rainbond 已有的對接 helm 商店的功能,可以方便的進行部署。
-
實現基於應用的資料平面的注入。
Istio治理模式的開發
接下來以 Istio 治理模式的開發為例,詳細介紹如何自行擴充套件應用的治理能力。
前端展示支援 Istio 應用治理模式:
Rainbond 主要分為兩層,即業務層和資料中心層,具體可參考 Rainbond 技術架構 。
rainbond-ui 為業務層的前端專案,首先需要支援 Istio 治理模式,由於 Rainbond 是以應用為中心的應用管理平臺,所以 Istio 治理模式也是針對應用來說的。
如下圖所示:在應用頁面,可以切換治理模式。我們需要在這裡增加 Istio 治理模式。
治理模式有效性校驗
Initializer 的機制決定了,需要有一個准入控制器,去處理符合條件的 API 物件。通常情況下准入控制器包含在對應 ServiceMesh 框架的控制平面中。
所以我們在切換治理模式時,需要去校驗叢集中是否已經部署過對應 ServiceMesh 框架的控制平面,這一步應該在切換時進行校驗。如果未部署對應的控制平面,則不具有對應的治理能力。也就不能切換。
根據 Rainbond 技術架構 ,我們可以知道,rainbond-console 屬於業務層的後端。它需要與資料中心端進行通訊,才能獲取叢集的狀態。因此在 rainbond-console 和 rainbond 這兩個專案中,都需要對治理模式的有效性進行校驗。
rainbond-console 對治理模式有效性的校驗
參考如下程式碼,類 GovernanceModeEnum
定義了支援的治理模式。首先我們需要在治理模式這裡增加 ISTIO_SERVICE_MESH
,用於在業務層判斷治理模式是否有效。當此處校驗通過後,我們需要請求資料中心端的介面,檢測此種治理模式是否已安裝了對應的控制平面。
/console/enum/app.py
class GovernanceModeEnum(AutoNumber):
BUILD_IN_SERVICE_MESH = ()
KUBERNETES_NATIVE_SERVICE = ()
ISTIO_SERVICE_MESH = ()
@classmethod
def choices(cls):
return [(key.value, key.name) for key in cls]
@classmethod
def names(cls):
return [key.name for key in cls]
rainbond 對治理模式有效性的校驗
在接收到來自業務端的校驗請求時,我們需要檢測在該叢集是否已部署了對應的 ServiceMesh 框架的控制平面。參考如下程式碼,由於部署 Istio 控制平面後,在每個名稱空間下都可以檢視到 istio-ca-root-cert
這個 ConfigMap,所以我們這裡使用該 ConfigMap 作為判斷 Istio 控制平面部署的依據。
當確認 Istio 控制平面已安裝後,我們返回給業務端結果。最終完成切換。
/api/handler/app_governance_mode/adaptor/istio.go
func (i *istioServiceMeshMode) IsInstalledControlPlane() bool {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmName := os.Getenv("ISTIO_CM")
if cmName == "" {
cmName = "istio-ca-root-cert"
}
_, err := i.kubeClient.CoreV1().ConfigMaps("default").Get(ctx, cmName, metav1.GetOptions{})
if err != nil {
return false
}
return true
}
實現基於應用的資料平面的注入
僅僅完成治理模式的切換還不夠,我們需要讓 Istio 的控制平面為指定的應用注入 Sidecar,即資料平面。Rainbond 自身通過 Worker 元件將 Rainbond-Application Model 進行例項化轉化為 Kubernetes 資源模型。控制應用的生命週期。
因此,我們需要在 Worker 元件轉化資源時,自動為使用者完成對應用的注入。參考 Istio 注入策略。我們可以發現 Istio 依賴於 Label "sidecar.istio.io/inject": "true"
完成注入。而在 Rainbond的程式碼中,我們可以看到如下程式碼。這是將 Rainbond 的應用模型轉化為 Deployment 的部分程式碼。在這裡,我們為 Deployment 新增了對應的 injectLabels。
有了這些初始化操作。當 API 物件被建立出來時,便會被 Istio 的准入控制器處理,完成資料平面的注入。
/worker/appm/conversion/service.go
func initBaseDeployment(as *v1.AppService, service *dbmodel.TenantServices) {
as.ServiceType = v1.TypeDeployment
... ...
injectLabels := getInjectLabels(as)
deployment.Labels = as.GetCommonLabels(
deployment.Labels,
map[string]string{
"name": service.ServiceAlias,
"version": service.DeployVersion,
},
injectLabels)
... ...
as.SetDeployment(deployment)
}
func getInjectLabels(as *v1.AppService) map[string]string {
mode, err := governance_mode.NewAppGoveranceModeHandler(as.GovernanceMode, nil)
if err != nil {
logrus.Warningf("getInjectLabels failed: %v", err)
return nil
}
injectLabels := mode.GetInjectLabels()
return injectLabels
}
對於不同的應用治理模式,我們可以參考 應用治理模式擴充套件 的程式碼。實現如下介面,便可以完成應用下治理模式的切換和注入。
其中IsInstalledControlPlane
這個介面的實現在前面已經體現。它主要用於判斷控制平面是否已完成安裝,可以正常使用。而 GetInjectLabels
則主要用於 Worker 元件轉化應用模型為 Kubernetes 資源時,新增上指定的 Labels,以便被部署的准入控制器處理。
/api/handler/app_governance_mode/adaptor/app_governance_mode.go
type AppGoveranceModeHandler interface {
IsInstalledControlPlane() bool
GetInjectLabels() map[string]string
}
四、總結
本文我們主要介紹了應用治理模式的注入機制和開發,使用者可以通過查閱需要注入的 ServiceMesh 外掛官方文件,通過以上兩步完成應用下治理模式的切換。使應用獲得不同的治理能力。