基于Spring Boot应用实现全链路灰度

目前Java 探针已经支持k8s service服务发现方式的全链路灰度。由于Spring Boot框架本身不具备连接三方注册中心的能力,许多部署在Kubernetes容器集群的Spring Boot框架直接基于容器集群中k8s service的服务发现能力进行应用间调用。本文介绍如何使用MSE产品,为部署在Kubernetes容器集群的Spring Boot应用实现全链路灰度能力。

前提条件

使用限制

重要

由于该能力目前正在公测,仅以下地区开放支持:北京、上海、杭州、深圳、张家口、硅谷、新加坡,其他地区暂不支持。

  • Spring Boot 应用调用其他应用时支持的HTTP框架及版本限制,请参见微服务治理支持的框架

  • 由于该能力目前正在公测,Java探针需要指定特殊版本为4.2.5-proxyless。指定探针版本具体操作请参见指定MSE探针版本

Demo概述

本文以容器服务控制台部署应用为例进行说明,Demo应用的结构图如下图所示,应用之间的调用既包含了 Spring CloudNacos 的服务发现场景,也包含了 Spring BootK8s Service 的服务发现场景。

  • Gateway:基于Spring Cloud Gateway实现,作为后端流量入口,基于Nacos发现方式调用A。

  • A:Spring Cloud应用,应用节点会注册到Nacos进行服务发现,基于K8s Service发现方式调用B、基于Nacos发现方式调用D。

  • B:Spring Boot应用,基于K8s Service发现方式调用D。

  • D:Spring Cloud应用,应用节点会注册到Nacos进行服务发现。

image

操作步骤

