管理Spring Cloud服务

您可以将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

支持,ASM版本需≥1.13.4.53

Spring Cloud Consul

Consul

支持,ASM版本需≥1.13.4.53

Spring Cloud Zookeeper

Zookeeper

支持,ASM版本需≥1.13.4.53

Demo介绍

本文以Spring Cloud+Nacos为例进行说明,Demo示例代码可以通过此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

若Consumer、Provider不开启Mesh能力,即业务POD不注入Sidecar的情况下,请求可以正常访问,但缺少Istio提供的相关服务治理能力,本文通过以下步骤,验证ASM管理Spring Cloud服务是否成功。

步骤一:ASM控制面开启SpringCloud能力支持

方式一:适用于所有SpringCloud版本和注册中心

说明

ASM实例需为1.13.4.32及以上版本。

  1. 使用kubectl连接到控制面集群。具体操作,请参见通过控制面kubectl访问Istio资源

  2. 在ASM集群下创建EnvoyFilter。

    1. 使用以下内容,创建any-spring-cloud-support.yaml

      apiVersion: networking.istio.io/v1alpha3
      kind: EnvoyFilter
      metadata:
        labels:
          provider: "asm"
          asm-system: "true"
        name: any-spring-cloud-support
        namespace: istio-system
      spec:
        configPatches:
        - applyTo: HTTP_FILTER
          match:
            proxy:
              proxyVersion: "^1.*"
            context: SIDECAR_OUTBOUND
            listener:
              portNumber: 8070
              filterChain:
                filter:
                  name: "envoy.filters.network.http_connection_manager"
                  subFilter:
                    name: "envoy.filters.http.router"
          patch:
            operation: INSERT_BEFORE
            value: # reverse_dns filter specification
             name: com.aliyun.reverse_dns
             typed_config:
               "@type": "type.googleapis.com/udpa.type.v1.TypedStruct"
               type_url: type.googleapis.com/envoy.config.filter.reverse_dns.v3alpha.CommonConfig
               value:
                 pod_cidrs:
                 - "10.0.128.0/18"

      YAML中的参数请您根据实际业务进行修改,部分参数说明如下:

      • portNumber :SpringCloud服务的端口。若端口不统一,可以删除该参数不进行配置;若端口可以收敛,可以配置多个该EnvoyFilter(每个EnvoyFilter绑定一个具体portNumber。)

      • pod_cidrs:ACK或ACK Serverless集群的Pod CIDR。您可以登录容器服务管理控制台,在集群信息页面的集群资源页签,查看虚拟专有网络 VPC下虚拟交换机对应的CIDR进行配置。

    2. 执行以下命令,对目标服务开启com.aliyun.reverse_dns filter

      kubectl apply -f any-spring-cloud-support.yaml

方式二:仅适用于Nacos注册中心

  1. 使用kubectl连接到控制面集群。具体操作,请参见通过控制面kubectl访问Istio资源

  2. 创建ServiceEntry。

    1. 使用以下内容,创建external-nacos-svc.yaml

      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

      上述YAML中,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=10.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连接到数据面集群。具体操作,请参见获取集群KubeConfig并通过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 -

    关于创建Nacos引擎的具体操作,请参见创建Nacos引擎

  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. 创建网关规则。

    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. 使用ASM的kubeconfig,执行以下命令,创建网关规则。

      kubectl apply -f test-gateway.yaml
  2. 创建虚拟服务。

    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. 网格管理页面,单击目标实例名称,然后在左侧导航栏,选择ASM网关 > 入口网关

    3. 入口网关页面,查看Ingress Gateway的服务地址

  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的流量拦截。

    • 若采用reverse_dns方式,需要拦截Pod IP。

    • 若采用Lua脚本方式,需要拦截Nacos Server IP和Cluster IP。

  2. 因为需要拦截注册流程,EnvoyFilter需要先于业务工作负载Deployment之前创建。若某些业务Deployment先于EnvoyFilter创建,您需要滚动更新该业务Deployment。

  3. 检查业务服务是否创建了Kubernetes Service资源,并且Type类型为Cluster IP。

  4. 查看ASM控制面开启SpringCloud能力的方式。

    若采用方式二,Nacos Client SDK版本需低于2.0。Nacos 2.0版本以上的Client SDK采用GRPC和服务端建立连接,方式二不适用。方式一对Nacos版本没有依赖,支持任意Nacos版本。

  5. 检查相关服务的Sidecar版本,若Sidecar镜像版本低于v1.13.4.32,可能存在仅升级了ASM实例控制面,未升级数据面的情况,需要将对应服务的Deployment进行滚动更新。