Implement end-to-end canary release for Spring Boot applications

Updated at:
Copy as MD

The Java agent now supports end-to-end canary release for applications that use Kubernetes service discovery. Unlike Spring Cloud, the Spring Boot framework does not natively connect to third-party service registries. Instead, many Spring Boot applications deployed in Kubernetes clusters rely on Kubernetes Services for inter-application communication. This topic explains how to use MSE Microservices Governance to implement an end-to-end canary release for these applications.

Prerequisites

Limitations

Important

During the public preview, this feature is available only in the following regions: China (Beijing), China (Shanghai), China (Hangzhou), China (Shenzhen), China (Zhangjiakou), US (Silicon Valley), and Singapore.

Because this feature is in public preview, you must pin the Java agent version to 4.2.5-proxyless. For more information, see Specify the MSE agent version.

For information about the supported HTTP frameworks and version limits when a Spring Boot application calls other applications, see Frameworks supported by Microservices Governance.

Demo overview

This topic provides an example of deploying applications from the ACK console. The following diagram shows the architecture of the demo applications. The application call chain includes both Spring Cloud applications that use Nacos for service discovery and Spring Boot applications that use Kubernetes Services for service discovery.

  • Gateway: Based on Spring Cloud Gateway, this application is the traffic ingress and calls Application A by using Nacos-based service discovery.

  • A: A Spring Cloud application. Its nodes are registered with Nacos for service discovery. It calls Application B by using Kubernetes service discovery and calls Application D by using Nacos-based service discovery.

  • B: A Spring Boot application. It calls Application D by using Kubernetes service discovery.

  • D: A Spring Cloud application. Its nodes are registered with Nacos for service discovery.

Procedure

Step 1: Deploy base and canary applications

  1. Log on to the ACK console.

  2. In the left-side navigation pane, click Clusters. Then, click the name of the cluster that you want to manage.

  3. In the left-side navigation pane, choose Workloads > Deployments.

  4. In the upper part of the page, select the namespace of the cluster, and click Create from YAML.

  5. Use the following YAML to deploy the applications.

    Expand 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

Step 2: Configure end-to-end canary release

  1. Log on to the MSE console, and select a region in the top navigation bar.

  2. In the left-side navigation pane, choose Microservices Governance > Full link grayscale.

  3. On the End-to-end Canary Release page, select mse-springboot-demo from the microservice namespace drop-down list.

  4. If no lane group exists in the selected microservice namespace, click Create Lane Groups and Lanes. If a lane group already exists, click + Create Lane Group.

  5. In the Create Lane Group panel, set the following parameters and click OK.

    Parameter

    Example

    Name of Lane Group

    Enter a custom name for the lane group, such as mse-springboot-demo.

    Entry Type

    Select Java Service Gateway.

    Lane Group Traffic Entry

    Select spring-cloud-gateway.

    Lane Group Application

    Select spring-cloud-a, spring-boot-b, and spring-cloud-d.

  6. At the bottom of the Full link Grayscale page, click Click to create the first shunt Lane if no lane exists in the microservice namespace. If a lane already exists, click Create swim lanes.

  7. In the Create swim lanes panel, configure the lane settings and click OK.

    Parameter

    Description

    Configure node tags

    Add a tag to the canary application nodes to distinguish them from the base nodes.

    Enter lane information

    Lane name: Enter a descriptive name for the lane.

    Lane tag: The destination tag for traffic routed to this lane. In this example, the tag is gray.

    Confirm match: Verify that the number of application nodes with this tag is correct.

    Lane status: Select Enabled.

    Configure routing and canary rules

    Set the rules for routing traffic to this lane.

    • Canary release mode: Select By Request Content.

    • Canary release conditions: Select All conditions must be met.

      For this example, configure the conditions as follows:

      • Parameter Type: Header.

      • Parameter: x-springboot-demo.

      • Condition: ==.

      • Value: 1.

Step 3: Verify the results

  1. Log on to the ACK console.

  2. In the left-side navigation pane, click Clusters. Then, click the name of the cluster that you want to manage.

  3. On the Cluster Information page, choose Network > Services in the navigation pane on the left. Copy the external IP address of spring-cloud-gateway-slb and use it to perform the following checks.

    Base traffic routing

    Send a request to the Spring Cloud Gateway with the path /A/A/a. Observe that all traffic is routed to the base nodes.

    # Test command
    curl http://<external-ip-address>/A/A/a
    # Test result
    A:192.168.0.17:base -(java)- B:192.168.100.231:base - D:192.168.0.19:base
    Canary traffic routing

    Send a request to Spring Cloud Gateway with the route /A/A/a and add x-springboot-demo=1 to the Header. All traffic is routed to the canary node, which indicates that the end-to-end canary release configuration is effective.

    # Test command
    curl -H "x-springboot-demo:1" http://<external-ip-address>/A/A/a
    # Test result
    A:192.168.100.234:gray -(java)- B:192.168.0.21:gray - D:192.168.0.14:gray

    Tab 2 content