步骤一:部署基线与灰度应用

  1. 登录容器服务控制台

  2. 在左侧导航栏,单击集群列表,然后单击目标集群名称。

  3. 在左侧导航栏,选择工作负载 > 无状态

  4. 在页面上方,选择集群的命名空间,然后单击右上角的使用YAML创建资源

  5. 使用如下YAML部署应用。

    展开YAML

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nacos-server
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: nacos-server
      template:
        metadata:
          labels:
            msePilotAutoEnable: "off"
            app: nacos-server
        spec:
          containers:
            - name: nacos-server
              image: "registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/nacos-server:v2.1.2"
              env:
                - name: MODE
                  value: standalone
                - name: JVM_XMS
                  value: 512M
                - name: JVM_XMX
                  value: 512M
                - name: JVM_XMN
                  value: 256M
              imagePullPolicy: Always
              livenessProbe:
                failureThreshold: 3
                initialDelaySeconds: 15
                periodSeconds: 10
                successThreshold: 1
                tcpSocket:
                  port: 8848
                timeoutSeconds: 3
              readinessProbe:
                failureThreshold: 5
                initialDelaySeconds: 15
                periodSeconds: 15
                successThreshold: 1
                tcpSocket:
                  port: 8848
                timeoutSeconds: 3
              resources:
                requests:
                  cpu: '1'
                  memory: 2Gi
          dnsPolicy: ClusterFirst
          restartPolicy: Always
    
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: nacos-server
    spec:
      type: ClusterIP
      ports:
        - name: nacos-server-8848-8848
          port: 8848
          protocol: TCP
          targetPort: 8848
        - name: nacos-server-9848-9848
          port: 9848
          protocol: TCP
          targetPort: 9848
      selector:
        app: nacos-server
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: spring-cloud-gateway
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: spring-cloud-gateway
      strategy:
        rollingUpdate:
          maxSurge: 100%
          maxUnavailable: 25%
        type: RollingUpdate
      template:
        metadata:
          labels:
            msePilotAutoEnable: "on"
            msePilotCreateAppName: "spring-cloud-gateway"
            mseNamespace: "mse-springboot-demo"
            app: spring-cloud-gateway
            sidecar.istio.io/inject: 'false'
            aliyun.com/agent-version: "4.2.5-proxyless"
        spec:
          containers:
            - image: "registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-gateway:3.1.0-heterogeneous"
              imagePullPolicy: Always
              env:
                - name: nacos.host
                  value: "nacos-server"
                - name: nacos.namespace
                  value: "public"
              name: "spring-cloud-gateway"
              resources:
                requests:
                  cpu: "1"
                  memory: "2Gi"
                limits:
                  cpu: "1"
                  memory: "2Gi"
              ports:
                - name: http-port
                  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: spring-cloud-gateway-slb
    spec:
      ports:
        - port: 80
          protocol: TCP
          targetPort: 20000
      selector:
        app: spring-cloud-gateway
      type: LoadBalancer
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: spring-cloud-a
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: spring-cloud-a
      strategy:
        rollingUpdate:
          maxSurge: 100%
          maxUnavailable: 25%
        type: RollingUpdate
      template:
        metadata:
          labels:
            msePilotAutoEnable: "on"
            msePilotCreateAppName: "spring-cloud-a"
            mseNamespace: "mse-springboot-demo"
            app: "spring-cloud-a"
            sidecar.istio.io/inject: 'false'
            aliyun.com/agent-version: "4.2.5-proxyless"
        spec:
          containers:
            - name: spring-cloud-a
              image: "registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-a:3.1.0-heterogeneous"
              imagePullPolicy: Always
              env:
                - name: nacos.host
                  value: "nacos-server"
                - name: nacos.namespace
                  value: "public"
              resources:
                requests:
                  cpu: "1"
                  memory: "2Gi"
                limits:
                  cpu: "1"
                  memory: "2Gi"
              ports:
                - name: http-port
                  containerPort: 20001
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: spring-cloud-a-gray
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: spring-cloud-a
      strategy:
        rollingUpdate:
          maxSurge: 100%
          maxUnavailable: 25%
        type: RollingUpdate
      template:
        metadata:
          labels:
            msePilotAutoEnable: "on"
            msePilotCreateAppName: "spring-cloud-a"
            mseNamespace: "mse-springboot-demo"
            alicloud.service.tag: gray
            app: "spring-cloud-a"
            sidecar.istio.io/inject: 'false'
            aliyun.com/agent-version: "4.2.5-proxyless"
        spec:
          containers:
            - name: spring-cloud-a
              image: "registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-a:3.1.0-heterogeneous"
              imagePullPolicy: Always
              env:
                - name: nacos.host
                  value: "nacos-server"
                - name: nacos.namespace
                  value: "public"
              resources:
                requests:
                  cpu: "1"
                  memory: "2Gi"
                limits:
                  cpu: "1"
                  memory: "2Gi"
              ports:
                - name: http-port
                  containerPort: 20001
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: spring-cloud-a
    spec:
      ports:
        - port: 20001
          protocol: TCP
          targetPort: 20001
      selector:
        app: spring-cloud-a
      type: ClusterIP
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: spring-boot-b
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: spring-boot-b
      strategy:
        rollingUpdate:
          maxSurge: 100%
          maxUnavailable: 25%
        type: RollingUpdate
      template:
        metadata:
          labels:
            msePilotAutoEnable: "on"
            msePilotCreateAppName: "spring-boot-b"
            mseNamespace: "mse-springboot-demo"
            app: "spring-boot-b"
            sidecar.istio.io/inject: 'false'
            aliyun.com/agent-version: "4.2.5-proxyless"
        spec:
          containers:
            - name: spring-boot-b
              image: "registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-boot-b:3.1.0-heterogeneous"
              imagePullPolicy: Always
              resources:
                requests:
                  cpu: "1"
                  memory: "2Gi"
                limits:
                  cpu: "1"
                  memory: "2Gi"
              ports:
                - name: http-port
                  containerPort: 20002
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: spring-boot-b-gray
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: spring-boot-b
      strategy:
        rollingUpdate:
          maxSurge: 100%
          maxUnavailable: 25%
        type: RollingUpdate
      template:
        metadata:
          labels:
            msePilotAutoEnable: "on"
            msePilotCreateAppName: "spring-boot-b"
            mseNamespace: "mse-springboot-demo"
            alicloud.service.tag: gray
            app: "spring-boot-b"
            sidecar.istio.io/inject: 'false'
            aliyun.com/agent-version: "4.2.5-proxyless"
        spec:
          containers:
            - name: spring-boot-b
              image: "registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-boot-b:3.1.0-heterogeneous"
              imagePullPolicy: Always
              resources:
                requests:
                  cpu: "1"
                  memory: "2Gi"
                limits:
                  cpu: "1"
                  memory: "2Gi"
              ports:
                - name: http-port
                  containerPort: 20002
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: spring-boot-b
    spec:
      ports:
        - port: 20002
          protocol: TCP
          targetPort: 20002
      selector:
        app: spring-boot-b
      type: ClusterIP
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: spring-cloud-d
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: spring-cloud-d
      strategy:
        rollingUpdate:
          maxSurge: 100%
          maxUnavailable: 25%
        type: RollingUpdate
      template:
        metadata:
          labels:
            msePilotAutoEnable: "on"
            msePilotCreateAppName: "spring-cloud-d"
            mseNamespace: "mse-springboot-demo"
            app: "spring-cloud-d"
            sidecar.istio.io/inject: 'false'
            aliyun.com/agent-version: "4.2.5-proxyless"
        spec:
          containers:
            - name: spring-cloud-d
              image: "registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-d:3.1.0-heterogeneous"
              imagePullPolicy: Always
              env:
                - name: nacos.host
                  value: "nacos-server"
                - name: nacos.namespace
                  value: "public"
              resources:
                requests:
                  cpu: "1"
                  memory: "2Gi"
                limits:
                  cpu: "1"
                  memory: "2Gi"
              ports:
                - name: http-port
                  containerPort: 20004
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: spring-cloud-d-gray
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: spring-cloud-d
      strategy:
        rollingUpdate:
          maxSurge: 100%
          maxUnavailable: 25%
        type: RollingUpdate
      template:
        metadata:
          labels:
            msePilotAutoEnable: "on"
            msePilotCreateAppName: "spring-cloud-d"
            mseNamespace: "mse-springboot-demo"
            alicloud.service.tag: gray
            app: "spring-cloud-d"
            sidecar.istio.io/inject: 'false'
            aliyun.com/agent-version: "4.2.5-proxyless"
        spec:
          containers:
            - name: spring-cloud-d
              image: "registry.cn-hangzhou.aliyuncs.com/mse-governance-demo/spring-cloud-d:3.1.0-heterogeneous"
              imagePullPolicy: Always
              env:
                - name: nacos.host
                  value: "nacos-server"
                - name: nacos.namespace
                  value: "public"
              resources:
                requests:
                  cpu: "1"
                  memory: "2Gi"
                limits:
                  cpu: "1"
                  memory: "2Gi"
              ports:
                - name: http-port
                  containerPort: 20004
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: spring-cloud-d
    spec:
      ports:
        - port: 20004
          protocol: TCP
          targetPort: 20004
      selector:
        app: spring-cloud-d
      type: ClusterIP

