基於騰訊雲微服務引擎(TSE) ,輕鬆實現雲上全鏈路灰度發佈
作者:劉遠
1. 概述
軟件開發過程中,應用發佈非常頻繁,通常情況下,開發或運維人員會將系統裏所有服務同時上線,使得所有用户都使用上新版本。這樣的操作時常會導致發佈失敗,或因發佈前修改代碼,線上出現 Bug。
假設一個在線商城,每天都有大量的用户訪問,如果直接在所有用户中部署新版本應用,一旦出現問題,所有用户都可能受到影響。相比之下,通過引入灰度發佈策略,先將新版本的應用部署到少量的用户中,檢查是否存在問題,如果沒有,再逐步擴展到更多的用户中,由此解決全量發佈的各種弊端。
灰度發佈是一種軟件發佈策略,它允許你在生產環境中漸進式部署應用,新版本只對部分用户可見,在問題出現時儘量減少影響。在微服務體系架構中,服務間的依賴關係錯綜複雜,有時單個服務發版依賴多個服務同時運行聯動,對這個新版本服務的上下游進行分組驗證,這是微服務架構中特有的全鏈路灰度發佈場景。
使用騰訊雲微服務引擎 TSE 提供的網關和服務治理能力,可以在不修改任何業務代碼的情況下,可視化配置灰度規則,實現雲上輕鬆易用的全鏈路灰度發佈。
圖1-1 全鏈路灰度場景架構
接下來演示雲原生 API 網關+北極星網格構建的全鏈路灰度發佈能力。下圖模擬的雲書城應用,由4個後端的微服務組成,採用 Spring Boot + Dubbo 實現,調用鏈包含:收藏功能、購買功能,用户管理功能和訂單功能。用户通過前端頁面訪問書城,進行內容瀏覽和書籍下單。
圖1-2 雲書城前端頁面
2. 環境
2.1 雲組件版本
本次實踐採用如下組件:
● TSE-雲原生網關:v2.5.1
● TSE-北極星網格: v1.12.0.1
● TKE:v1.20
● APM-SkyWalking: v8.5.0
我們將應用部署在騰訊雲 TKE 集羣中,在實際生產中,全鏈路灰度對於應用的部署模式沒有限制性要求,無論是 CVM 虛擬機,還是自建容器環境,都能應用此方案。
2.2 灰度服務準備
項目模擬書城收藏服務改版,對 addUserFavoriteBook 接口進行修改:當前基線版本點擊【收藏】按鈕後,僅顯示成功收藏字樣,代碼示例如下:
public Response<String> addUserFavoriteBook(Long userId, Long isbn) {
Response<String> resp = new Response<String>(ResponseCode.SUCCESS);
try {
FavoritesInfo entity = new FavoritesInfo(userId, isbn);
if (favoritesPersistCmpt.favoriteExist(entity)) {
resp.setMsg("已收藏(version:1.0.0)");
return resp;
}
favoritesPersistCmpt.addUserFavorite(entity);
resp.setMsg("收藏成功");
BookInfoDto dto = storeClient.getBookInfoByIsbn(isbn);
cacheCmpt.cashUserFavoriteBook(userId, dto);
} catch (Exception e) {
logger.error("failed to add FavoritesInfo", e);
resp.setFailue("服務異常,請稍後重試!");
}
return resp;
}
灰度版本修改後,頁面點擊【收藏】,會詳細顯示用户 ID 及書籍 ID,代碼示例如下:
public Response<String> addUserFavoriteBook(Long userId, Long isbn) {
Response<String> resp = new Response<String>(ResponseCode.SUCCESS);
try {
FavoritesInfo entity = new FavoritesInfo(userId, isbn);
if (favoritesPersistCmpt.favoriteExist(entity)) {
resp.setMsg("已收藏(version:2.0.0)");
return resp;
}
favoritesPersistCmpt.addUserFavorite(entity);
resp.setMsg("用户 userId = " + userId + " 成功收藏 book isbn = " + isbn);
BookInfoDto dto = storeClient.getBookInfoByIsbn(isbn);
cacheCmpt.cashUserFavoriteBook(userId, dto);
} catch (Exception e) {
logger.error("failed to add FavoritesInfo", e);
resp.setFailue("服務異常,請稍後重試!");
}
return resp;
}
為了方便查看全鏈路服務當前版本,各服務將應用版本號回傳給前端,在前端頁面上顯示。
圖2-1 基線版本收藏服務
圖2-2 灰度版本收藏服務
2.3 北極星網格接入
雲書城架構中,服務發現能力目前是通過 Nacos 實現,在全鏈路灰度發佈中,服務間需要使用到治理能力,我們採用北極星網格對註冊發現功能進行替換。項目選擇 Polaris-Dubbo 框架方式接入,通過更新北極星代碼依賴,無需修改代碼即可完成。對比原項目,有以下幾點變化:
● 修改 POM.XML 文件,增加北極星-Dubbo 服務註冊、服務熔斷及服務路由依賴包。
//服務註冊插件
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>dubbo-registry-polaris</artifactId>
<version>${polaris.version}</version>
</dependency>
//服務熔斷插件
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>dubbo-circuitbreaker-polaris</artifactId>
<version>${polaris.version}</version>
</dependency>
//服務路由插件
<dependency>
<groupId>com.tencent.polaris</groupId>
<artifactId>dubbo-router-polaris</artifactId>
<version>${polaris.version}</version>
</dependency>
● 在配置文件中,修改 Dubbo 應用註冊中心接入地址。
dubbo.registry.address=polaris://x.x.x.x:8091?username=polaris&password=*****
修改後的項目,代碼保持 Dubbo 標準方式進行註冊及調用,無需變更。
//註冊服務(服務端)
@DubboService(version = "${provicer.service.version}")
public class ProviderServiceImpl implements ProviderService {}
//服務調用(消費端)
@DubboReference(version = "1.0.0")
private ProviderService providerService;
2.4 容器服務部署
完成上述修改後,對微服務應用重新編譯打包,推送至鏡像倉庫。在 TKE 集羣中,我們以 Deployment 方式下發應用。其中,收藏服務將基線版本和灰度版本都部署在集羣中,其他服務僅部署一個版本,使用服務治理能力進行流量路由。
● 基線版本收藏服務 YAML:
apiVersion: apps/v1
kind: Deployment
metadata:
name: favorites-service
namespace: qcbm
labels:
app: favorites-service
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: favorites-service
version: v1
template:
metadata:
labels:
app: favorites-service
version: v1
spec:
containers:
- name: favorites-service
image: ccr.ccs.tencentyun.com/qcbm/favorites-service-polaris
env:
- name: MYSQL_HOST
valueFrom:
configMapKeyRef:
key: MYSQL_HOST
name: qcbm-env
optional: false
- name: REDIS_HOST
valueFrom:
configMapKeyRef:
key: REDIS_HOST
name: qcbm-env
optional: false
- name: MYSQL_ACCOUNT
valueFrom:
secretKeyRef:
key: MYSQL_ACCOUNT
name: qcbm-keys
optional: false
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
key: MYSQL_PASSWORD
name: qcbm-keys
optional: false
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
key: REDIS_PASSWORD
name: qcbm-keys
optional: false
ports:
- containerPort: 20880
protocol: TCP
● 灰度版本收藏服務YAML:
apiVersion: apps/v1
kind: Deployment
metadata:
name: favorites-service-new
namespace: qcbm
labels:
app: favorites-service-new
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: favorites-service-new
version: v1
template:
metadata:
labels:
app: favorites-service-new
version: v1
spec:
containers:
- name: favorites-service-new
image: ccr.ccs.tencentyun.com/qcbm/favorites-service-new-polaris
env:
- name: MYSQL_HOST
valueFrom:
configMapKeyRef:
key: MYSQL_HOST
name: qcbm-env
optional: false
- name: REDIS_HOST
valueFrom:
configMapKeyRef:
key: REDIS_HOST
name: qcbm-env
optional: false
- name: MYSQL_ACCOUNT
valueFrom:
secretKeyRef:
key: MYSQL_ACCOUNT
name: qcbm-keys
optional: false
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
key: MYSQL_PASSWORD
name: qcbm-keys
optional: false
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
key: REDIS_PASSWORD
name: qcbm-keys
optional: false
ports:
- containerPort: 20880
protocol: TCP
● 前端頁面服務以 Service 方式部署,通過 Ingress 模式對接雲原生網關,提供集羣外部訪問能力
apiVersion: v1
kind: Service
metadata:
name: qcbm-front
namespace: qcbm
spec:
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
selector:
app: qcbm-front
version: v1
type: NodePort
2.5 雲原生網關接入
雲原生網關支持將流量直通到 Service 所在的 Pod,無需通過 NodePort 中轉。在控制枱裏綁定 TKE 集羣,輸入 Service 名,網關通過 Endpoint 裏收集 Pod IP,在網關裏自動生成 Kong Services 和 Upstream。一旦 TKE Service 發生變化,Ingress Controller 會動態更新 Upstream 裏的 Target 信息。
後續操作基於 Kong 裏自動生成的 Services,配置基線及灰度網關路由規則。
圖2-3 雲原生網關綁定TKE集羣服務
圖2-4 雲原生網關自動生成 Services
圖2-5 雲原生網關自動生成 Upstreams
2.6 鏈路追蹤接入
單體系統時代追蹤的範圍只侷限於棧追蹤,而在微服務環境中,追蹤不只限於調用棧,一個外部請求需要內部若干服務的聯動,完整的一次請求會跨越多個服務。鏈路追蹤的主要目的是排查故障,如當前問題點處於調用鏈的哪一部分,各服務間輸入輸出是否符合預期,通過鏈路追蹤,可以查看到服務間的網絡傳輸信息,以及各服務內部的調用堆棧信息。
採用 APM 的 SkyWalking 協議方式進行上報,首先修改 SkyWalking 文件夾裏的 agent.config 文件,配置接入點、Token 、自定義空間和服務名稱。
collector.backend_service=x.x.x.x:11800
agent.authentication=xxxxxx
agent.service_name=favorites-service-new
agent.namespace=QCBM
在 Dockerfile 中,修改應用程序的啟動命令行,以 JavaAgent 方式指定 SkyWalking Agent 的路徑 :
java -javaagent:/app/skywalking/skywalking-agent.jar -jar favorites-service-new.jar
部署後,可以在控制枱裏驗證應用拓撲正確性。
圖2-6 應用拓撲圖
3. 解決方案
通過四個階段的操作,實現收藏服務的全鏈路灰度發佈,分別是實例打標、網關路由、微服務路和標籤透傳。
圖3-1 全鏈路灰度發佈方案
3.1 實例打標及標籤透傳
實例打標,指的是通過實例標籤標識不同的應用,將基線版本與灰度版本區分開。一般有兩種方式進行實例打標:一是框架自動同步,將應用名,環境變量等做為實例標籤;二是用 K8S 部署時的 CRD Label 作為實例標籤。本實踐中使用 Dubbo 框架裏的 applicaiton 字段來區分基線版本和灰度版本應用。
圖3-2 網關路由規則
網關層對灰度流量進行了染色,在微服務調用過程中,需要將染色標籤在每一跳進行傳遞,使得各微服務都可以識別到灰度流量,並進行正確路由處理。
圖3-3 標籤透傳示意圖
外部染色標籤在入口處,以 HTTP Header 方式存在,在 Dubbo-Gateway 服務處,編碼將 HTTP Header 轉化為 Dubbo attachment,使得染色標籤在微服務內部中繼續透傳,最終根據 attachment 裏的取值做服務間調用依據。
private FavoriteService add(FavoriteService favoriteService, String result) {
logger.info("header:{}", result);
RpcContext.getContext().setAttachment("gray", result == null ? "false" : result);
return favoriteService;
}
3.2 網關路由
網關作為系統流量入口,負責將外部流量按照一定的用户特徵,切分流入灰度版本和基線版本。並對灰度流量進行染色打標,供服務治理中心動態路由規則匹配使用。在實際生產中,一般有三種分流的方法:
● 通過匹配用户請求的 Header 參數,進行流量區分。
● 通過匹配用户請求的 Host 特徵,進行流量區分。
● 通過流量百分比進行區分,使用雲原生網關能力,將其中一部分流量進行染色處理。
本次實踐針對前兩種切分方式進行介紹。
圖3-4 網關路由示意圖
3.3 微服務路由
北極星網格在全鏈路灰度中,充當服務治理中心的角色,解決架構中註冊發現、故障容錯、流量控制和安全問題。通過北極星網格控制枱中的配置,把基線和灰度請求,路由到不同的實例分組上,並將灰度請求固定在灰度版本服務間進行傳遞和處理。
圖3-5 動態路由示意圖
我們創建了2條服務間動態路由規則,基線和灰度請求按照不同匹配規則,路由至對應實例分組。實現中,北極星基於請求消息內容來對請求匹配,並根據優先級進行流量調度。
圖3-6 治理中心路由規則
4. 場景
4.1 通過Header特徵全鏈路灰度
1)場景説明
如果客户端訪問希望統一域名,比如實踐中的 gray.qcbm.yunnative.com,我們可以通過傳入不同的 Header,把請求分別路由到基線和灰度環境。當生產環境中存在多個客户分組,或多條灰度路由規則,也可以通過雲原生網關進行自定義 Header 染色,使用不同染色標籤,進行服務間路由搭配。
圖4-1 通過Header特徵全鏈路灰度
2)配置方法
在雲原生網關上創建兩條路由規則:
● qcbm-front-router-web,HOST為gray.qcbm.yunnative.com,Header為app:web,路由到Dubbo-Gateway服務
● qcbm-front-router-mobile,HOST為gray.qcbm.yunnative.com,Header為app:mobile,路由到Dubbo-Gateway服務,開啟染色(gray:true)
圖4-2 雲原生網關路由規則
服務治理中心可以直接使用現成的 app:web 或 app:mobile 標籤路由,也可以對路由請求新增染色,使用染色標籤路由,優化複雜環境管理。這裏我們開啟雲原生網關的 Request-Transformer 插件,對 qcbm-front-router-mobile 路由報文進行修改,添加 gray:true 頭,使用該染色標識進行路由管理。
圖4-3 路由染色插件
圖4-4 添加染色標識
qcbm-front-router-mobile 路由規則的請求到達 Dubbo-Gateway 後,一旦需要訪問收藏服務(FavoriteService),gray:true 染色標籤會命中北極星網格灰度服務路由,選擇調用 remote.application為favorites-service-new 的服務實例。此實例分組為我們部署的 favorites-service-new 灰度版本 deployment。
圖4-5 灰度服務路由
qcbm-front-router-web 路由規則的請求會命中無染色標籤的基線服務路由,調用 remote.application 為 favorites-service 的服務實例。此實例分組為我們部署的 favorites-service 基線版本 deployment。
圖4-6 基線服務路由
3)結果驗證
我們借用 chrome 瀏覽器插件 ModHeader,對訪問請求按需添加 Header。
● 當添加 app:mobile 後,瀏覽器訪問 gray.qcbm.yunnative.com,我們的訪問鏈路如下:
[雲原生網關] --> [Dubbo-Gateway] --> [Favorite-Service-New](灰度)
頁面顯示如下:
圖4-7 灰度請求頁面
同時,也可以通過鏈路監控觀察到,gateway-service(基線服務)正確的請求到 favorite-service-new(灰度服務),同時 favorite-service-new 正確請求到 store-service(基線服務):
圖4-8 灰度請求鏈路詳情
● 當添加 app:web 後,瀏覽器訪問 gray.qcbm.yunnative.com,此時我們的訪問鏈路如下:
[雲原生網關] --> [Dubbo-Gateway] --> [Favorite-Service](基線)
頁面顯示如下:
圖4-9 基線請求頁面
通過鏈路監控,可以觀察到,gateway-service(基線服務)正確的請求到 favorite-service(基線服務),同時 favorite-service 正確請求到 store-service(基線服務):
圖4-10 基線請求鏈路詳情
在北極星網格中,我們可以針對鏈路的每一跳配置路由規則,每個主調服務都可以定義屬於自己的匹配規則。
4.2 通過域名特徵全鏈路灰度
1)場景説明
同樣的,也可以採用域名對請求進行區分,預期 web 端用户採用 gray.web.yunnative.com 訪問基線環境;mobile 端用户採用 gray.mobile.yunnative.com 訪問灰度環境。這種分流方式,適用於網關根據用户登錄信息,動態分流的場景,不同的用户在登錄時,登錄模塊根據驗證信息,返回302報文,給予不同的重定向域名,用户此時使用不同的域名去訪問,雲原生網關通過 HOST 來做流量區分,動態染色 HTTP 請求。
圖4-11 通過域名特徵全鏈路灰度
2)配置方法
在雲原生網關上創建兩條路由規則:
● qcbm-front-router-web,HOST 為 gray.web.yunnative.com,路由到 Dubbo-Gateway 服務。
● qcbm-front-router-mobile,HOST 為 gray.mobile.yunnative.com,路由到 Dubbo-Gateway 服務,開啟染色(gray:true)。
和場景1類似,qcbm-front-router-mobile 路由規則的請求到達 Dubbo-Gateway 後,一旦訪問收藏服務(FavoriteService),gray:true 染色標籤會命中北極星網格灰度路由,調用 remote.application 為 favorites-service-new 的實例分組;而 qcbm-front-router-web 路由規則的請求會命中無染色標籤的網格基線路由,調用 remote.application 為 favorites-service 的實例分組,訪問基線環境。
3)結果驗證
● 瀏覽器訪問gray.mobile.yunnative.com時,染色標籤會被打上,此時訪問鏈路如下:
[雲原生網關] --> [Dubbo-Gateway] --> [Favorite-Service-New](灰度)
頁面顯示如下:
圖4-12 灰度請求頁面
同時,也可以通過鏈路監控觀察到,gateway-service(基線服務)正確的請求到 favorite-service-new(灰度服務),同時 favorite-service-new 正確請求到 store-service(基線服務):
圖4-13 灰度請求鏈路詳情
● 當訪問gray.web.yunnative.com時,無染色標籤,此時我們的鏈路如下:
[雲原生網關] --> [Dubbo-Gateway] --> [Favorite-Service](基線)
頁面顯示如下:
圖4-14 灰度請求頁面
通過鏈路監控,可以觀察到,gateway-service(基線服務)正確的請求到 favorite-service(基線服務),同時 favorite-service 正確請求到 store-service(基線服務):
圖4-15 基線請求鏈路詳情
4.3 灰度服務故障轉移
1)場景説明
在灰度發佈過程中,可以通過監測系統性能和用户反饋來評估新功能的質量。如果新功能在測試期間表現良好,可以繼續將其推向更多用户,替換原版本應用。如果出現任何問題,可以對灰度服務進行訪問熔斷處理,及時修復問題,然後繼續灰度測試。
圖4-16 灰度服務故障轉移
2)配置方法
在北極星網格上配置熔斷規則,配合多實例分組路由規則,實現灰度服務故障 Failover。在全鏈路灰度場景基礎上,在北極星網格控制枱加上一條熔斷規則。
● 當 delUserFavoriteBook 接口錯誤率大於10%,閾值大於10個,熔斷 Favorite-Service-New 灰度服務實例分組,同一 Deployment 裏的所有 Pod 進入半開狀態。
● 半開時間60秒,期間一旦請求成功則認為服務恢復,關閉熔斷。
圖4-17 灰度服務熔斷規則
接下來,在網格灰度路由中,添加低優先級實例分組,該分組為基線實例。一旦灰度實例分組被熔斷,請求會去訪問基線實例分組,直到灰度服務修復,熔斷關閉。
圖4-18 灰度服務路由規則
3)結果驗證
部署一個新的“故障“收藏服務,Dubbo 程序延用 application=favorites-service-new 標籤(為區分應用,這裏故障灰度服務命名為 Favorites-Service-New-Bad),保證原路由規則可用。該“故障”程序修改了收藏服務的 delUserFavoriteBook 接口代碼,當訪問時直接拋出異常,模擬服務故障。代碼如下所示:
public Response<String> delUserFavoriteBook(Long userId, Long isbn) {
String hostAddress;
try {
hostAddress = InetAddress.getLocalHost().getHostAddress();
} catch (Exception e) {
hostAddress = "ip獲取失敗";
}
throw new RuntimeException("刪除收藏-故障 ip:" + hostAddress);
}
瀏覽器訪問 gray.mobile.yunnative.com 時,此時訪問鏈路如下:
[雲原生網關] --> [Dubbo-Gateway] --> [Favorite-Service-New-Bad](故障灰度)
頁面顯示如下:
圖4-19 灰度請求頁面
進入收藏頁面,點擊【刪除】,程序報錯,顯示調用異常。
圖4-20 灰度服務刪除報錯
通過鏈路追蹤,也可以查看到服務異常。
圖4-21 應用調用拓撲
圖4-22 灰度服務刪除鏈路錯誤
當故障錯誤大於10次,favorite-service-new 灰度實例分組被熔斷,灰度路由進行低優先級目標選擇,流量回源至基線實例分組favorite-service,此時測試刪除功能正常,因為此時我們的訪問鏈路重新變為:
[雲原生網關] --> [Dubbo-Gateway] --> [Favorite-Service](基線正常)
頁面顯示如下,服務調用已回源:
圖4-22 灰度請求頁面(已回源)
5. 總結
在灰度發佈實施前,需要按照如下三方面,對整體流程進行計劃:
● 目標:在開始灰度發佈之前,需要了解發布的目標是什麼,比如説是測試新版本的性能,還是功能兼容性等,以便在灰度時進行對應的測試和觀測。
● 灰度策略:有很多種灰度策略可供選擇,例如按用户特徵來灰度、按流量來灰度、按地域來灰度等。通過系統用户的特點,選擇最合適的灰度策略。
● 灰度範圍:在灰度發佈過程中,應當能隨時控制哪些用户可以訪問新版本,當出現問題時,能將請求快速回滾到舊版本上。
灰度發佈過程中,確認流量是否已經按計劃切換到灰度實例分組,通過監控和日誌,檢查各服務是否正常運行,是否符合預期。
確定本次發佈成功後,可以依次對老版本分組的實例進行滾動升級,多次升級完成灰度發佈,一旦出現錯誤執行回退,有序控制發佈節奏。最後,根據實際應用情況,刪除或保留網關和治理中心的動態路由規則。
騰訊雲TSE提供了完整的全鏈路灰度發佈解決方案,適用各種發佈流程,無需侵入代碼,通過可視化配置灰度規則,有效地解決了微服務全鏈路灰度發佈難實現的問題,讓灰度發佈更便捷、更順利。
- Apache Pulsar 技術系列 - Pulsar 總覽
- 解決創新業務的三大架構難題,央廣購物用對了這個關鍵策略
- 詳解 Apache Pulsar 消息生命週期
- 8年服務百萬客户,這家SaaS公司是懂雲原生的
- 基於騰訊雲微服務引擎(TSE) ,輕鬆實現雲上全鏈路灰度發佈
- 騰訊雲基於 Apache Pulsar 跨地域複製功能實現租户跨集羣遷移
- 面向異構技術棧和基礎設施的服務治理標準化
- Pulsar 在騰訊雲的穩定性實踐
- 迎接2023 | 北極星開源一週年,感恩禮傾情相送
- Apache Pulsar 技術系列 – 基於不同部署策略和配置策略的容災保障
- 輕量級SaaS化應用數據鏈路構建方案的技術探索及落地實踐
- 微服務架構下路由、多活、灰度、限流的探索與挑戰
- PolarisMesh北極星 V1.11.3 版本發佈
- 千億級、大規模:騰訊超大 Apache Pulsar 集羣性能調優實踐
- Apache Pulsar 系列 —— 深入理解 Bookie GC 回收機制
- 騰訊雲微服務引擎 TSE 產品動態
- 千億級、大規模:騰訊超大 Apache Pulsar 集羣性能調優實踐
- TSF微服務治理實戰系列(三)——服務限流
- 如何解決 Spring Cloud 下測試環境路由問題
- TSF微服務治理實戰系列(二)——服務路由