You can connect Spring Cloud applications to Alibaba Cloud Service Mesh (ASM) so that you can use cloud-native service governance capabilities to manage Spring Cloud services without modifying business code. This topic describes how to use ASM to manage Spring Cloud services.

Prerequisites

Background information

Spring Cloud is a standard with different implementations, such as Spring Cloud Netflix, Spring Cloud Alibaba, and Spring Cloud Consul. For ASM, the core difference among different Spring Cloud implementations is that they use different registries. The following table describes whether ASM supports migration between registries for different Spring Cloud versions.
Spring Cloud version Registry Whether migration is supported without code modification
Spring Cloud Alibaba Microservice Engine (MSE) Nacos (an Alibaba Cloud service) Supported
Spring Cloud Alibaba Self-managed Nacos Supported
Spring Cloud Netflix Eureka Not supported. We recommend that you use Nacos as the registry.
Spring Cloud Consul Consul Not supported. We recommend that you use Nacos as the registry.
Spring Cloud Zookeeper Zookeeper Not supported. We recommend that you use Nacos as the registry.

Demo introduction

You can download the sample files for the Spring Cloud services that are deployed in this topic from the nacos-examples directory on GitHub.

The Spring Cloud services involved include a consumer service and a provider service. The provider service has two versions: V1 and V2. Both versions of the provider service are registered with the Nacos registry. The consumer service synchronizes the endpoints of the two versions of the provider service from the Nacos registry and sends requests to the endpoints in a load balancing manner. The consumer service exposes port 8080 and provides an echo interface. After the requests are forwarded to the provider service, the provider service returns corresponding responses, and then the consumer service delivers the responses. Different versions of the provider service return different responses.
  • The provider service of V1 responds to an echo request with the following information: Hello Nacos Discovery From v1xxx.
  • The provider service of V2 responds to an echo request with the following information: Hello Nacos Discovery From v2xxx.
The xxx string in a response indicates the specific parameter in the corresponding echo request. For example, if the /echo/world request is sent to the provider service of V1, the Hello Nacos Discovery From v1world response is returned. demo

Step 1: Configure a service entry and an Envoy filter

You must configure a service entry and an Envoy filter in ASM so that ASM can manage Spring Cloud services.

  1. Use kubectl to connect to an ASM instance.
  2. Create a service entry.
    1. Create an external-nacos-svc.yaml file that contains the following code:
      apiVersion: networking.istio.io/v1alpha3
      kind: ServiceEntry
      metadata:
        name: external-nacos-svc
      spec:
        hosts:
        - "NACOS_SERVER_HOST"  ## Set this parameter to the endpoint of your Nacos server host. Example: mse-xxx-p.nacos-ans.mse.aliyuncs.com. 
        location: MESH_EXTERNAL
        ports:
        - number: 8848
          name: http
        resolution: DNS
      Port 8848 is the default port used by Nacos. If you use a self-managed Nacos server and a custom port number, you must set the number parameter to the port number that you use.
    2. Run the following command to create a service entry:
      kubectl apply -f external-nacos-svc.yaml
  3. Create an Envoy filter.
    1. Create an external-envoyfilter.yaml file that contains the following code:
      apiVersion: networking.istio.io/v1alpha3
      kind: EnvoyFilter
      metadata:
        labels:
          provider: "asm"
          asm-system: "true"
        name: nacos-subscribe-lua
        namespace: istio-system
      spec:
        configPatches:
          # The first patch adds the lua filter to the listener/http connection manager
        - applyTo: HTTP_FILTER
          match:
            proxy:
              proxyVersion: "^1.*"
            context: SIDECAR_OUTBOUND
            listener:
              portNumber: 8848
              filterChain:
                filter:
                  name: "envoy.filters.network.http_connection_manager"
                  subFilter:
                    name: "envoy.filters.http.router"
          patch:
            operation: INSERT_BEFORE
            value: # lua filter specification
             name: envoy.lua
             typed_config:
                "@type": "type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua"
                inlineCode: |
                   -- copyright: ASM (Alibaba Cloud ServiceMesh)
                   function envoy_on_request(request_handle)
                     local request_headers = request_handle:headers()
                     -- /nacos/v1/ns/instance/list?healthyOnly=false&namespaceId=public&clientIP=11.122.XX.XX&serviceName=DEFAULT_GROUP%40%40service-provider&udpPort=53174&encoding=UTF-8
                     local path = request_headers:get(":path")
                     if string.match(path,"^/nacos/v1/ns/instance/list") then
                       local servicename = string.gsub(path,".*&serviceName.-%%40([%w.\\_\\-]+)&.*","%1")
                       request_handle:streamInfo():dynamicMetadata():set("context", "request.path", path)
                       request_handle:streamInfo():dynamicMetadata():set("context", "request.servicename", servicename)
                       request_handle:logInfo("subscribe for serviceName: " .. servicename)
                     else
                       request_handle:streamInfo():dynamicMetadata():set("context", "request.path", "")
                     end
                   end
                   function envoy_on_response(response_handle)
                     local request_path = response_handle:streamInfo():dynamicMetadata():get("context")["request.path"]
                     if request_path == "" then
                        return
                     end
                     local servicename = response_handle:streamInfo():dynamicMetadata():get("context")["request.servicename"]
                     response_handle:logInfo("modified response ip to serviceName:" .. servicename)
                     local bodyObject = response_handle:body(true)
                     local body= bodyObject:getBytes(0,bodyObject:length())
                     body = string.gsub(body,"%s+","")
                     body = string.gsub(body,"(ip\":\")(%d+.%d+.%d+.%d+)","%1"..servicename)
                     response_handle:body():setBytes(body)
                   end
    2. Run the following command to create an Envoy filter:
      kubectl apply -f external-envoyfilter.yaml

