一、背景
現有的 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 插件官方文檔,通過以上兩步完成應用下治理模式的切換。使應用獲得不同的治理能力。
Reference Link
- Dynamic Admission Control
- Rainbond-UI 實作 Istio Commit
- Rainbond-Console 實作 Istio Commit
- Rainbond 實作 Istio Commit:
- Istio.go
- service.go
- app_governance_mode.go
- Rainbond 技術架構
- Istio 注入政策