您可以将Spring Cloud业务应用接入ASM,从而可以使用云原生化的服务治理能力,不需要业务做任何代码修改,即可管理Spring Cloud业务服务。本文介绍如何使用ASM管理Spring Cloud服务。

前提条件

背景信息

Spring Cloud是一个标准,有不同的实现,比如Spring Cloud Netflix、Spring Cloud Alibaba、Spring Cloud Consul 等。不同的Spring Cloud 实现对于ASM 来说核心区别主要在于采用了不同的服务发现,ASM 针对这些不同的Spring Cloud 版本迁移支持列表如下:
Spring Cloud版本 服务发现 迁移支持 (零代码修改)
Spring Cloud Alibaba MSE Nacos (阿里云上产品) 支持
Spring Cloud Alibaba Nacos(自建) 支持
Spring Cloud Netflix Eureka 暂不支持,建议采用Nacos注册中心
Spring Cloud Consul Consul 暂不支持,建议采用Nacos注册中心
Spring Cloud Zookeeper Zookeeper 暂不支持,建议采用Nacos注册中心

Demo介绍

本文部署的Spring Cloud服务可以通过此nacos-example下载。

Spring Cloud服务包含Consumer服务和Provider服务,其中Provider有v1和v2两个版本,并且都注册到Nacos注册中心。Consumer从Nacos注册中心同步Provider服务地址进行负载均衡发起请求,其中Consumer暴露一个8080端口,提供了一个echo接口,对应逻辑是将请求转发给Provider,并输出Provider返回的结果,不同的Provider版本返回结果不同:
  • Provider v1版本收到echo请求会返回Hello Nacos Discovery From v1xxx
  • Provider v2版本收到echo请求会返回Hello Nacos Discovery From v2xxx
其中返回结果中.xxx为echo接口对应的具体参数,例如请求/echo/world发送到Provider v1版本,则会返回Hello Nacos Discovery From v1worlddemo

步骤一:配置ServiceEntry和EnvoyFilter

您需要在ASM中配置ServiceEntry和EnvoyFilter,使ASM可以管理Spring Cloud服务。

  1. 通过kubectl连接ASM实例
  2. 创建ServiceEntry。
    1. 使用以下内容,创建external-nacos-svc.yaml
      apiVersion: networking.istio.io/v1alpha3
      kind: ServiceEntry
      metadata:
        name: external-nacos-svc
      spec:
        hosts:
        - "NACOS_SERVER_HOST"  ## 需要替换为你的Nacos Server HOST,例如"mse-xxx-p.nacos-ans.mse.aliyuncs.com"。
        location: MESH_EXTERNAL
        ports:
        - number: 8848
          name: http
        resolution: DNS
      其中8848为Nacos的默认端口,如果您是自建的Nacos Server且对端口有修改,则number参数也需要对应修改。
    2. 执行以下命令,创建ServiceEntry。
      kubectl apply -f external-nacos-svc.yaml
  3. 创建EnvoyFilter。
    1. 使用以下内容,创建external-envoyfilter.yaml
      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.63.81&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. 执行以下命令,创建EnvoyFilter。
      kubectl apply -f external-envoyfilter.yaml

步骤二:在ACK创建Spring Cloud服务

说明
  • 因为需要拦截注册流程,EnvoyFilter需要先于业务工作负载Deployment之前创建。若某些业务Deployment先于EnvoyFilter创建,您需要滚动更新该业务Deployment。
  • 业务服务需要创建Kubernetes Service资源,并且需要有Cluster IP。
  1. 通过kubectl工具连接集群
  2. 执行以下命令,创建Spring Cloud服务。
    export NACOS_ADDRESS=xxxx # xxxx为MSE或自建的Nacos地址,建议使用VPC内网地址。
    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. 执行以下命令,查看Spring Cloud服务。
     kubectl get pods

    预期输出:

    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

步骤三:创建网关规则和虚拟服务

  1. 通过kubectl连接ASM实例
  2. 创建网关规则。
    1. 使用以下内容,创建test-gateway.yaml
      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. 执行以下命令,创建网关规则。
      kubectl apply -f test-gateway.yaml
  3. 创建虚拟服务。
    1. 使用以下内容,创建consumer.yaml
      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. 执行以下命令,创建虚拟服务。
      kubectl apply -f consumer.yaml

步骤四:验证ASM是否管理Spring Cloud服务成功

  1. 查看Ingress Gateway的IP地址。
    1. 登录ASM控制台
    2. 在左侧导航栏,选择服务网格 > 网格管理
    3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理
    4. 在网格管理页面左侧导航栏单击ASM网关
      在ASM网关页面查看Ingress Gateway Kubernetes服务列下的IP地址。
  2. 执行以下命令,通过Ingress Gateway向Spring Cloud Consumer服务发起请求。
    curl <Ingress Gateway的IP地址>/echo/world
    预期输出:
    Hello Nacos Discovery From v1world
    Hello Nacos Discovery From v2world
    Hello Nacos Discovery From v1world
    Hello Nacos Discovery From v2world
    可以看到Provider默认在v1、v2版本间轮询访问。
  3. 创建目标规则和虚拟服务。
    1. 使用以下内容,创建service-provider.yaml
      ---
      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. 执行以下命令,创建目标规则。
      kubectl apply -f service-provider.yaml
    3. 使用以下内容,创建service-provider1.yaml
      以下虚拟服务定义了/echo/hello的请求将被路由到Provider v1版本,其他请求将被路由到Provider 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. 执行以下命令,创建虚拟服务。
      kubectl apply -f service-provider1.yaml
  4. 执行以下命令,向Spring Cloud Consumer服务发起请求。
    curl <Ingress Gateway的IP地址>/echo/hello
    预期输出:
    Hello Nacos Discovery From v1hello
    Hello Nacos Discovery From v1hello
    可以看到/echo/hello请求都被路由到Provider v1版本,其他请求则会路由到Provider v2版本。说明Spring Cloud 流量被Istio接管,并可以支持使用Istio方式配置相关路由规则,管理Spring Cloud服务成功。

FAQ

为什么我参照该文档,自己部署的SpringCloud业务服务不生效呢?
  1. 请检查是否开启了针对Nacos端口或者IP的流量拦截
  2. 因为需要拦截注册流程,EnvoyFilter需要先于业务工作负载Deployment之前创建。若某些业务Deployment先于EnvoyFilter创建,您需要滚动更新该业务Deployment。
  3. 请检查业务服务是否创建了Kubernetes Service资源,并且Type类型为Cluster IP。
  4. Nacos Client SDK版本需低于2.0版本,因为Nacos 2.0+ Client SDK 采用GRPC和服务端建立,当前方案不适用。