Step 2: Create Spring Cloud services in an ACK cluster

Note
  • The registration process needs to be intercepted. Therefore, you must create an Envoy filter before you create deployments. If specific deployments are created before you create the Envoy filter, you must enable rolling update for the deployments.
  • For Spring Cloud services, you must create Kubernetes service resources and provide a cluster IP address.
  1. Connect to ACK clusters by using kubectl.
  2. Run the following commands to create Spring Cloud services:
    export NACOS_ADDRESS=xxxx # Replace xxxx with the endpoint of the MSE Nacos registry or a self-managed Nacos registry. We recommend that you use a virtual private cloud (VPC) endpoint. 
    wget https://alibabacloudservicemesh.oss-cn-beijing.aliyuncs.com/asm-labs/springcloud/demo.yaml -O demo.yaml
    sed -e "s/NACOS_SERVER_CLUSTERIP/$NACOS_ADDRESS/g" demo.yaml |kubectl apply -f -
  3. Run the following command to check the Spring Cloud services:
     kubectl get pods

    Expected output:

    consumer-bdd464654-jn8q7       2/2     Running     0          25h
    provider-v1-66bc67fb6d-46pgl   2/2     Running     0          25h
    provider-v2-76568c45f6-85z87   2/2     Running     0          25h

Step 3: Create a gateway rule and a virtual service

  1. Use kubectl to connect to an ASM instance.
  2. Create a gateway rule.
    1. Create a test-gateway.yaml file that contains the following code:
      apiVersion: networking.istio.io/v1alpha3
      kind: Gateway
      metadata:
        name: test-gateway
      spec:
        selector:
          istio: ingressgateway # use istio default controller
        servers:
        - port:
            number: 80
            name: http
            protocol: HTTP
          hosts:
          - "*"
    2. Run the following command to create a gateway rule:
      kubectl apply -f test-gateway.yaml
  3. Create a virtual service.
    1. Create a consumer.yaml file that contains the following code:
      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: consumer
      spec:
        hosts:
        - "*"
        gateways:
        - test-gateway
        http:
        - match:
          - uri:
              prefix: /
          route:
          - destination:
              host: consumer.default.svc.cluster.local
              port:
                number: 8080
    2. Run the following command to create a virtual service:
      kubectl apply -f consumer.yaml

Step 4: Check whether ASM can manage the Spring Cloud services

  1. Query the IP address of the ingress gateway.
    1. Log on to the ASM console.
    2. In the left-side navigation pane, choose Service Mesh > Mesh Management.
    3. On the Mesh Management page, find the ASM instance that you want to configure. Click the name of the ASM instance or click Manage in the Actions column.
    4. On the details page of the ASM instance, click ASM Gateways in the left-side navigation pane.
      On the ASM Gateways page, view the IP address of the ingress gateway in the Kubernetes Service column.
  2. Run the following command to initiate requests from the ingress gateway to the Spring Cloud consumer service:
    curl <IP address of the ingress gateway>/echo/world
    Expected output:
    Hello Nacos Discovery From v1world
    Hello Nacos Discovery From v2world
    Hello Nacos Discovery From v1world
    Hello Nacos Discovery From v2world
    The output shows that traffic is routed to V1 and V2 of the provider service in polling mode by default.
  3. Create a destination rule and a virtual service.
    1. Create a service-provider.yaml file that contains the following code:
      ---
      apiVersion: networking.istio.io/v1alpha3
      kind: DestinationRule
      metadata:
        name: service-provider
      spec:
        host: service-provider
        subsets:
        - name: v1
          labels:
            label: v1
        - name: v2
          labels:
            label: v2
                                      
    2. Run the following command to create a destination rule:
      kubectl apply -f service-provider.yaml
    3. Create a service-provider1.yaml file that contains the following code.
      The virtual service to be created defines that /echo/hello requests are routed to the provider service of V1 and other requests are routed to the provider service of V2.
      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: service-provider
      spec:
        hosts:
        - service-provider
        http:
        - name: "hello-v1"
          match:
          - uri:
              prefix: "/echo/hello"
          route:
          - destination:
              host: service-provider
              subset: v1
        - name: "default"
          route:
          - destination:
              host: service-provider
              subset: v2
    4. Run the following command to create a virtual service:
      kubectl apply -f service-provider1.yaml
  4. Run the following command to initiate requests to the Spring Cloud consumer service:
    curl <IP address of the ingress gateway>/echo/hello
    Expected output:
    Hello Nacos Discovery From v1hello
    Hello Nacos Discovery From v1hello
    The output shows that /echo/hello requests are routed to the provider service of V1 and other requests are routed to the provider service of V2. This indicates that Spring Cloud traffic is taken over by Istio and Custom Resource Definitions (CRDs) provided by Istio can be used to configure routing rules. In this case, ASM can manage the Spring Cloud services.

FAQ

What do I do if the Spring Cloud services that I deployed by following the steps in this topic do not take effect?
  1. Check whether throttling is enabled for the port or IP address of Nacos.
  2. Make sure that an Envoy filter is created before you create deployments so as to intercept the registration process. If specific deployments are created before you create the Envoy filter, you must enable rolling update for the deployments.
  3. Check whether you have created Kubernetes service resources and provided a cluster IP address for the Spring Cloud services.
  4. Make sure that the client SDK of Nacos is of a version earlier than V2.0. The client SDK of V2.0 or later uses gRPC to connect to the server, which is beyond the application scope of the solution provided in this topic.