通過消息隊列這類異步場景下的流量控制,您可以在不需要修改任何業務代碼的情況下,實現全鏈路灰度。本文介紹MSE基于消息隊列RocketMQ版實現全鏈路灰度。
前提條件
Demo架構
本文以容器服務控制臺部署應用為例進行說明,您也可以使用kubectl方式部署應用,模擬一個真實的調用鏈路。Demo應用的結構圖如下圖所示,應用之間的調用既包含了Spring Cloud的調用,也包含了Dubbo的調用,覆蓋了當前最常用的兩種微服務框架。其中C應用會生產出RocketMQ消息,由A應用進行消費,A應用在消費消息時,也會發起新的調用。這些應用都是基于Spring Cloud、Dubbo和RocketMQ的標準用法。
步驟一:將應用接入MSE微服務治理
將ACK微服務應用接入MSE治理中心,您可以選擇您需要的方式實現應用接入。更多信息,請參見ACK微服務應用接入MSE治理中心。
為ACK命名空間中的應用開啟MSE微服務治理
登錄MSE治理中心控制臺,并在頂部菜單欄選擇地域。
在左側導航欄,選擇治理中心 > 應用治理。
在應用列表頁面,單擊ACK應用接入。
在ACK應用接入對話框中,進行配置,配置完成后,單擊確定。
配置項
說明
集群類型
選擇ACK集群、ACK Serverless集群和ACS集群
說明如果您尚未授權容器服務調用微服務引擎,則需要單擊請授權進行授權。
集群名稱/ID
選擇接入MSE微服務治理的集群名稱/ID,可通過關鍵詞搜索。
ack-onepilot
顯示ack-onepilot接入狀態。
如果您未安裝ack-onepilot,單擊ack-onepilot右側的點擊安裝,安裝完成后狀態會顯示為已安裝。
如果您使用子賬號接入,提示沒有權限使用時,您可以登錄容器服務管理控制臺進入目標集群,然后單擊運維管理>組件管理,然后找到ack-onepilot,點擊安裝。
說明該步驟接入的組件為ack-onepilot,您可以登錄容器服務管理控制臺進入目標集群,然后單擊運維管理>組件管理查看詳情。
ack-onepilot安裝后會自動注入探針,可能會導致應用啟動耗時增加(10s內)。
接入類型
選擇命名空間接入。
容器集群命名空間
選擇容器集群命名空間。
治理命名空間
選擇治理命名空間。在對應命名空間下重新部署現有應用或新創建的應用,均會接入到MSE微服務治理中。關于命名空間的相關信息,請參見微服務命名空間管理。
為單個應用開啟MSE微服務治理
登錄MSE治理中心控制臺,并在頂部菜單欄選擇地域。
在左側導航欄,選擇治理中心 > 應用治理。
在應用列表頁面,單擊ACK應用接入。
在ACK應用接入對話框中,進行配置,配置完成后,單擊確定。
配置項
說明
集群類型
選擇ACK集群、ACK Serverless集群和ACS集群。
說明如果您尚未授權容器服務調用微服務引擎,則需要單擊請授權進行授權。
集群名稱/ID
選擇接入MSE微服務治理的集群名稱/ID,可通過關鍵詞搜索。
ack-onepilot
顯示ack-onepilot接入狀態。
如果您未安裝ack-onepilot,單擊ack-onepilot右側的點擊安裝,安裝完成后狀態會顯示為已安裝。
如果您使用子賬號接入,提示沒有權限使用時,您可以登錄容器服務管理控制臺進入目標集群,然后單擊運維管理>組件管理,然后找到ack-onepilot,點擊安裝。
說明該步驟接入的組件為ack-onepilot,您可以登錄容器服務管理控制臺進入目標集群,然后單擊運維管理>組件管理查看詳情。
ack-onepilot安裝后會自動注入探針,可能會導致應用啟動耗時增加(10s內)。
接入類型
選擇單個應用接入。
接入步驟
按照接入步驟進行操作。
Step 1:進入集群工作負載-無狀態應用頁面,切換到應用的命名空間下。
Step 2:找到所接入的應用,點擊「查看Yaml」。
Step 3:按以下格式編輯Labels,完成后點擊「更新」。
spec: template: metadata: labels: # 填寫“on”表示開啟接入,需加上雙引號 msePilotAutoEnable: "on" # 填寫接入到的治理命名空間,值不存在可自動新建 mseNamespace: 202401 # 填寫接入MSE的實際應用名稱,需加上雙引號 msePilotCreateAppName: "your-deployment-name"
步驟二:部署應用
部署spring-cloud-zuul、spring-cloud-a、spring-cloud-b、spring-cloud-c這四個業務應用,以及注冊中心Nacos Server和消息服務RocketMQ Server,您也可以直接在Demo中獲取對應的源碼。
spring-cloud-zuul應用在收到/A/dubbo
的請求時,會把請求轉發給spring-cloud-a,然后spring-cloud-a通過Dubbo協議去訪問spring-cloud-b,spring-cloud-b也通過Dubbo協議去訪問spring-cloud-c,spring-cloud-c在收到請求后,會生產一個消息,并返回自己的環境標簽和IP。這些生產出來的消息會由spring-cloud-a應用消費,spring-cloud-a應用在消費消息的時候,會通過spring cloud去調用spring-cloud-b,spring-cloud-b進而通過spring cloud去調用spring-cloud-c,并且將結果輸出到自己的日志中。
# 當訪問/A/dubbo的時候,返回值為:A[10.25.xx.xx] -> B[10.25.xx.xx] -> C[10.25.xx.xx]
# 同時,A應用在接收到消息之后,輸出的日志如下:
2021-12-28 10:58:50.301 INFO 1 --- [essageThread_15] c.a.mse.demo.service.MqConsumer
: topic:TEST_MQ,producer:C[10.25.xx.xx],invoke result:A[10.25.xx.xx] -> B[10.25.xx.xx] -> C[10.25.xx.xx]
登錄容器服務管理控制臺,在左側導航欄選擇集群。
在集群列表頁面,單擊目標集群名稱或者目標集群右側操作列下的詳情。
在集群管理頁左側導航欄,選擇 。
在無狀態頁面選擇命名空間,然后單擊使用YAML創建資源。使用如下YAML部署應用:
# 部署Nacos Server apiVersion: apps/v1 kind: Deployment metadata: name: nacos-server spec: selector: matchLabels: app: nacos-server template: metadata: labels: app: nacos-server spec: containers: - env: - name: MODE value: "standalone" image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/nacos-server:v2.1.2 imagePullPolicy: IfNotPresent name: nacos-server ports: - containerPort: 8848 --- apiVersion: v1 kind: Service metadata: name: nacos-server spec: type: ClusterIP selector: app: nacos-server ports: - name: http port: 8848 targetPort: 8848 # 部署業務應用 --- apiVersion: apps/v1 kind: Deployment metadata: name: spring-cloud-zuul spec: selector: matchLabels: app: spring-cloud-zuul template: metadata: labels: app: spring-cloud-zuul msePilotCreateAppName: spring-cloud-zuul spec: containers: - env: - name: JAVA_HOME value: /usr/lib/jvm/java-1.8-openjdk/jre - name: enable.mq.invoke value: 'true' image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-zuul:3.0.1 imagePullPolicy: Always name: spring-cloud-zuul ports: - containerPort: 20000 --- apiVersion: v1 kind: Service metadata: annotations: service.beta.kubernetes.io/alibaba-cloud-loadbalancer-spec: slb.s1.small service.beta.kubernetes.io/alicloud-loadbalancer-address-type: internet name: zuul-slb spec: ports: - port: 80 protocol: TCP targetPort: 20000 selector: app: spring-cloud-zuul type: LoadBalancer status: loadBalancer: {} --- apiVersion: apps/v1 kind: Deployment metadata: name: spring-cloud-a spec: selector: matchLabels: app: spring-cloud-a template: metadata: labels: app: spring-cloud-a msePilotCreateAppName: spring-cloud-a spec: containers: - env: - name: JAVA_HOME value: /usr/lib/jvm/java-1.8-openjdk/jre image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-a:3.0.1 imagePullPolicy: Always name: spring-cloud-a ports: - containerPort: 20001 livenessProbe: tcpSocket: port: 20001 initialDelaySeconds: 10 periodSeconds: 30 --- apiVersion: apps/v1 kind: Deployment metadata: name: spring-cloud-b spec: selector: matchLabels: app: spring-cloud-b template: metadata: labels: app: spring-cloud-b msePilotCreateAppName: spring-cloud-b spec: containers: - env: - name: JAVA_HOME value: /usr/lib/jvm/java-1.8-openjdk/jre image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-b:3.0.1 imagePullPolicy: Always name: spring-cloud-b ports: - containerPort: 20002 livenessProbe: tcpSocket: port: 20002 initialDelaySeconds: 10 periodSeconds: 30 --- apiVersion: apps/v1 kind: Deployment metadata: name: spring-cloud-c spec: selector: matchLabels: app: spring-cloud-c template: metadata: labels: app: spring-cloud-c msePilotCreateAppName: spring-cloud-c spec: containers: - env: - name: JAVA_HOME value: /usr/lib/jvm/java-1.8-openjdk/jre image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-c:3.0.1 imagePullPolicy: Always name: spring-cloud-c ports: - containerPort: 20003 livenessProbe: tcpSocket: port: 20003 initialDelaySeconds: 10 periodSeconds: 30 --- apiVersion: apps/v1 kind: Deployment metadata: name: rockectmq-broker spec: selector: matchLabels: app: rockectmq-broker template: metadata: labels: app: rockectmq-broker spec: containers: - command: - sh - mqbroker - '-n' - 'mqnamesrv:9876' - '-c /home/rocketmq/rocketmq-4.9.7/conf/broker.conf' env: - name: ROCKETMQ_HOME value: /home/rocketmq/rocketmq-4.9.7 image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/rocketmq:4.9.7 imagePullPolicy: Always name: rockectmq-broker ports: - containerPort: 9876 protocol: TCP - containerPort: 10911 protocol: TCP - containerPort: 10912 protocol: TCP - containerPort: 10909 --- apiVersion: apps/v1 kind: Deployment metadata: name: rocketmq-name-server spec: selector: matchLabels: app: rocketmq-name-server template: metadata: labels: app: rocketmq-name-server spec: containers: - command: - sh - mqnamesrv env: - name: ROCKETMQ_HOME value: /home/rocketmq/rocketmq-4.9.7 image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/rocketmq:4.9.7 imagePullPolicy: Always name: rocketmq-name-server ports: - containerPort: 9876 protocol: TCP - containerPort: 10911 protocol: TCP - containerPort: 10912 protocol: TCP - containerPort: 10909 protocol: TCP --- apiVersion: v1 kind: Service metadata: name: mqnamesrv spec: type: ClusterIP selector: app: rocketmq-name-server ports: - name: mqnamesrv-9876-9876 port: 9876 targetPort: 9876
執行以下命令,查看應用是否部署成功。
kubectl get svc,deploy
預期輸出:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 192.168.xx.xx <none> 4xx/TCP 7d service/mqnamesrv ClusterIP 192.168.xx.xx <none> 98xx/TCP 47h service/nacos-server ClusterIP 192.168.xx.xx <none> 88xx/TCP 47h service/zuul-slb LoadBalancer 192.168.xx.xx 123.56.xx.xx 80:302xxx/TCP 47h NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/nacos-server 1/1 1 1 4m deployment.apps/rockectmq-broker 1/1 1 1 4m deployment.apps/rocketmq-name-server 1/1 1 1 5m deployment.apps/spring-cloud-a 1/1 1 1 5m deployment.apps/spring-cloud-b 1/1 1 1 5m deployment.apps/spring-cloud-c 1/1 1 1 5m deployment.apps/spring-cloud-zuul 1/1 1 1 5m
步驟三:為應用開啟RocketMQ消息灰度
假設spring-cloud-c、spring-cloud-a應用分別為消息的生產者和消費者,為其開啟RocketMQ消息灰度。
RocketMQ消息灰度功能開啟和關閉需要容器服務控制臺重新部署應用后才能生效。
消息的生產者和消息的消費者,需要同時開啟RocketMQ消息灰度,消息的灰度功能才能生效。
消息類型目前只支持RocketMQ,包含開源版本和阿里云商業版。
如果您使用開源RocketMQ,則RocketMQ Server和RocketMQ Client都需要使用4.5.0及以上版本。
如果您使用阿里云RocketMQ的4.x系列版本,SQL92的過濾方式需要使用鉑金版(使用客戶端過濾的方式無該限制,使用阿里云RocketMQ的5.x系列版本也無該限制)。
如果您使用的客戶端為 Ons Client ,需要使用1.8.0.Final及以上版本。
開啟RocketMQ消息灰度后,MSE會修改消息的Consumer Group。例如原來的Consumer Group為group1,環境標簽為gray,開啟RocketMQ消息灰度后,則group會被修改成group1_gray,如果您使用的是阿里云RocketMQ,請提前創建好group。
MSE默認使用SQL92的過濾方式,如果您使用開源RocketMQ,需要在服務端開啟SQL92過濾功能(即在broker.conf中配置
enablePropertyFilter=true
)。如果您的應用場景不滿足支持SQL92過濾的條件,那么可以使用通過FilterMessageHook在消費者過濾的方式,此方式需要在所有的應用中打開消息灰度并且選擇客戶端過濾方式。因為消費者過濾的方式會在每個環境都處理全量的消息,對消息的生產者和消費者壓力都比較大,不推薦在生產中使用此模式。
登錄MSE治理中心控制臺,并在頂部菜單欄選擇地域。
在左側導航欄,選擇治理中心 > 應用治理。
在應用列表頁面,單擊目標應用的資源卡片,然后在左側導航欄單擊流量治理。
單擊消息灰度頁簽,打開開啟消息灰度開關,單擊確定。
步驟四:部署新版本應用
部署新版本的spring-cloud-a-gray、spring-cloud-b-gray和spring-cloud-c-gray應用。
登錄容器服務管理控制臺,在左側導航欄選擇集群。
在集群列表頁面,單擊目標集群名稱或者目標集群右側操作列下的詳情。
在集群管理頁左側導航欄,選擇 。
在無狀態頁面選擇命名空間,然后單擊使用YAML創建資源。使用如下YAML部署應用:
apiVersion: apps/v1 kind: Deployment metadata: name: spring-cloud-a-gray spec: selector: matchLabels: app: spring-cloud-a-gray template: metadata: labels: alicloud.service.tag: gray app: spring-cloud-a-gray msePilotCreateAppName: spring-cloud-a spec: containers: - env: - name: JAVA_HOME value: /usr/lib/jvm/java-1.8-openjdk/jre image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-a:3.0.1 imagePullPolicy: Always name: spring-cloud-a-gray ports: - containerPort: 20001 livenessProbe: tcpSocket: port: 20001 initialDelaySeconds: 10 periodSeconds: 30 --- apiVersion: apps/v1 kind: Deployment metadata: name: spring-cloud-b-gray spec: selector: matchLabels: app: spring-cloud-b-gray template: metadata: labels: alicloud.service.tag: gray app: spring-cloud-b-gray msePilotCreateAppName: spring-cloud-b spec: containers: - env: - name: JAVA_HOME value: /usr/lib/jvm/java-1.8-openjdk/jre image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-b:3.0.1 imagePullPolicy: Always name: spring-cloud-b-gray ports: - containerPort: 20002 livenessProbe: tcpSocket: port: 20002 initialDelaySeconds: 10 periodSeconds: 30 --- apiVersion: apps/v1 kind: Deployment metadata: name: spring-cloud-c-gray spec: selector: matchLabels: app: spring-cloud-c-gray template: metadata: labels: alicloud.service.tag: gray app: spring-cloud-c-gray msePilotCreateAppName: spring-cloud-c spec: containers: - env: - name: JAVA_HOME value: /usr/lib/jvm/java-1.8-openjdk/jre image: registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-c:3.0.1 imagePullPolicy: Always name: spring-cloud-c-gray ports: - containerPort: 20003 livenessProbe: tcpSocket: port: 20003 initialDelaySeconds: 10 periodSeconds: 30
步驟五:引入流量并進行驗證
登錄MSE治理中心控制臺,并在頂部菜單欄選擇地域。
在左側導航欄,選擇治理中心 > 應用治理。
單擊目標應用spring-cloud-a資源卡片。在應用概覽頁面,可以通過QPS數據觀察到所有的流量請求都是去往spring-cloud-a應用未打標的版本,即穩定版本。
在左側導航欄,單擊流量治理,然后單擊標簽路由頁簽,在
gray
標簽的流量規則列下單擊添加。在創建標簽路由面板,配置流量規則,然后單擊確定。
本文示例中設置的流量規則條件為
name=xiaoming
。關于流量規則的配置,請參見配置標簽路由。流量規則生效后,在應用概覽頁面可以查看流量分布結果。
在標簽路由頁簽,單擊gray標簽的目標規則名稱。在規則詳情面板,單擊編輯。在修改標簽路由面板,打開是否鏈路傳遞的開關,然后單擊確定。
說明打開鏈路傳遞開關后,
name=xiaoming
的灰度流量能在全鏈路里進行透傳,且無需重復地配置規則。登錄容器服務管理控制臺,在左側導航欄選擇集群。
在集群列表頁面,單擊目標集群名稱,然后在左側導航欄,選擇 。
單擊zuul-slb服務,然后在zuul-slb的基本信息區域,單擊外部 IP 地址(External IP)右側的地址。
在服務調用頁面輸入/A/a?name=xiaoming,然后單擊開始調用。
全鏈路金絲雀發布已經生效。
此時,spring-cloud-b應用的請求數據如下:
步驟六:調整消息的標簽過濾規則,并進行驗證
登錄MSE治理中心控制臺。
在左側導航欄,選擇 ,然后單擊目標應用spring-cloud-a資源卡片。
在左側導航欄,單擊流量治理,然后單擊消息灰度頁簽,找到未打標環境忽略的標簽參數,在標簽列表中選擇
gray
,單擊確定。在未打標環境忽略的標簽中,選擇
gray
,意味著帶著gray環境標簽的消息,只能由spring-cloud-a-gray消費,不能由spring-cloud-a來消費。未打標環境忽略的標簽參數配置完成后動態生效,您不需要進行重啟的操作。說明默認情況下,未打標節點將消費所有環境的消息,如果需要指定某個未打標節點不消費某個標簽環境生產出來的消息,只需配置未打標環境忽略的標簽參數即可。
登錄容器服務控制臺,查看spring-cloud-a應用的日志,驗證配置。
從下圖日志中,可以看到,此時基線環境可以同時消費gray和基線環境生產出來的消息,當配置完未打標環境忽略的標簽為 gray 后,基線環境就只消費基線環境生產出來的消息了。