步骤二:配置全链路灰度

  1. 登录MSE治理中心控制台,并在顶部菜单栏选择地域。

  2. 在左侧导航栏,选择治理中心 > 全链路灰度

  3. 全链路灰度页面的微服务命名空间处选择mse-springboot-demo

  4. 如果您选择的微服务命名空间内没有创建过泳道组,则单击创建泳道组及泳道。如果您选择的微服务命名空间内已经创建过泳道组,则单击+创建泳道组

  5. 创建泳道组面板,设置如下相关配置,然后单击确定

    配置项

    示例

    泳道组名称

    自定义泳道组的名称,例如mse-springboot-demo。

    入口类型

    选择Java服务网关

    泳道组流量入口

    选择spring-cloud-gateway

    泳道组涉及应用

    选择spring-cloud-a、spring-boot-bspring-cloud-d。

    image

  6. 全链路灰度页面底部,如果您选择的微服务命名空间内没有创建过泳道,则单击点击创建第一个分流泳道。如果您选择的微服务命名空间内已经创建过泳道,则单击创建泳道

  7. 创建泳道面板,设置流控泳道相关配置,然后单击确定

    配置项

    说明

    配置节点标签

    需要您给灰度应用节点打上标签,以便与正常节点做区分。

    填写泳道信息

    泳道名称:输入便于理解和识别的泳道名称。

    泳道标签:该泳道内的匹配的流量去往的目标标签,本示例为gray。

    确认匹配关系:检查您配置了该标签的应用节点数是否符合预期。

    泳道状态:选择开启。

    配置路由和灰度规则

    设置流量进入该泳道的规则。

    • 灰度模式:选择按内容灰度

    • 灰度条件:选择以下条件同时满足

      本示例灰度条件配置如下:

      • 参数类型Header

      • 参数x-springboot-demo

      • 条件==

      • 1

    image

步骤三:结果验证

  1. 登录容器服务控制台

  2. 在左侧导航栏,单击集群列表,然后单击目标集群名称。

  3. 集群信息页左侧导航栏选择网络 > 服务,复制spring-cloud-gateway-slb外部IP地址(External IP),进行如下验证。

    基线环境验证

    Spring Cloud Gateway发起路由为/A/A/a的请求调用,观察到所有流量都路由到了基线节点。

    # 测试命令
    curl http://xxx.xx.131.81/A/A/a
    
    # 测试结果
    A:192.168.0.17:base -(java)- B:192.168.100.231:base - D:192.168.0.19:base
    灰度环境验证

    Spring Cloud Gateway发起路由为/A/A/a的请求调用,并且在Header中添加x-springboot-demo=1,观察到所有流量都路由到灰度节点,说明全链路灰度配置生效。

    # 测试命令
    curl -H "x-springboot-demo:1" http://xxx.xx.131.81/A/A/a
    
    # 测试结果
    A:192.168.100.234:gray -(java)- B:192.168.0.21:gray - D:192.168.0.14:gray

    Tab 2 正文