Zadig 面向開發者的自測聯調子環境技術方案詳解
Zadig 作為一款先進的開源云原生軟件交付平臺,為開發者提供云原生運行環境,支持開發者本地聯調、微服務并行構建和部署、集成測試等。
環境管理在日常的研發過程中基礎問題,開發自測、聯調均需在環境中進行。Zadig 針對環境管理,當前提供了如下能力:
- 創建/銷毀環境
- 復制環境
- 托管環境
- 自測模式(自 v1.11.0 版本推出)
通過創建/銷毀環境,開發者可以便利使用隔離的環境。
通過復制環境,開發者可以快速復制出存量環境,在隔離的相同環境中進行開發、聯調。
通過托管環境,開發者可以將已經存在的 namespace 及該 namespace 中的應用納入到 Zadig 中,通過 Zadig 的能力管理環境和應用。
通過自測模式,開發者之間可以共享同一套基準環境,低成本搭建子環境,在子環境中僅部署少量服務,并和基準服務交互實現開發、聯調。
下述將針對 Zadig 的自測模式進行詳細的技術解析,解密自測模式的技術實現:
基于擁有全量服務的基準環境,開發者可以低成本建立不同的子環境,在子環境中開發、變更目標服務,然后子環境與基準環境的服務交互來實現聯調。
服務形態
在分析技術實現前,先通過如下簡化的服務調用來回顧 Zadig 自測模式的使用體感:
在集群中搭建一套基準環境,該環境擁有完整的服務調用鏈。沒有灰度標的請求會在基準環境中進行調用,調用鏈路為 A -> B -> C
當開發者需要進行開發、聯調時,比如涉及到到 A / C 兩個組件的變更,可以基于基準環境新建 dev1 子環境,該子環境中僅部署變更后的 A / C 組件,即 A' / C'。聯調時請求加上灰度標,如在 http header 中設定 x-env=dev1 的灰度標,此時請求會按照 A' -> B -> C' 進行。
同理,當開發、聯調時僅涉及到 B / C 兩個組件的變更時,可以基于基準環境新建 dev2 子環境,該子環境僅部署變更后的 B / C 組件,即 B'' / C''。聯調時加上灰度標 x-env=dev2 ,這樣請求按照 A -> B'' -> C'' 進行。
通過 Zadig 自測模式,集群中每條業務線僅需一套完整的基準環境,變更的組件在隔離的子環境中開發、部署,然后通過灰度標控制請求在基準環境和子環境中流轉,從而滿足開發、聯調的需求,同時降低搭建新環境的復雜度和成本。
用戶操作流程圖如下:
上圖中,左側表示用戶操作階段,右側表示每個階段可做的操作的組合和次數。
在自測模式的生命周期中,用戶可針對存量環境開啟自測模式,將該環境轉變為基準環境。然后基于基準環境,為業務不同的需求或缺陷修復創建不同的子環境,在自環境中部署變更的服務,通過子環境和基準環境交互,來實現自測聯調。
模型
系統模型是產品設計和技術實現的基礎,可以整體理解復雜系統的核心。
Zadig 自測模式的模型如下:
系統層面:
- 一個 K8s 集群,可以有多套自測環境
- 每個項目,可以有多套自測環境
- 每個自測環境中,擁有一個基準環境和 n 個子環境 (n≥0)
- 每個自測環境中,所有服務全部在一個 K8s 集群,不能跨集群部署
- 每個自測環境中,子環境僅能和基準環境交互,請求鏈至多經過一個子環境
- 每個自測環境中,不支持子環境間請求交互
- 服務在一個環境中,至多有一個版本
自測環境:
- 任意時刻,基準環境擁有全量服務
- 同步請求的服務間通過 K8s Service 訪問
- 同步請求的請求鏈通過 tracing 信息串接
- 子環境中可操作 (新增/刪除/更新) 的服務是基準環境服務的子集
- 基準環境、子環境、直接訪問者需要在同一個 Mesh 中
通過模型可知,自測環境支持的是同步請求的調用鏈,且處于同步請求調用鏈上的服務之間通過 K8s Service 進行訪問。
故環境若要開啟自測模式,需要確保業務的同步請求調用鏈的服務均有相應的 K8s Service,確保服務間調用通過 K8s Service 進行。
實現原理
先定義關鍵問題:
- 鏈路上各個組件和服務能夠根據 請求流量特征 進行 動態路由
- 需要識別出 不同的灰度流量
- 需要對流量進行 灰度標識
- 需要對服務下的所有節點進行分組,能夠 區分服務版本
為此,實現的一般思路為:
- 所有流量都經過流量管理組件 --- 根據流量特征進行動態路由
- 可在流量管理組件層面配置路由規則 --- 可控灰度標
經過流量管理組件的流量保持特殊標記 --- 灰度標可全鏈路傳遞
流量管理組件能夠根據流量標記,選擇出對應的服務實例 --- 由業務無關組件執行 動態路由/服務發現
不同版本的服務可部署在不同的環境中 -- 區分服務版本
通過上述實現一般思路可知,關鍵的技術實現在于 流量管理組件,通常可以考慮使用 網關 或 代理。
而在用戶的技術棧中,流量與網關的關系通常會有如下兩種場景:
所有 (南北/東西) 流量均經過網關
部分東西流量不經過網關
考慮到流量經過服務時必須均要經過一個流量管理組件,由于不能要求用戶改動業務來處理流量路徑,如服務調用均經過網關,故網關無法滿足要求,因此選擇代理作為流量管理的組件。
目前業界基于代理的流量管理服務比較成熟的是 ServiceMesh,根據上述需求,對 ServiceMesh 實現方案有如下技術要求:
- 可以基于 http header 等動態路由流量
- 基于 K8s Service 做服務發現
- proxy 支持 header propagation
Istio 和 Linkerd 均是當前主流的 ServiceMesh 開源項目,可以考慮選擇一種作為 ServiceMesh 實現方案。經調研,截止 Zadig 自測模式實現時,Linkerd 在如下功能層面暫不滿足:
暫不支持 SMI TrafficSplit v1alpha3/v1alpha4,即不支持基于 http header 等做動態路由,issue 參見 link[1]
暫不支持 header propagation,issue 參見 link[2]
Istio 均滿足上述需求,同時社區活躍,故采用 Istio 作為 Zadig 自測模式的 ServiceMesh 實現方案。
技術實現
流量管理
VirtualService 定義路由規則,控制流量路由到服務上的行為,基于部署平臺的能力實現服務發現,如 K8s Service。
EnvoyFilter 管理每個服務流量經過的 Envoy 配置,可被用來動態調整流量特征。
在 Zadig 自測模式中的使用如下:
備注:
- DestinationRule 也是 Istio 中常用的資源,在 VirtualSerivce 路由生效后,控制流量實際導入的服務
- 但通過 Zadig 自測模式的模型可知,一個環境僅會部署服務至多一個版本,可通過環境區分服務版本,故無需借助 DestinationRule 來區分一個環境中同一類服務的不同版本
灰度標傳遞
灰度標的傳遞在自測模式中是關鍵問題,有三種解決方案:
- 應用自身對于入請求引發的出請求,主動傳遞對應的灰度標
- 集成了 tracing 能力的應用,會通過 tracing sdk 等通過 trace id 等串聯請求,Istio 可在服務接收到請求時記錄 trace id 和灰度標的關系,然后服務發出請求時根據 trace id 自動增加灰度標
- 對于 Java 語言,可通過 Java Agent 劫持程序運行時流量,根據入請求引發的出請求,自動添加灰度標
方案 1 需要應用少量修改。
方案 2 依賴應用已經集成 tracing 能力。
方案 3 會限制應用開發語言,同時需要在 Java Agent 中實現類似 Istio 中的服務發現和流量動態路由的能力。
Zadig 自測模式不限制用戶使用的開發語言,同時又希望盡量減少對用戶現有應用的侵入,故采用方案 2。
簡化后的方案如下:
Zadig 在系統層面提供一個 Cache 服務:
- 當請求進入服務時,Envoy 會請求 Cache 服務記錄 trace id 和 灰度標 的對應關系
- 當請求流出服務時,Envoy 會根據請求中的 trace id,查詢 Cache 服務獲取 灰度標,配置在出請求
用戶操作實現
對于用戶操作,下圖整理了平臺層面對應的操作實現:
用戶對自測環境的操作,在平臺層面會涉及對子環境、基準環境、Istio 環境的變更,具體的操作如上所示,不再贅述。
核心代碼:
K8s 項目:
- 后端:https://github.com/koderover/zadig/pull/1214
- 前端:https://github.com/koderover/zadig-portal/pull/660
Helm 項目:
- 后端:https://github.com/koderover/zadig/pull/1425
- 前端:https://github.com/koderover/zadig-portal/pull/791
展望
開發者常用的開發工具是 IDE,隨著 Zadig v1.12.0 的重磅發布,已推出面向 VScode IDE 的插件,結合自測模式的能力,使得開發者之間在一個工作界面中就可以輕松進行遠程開發和聯調,進一步提升開發者的生產力。
自測模式是降低環境管理復雜度和部署成本的重要能力,Zadig 將會持續迭代,在產品層面給用戶帶來更好的環境管理體驗。
官網:https://koderover.com/
github: https://github.com/koderover/zadig
參考鏈接:
[1] https://github.com/linkerd/linkerd2/issues/7155
[2] https://github.com/linkerd/linkerd2/issues/4219