如何将一个 Dubbo 项目改造成一个 Service Mesh 项目?

2020-07-01 17:03:20 浏览数 (1)

本篇介绍如何将一个 Dubbo 项目改造成一个 SpringBoot K8S Istio 项目的全过程,实现了在不改变 Dubbo 项目整体代码结构的基础上,向 Service Mesh 云原生项目的蜕变。

一. 痛点问题:

  • 服务名调用问题:Istio 通过对 K8S 服务名调用的拦截,实现了无侵入式的流量治理功能,因此 Isito 要求不同服务间的调用必须以服务名的方式进行。现有项目是否为服务名调用,成了不同类型的项目向 Istio 改造的最大障碍之一,例如 Dubbo 项目就不是服务名调用,而是 Interface 调用,这是第一个痛点问题。
  • 注册中心问题:由于 Istio 目前只支持 K8S etcd、Consul 两种服务注册中心,其他注册中心(例如:Zookeeper)的对接以及跟 Istio 配置文件的集成及 xDS 协议数据的下发,成为了第二个痛点问题。
  • 私有协议问题:由于 Istio 目前只支持 http、gRPC、tcp 三种协议,私有协议适配难度较高,即使在新版 Envoy 已经支持了 Dubbo 协议的情况下,还是需要通过 EnvoyFilter 下发专属 xDS 协议数据来支持 Dubbo 的服务调用及流量治理,这是第三个痛点问题。

二. 改造思路:

  • 由于改造 Dubbo SDK、Isito控制面、Envoy 数据面,让 Dubbo 去适配 Service Mesh 的技术难度较大,而且即使改造成功也需要通过 EnvoyFilter 下发专属 xDS 协议数据的形式来支持 Dubbo 服务间调用的流量治理,使得这种方式与原生 Istio 的使用方式差距较大。
  • 所以我们选择了一条将 Dubbo 项目改造成 SpringBoot K8S Istio 项目的更简单的路,充分利用现有 Dubbo 项目的代码结构,将代码修改量降到一个可控的范围内。
  • 由于 Dubbo 项目 facade 模块的作用与 Spring Cloud Feign 模块的作用十分相似(模块内都是一些 interface,需要服务端 xxxServiceImpl 去实现各个 interface,消费端通过 @Resource 注解的方式引入 interface 并直接调用),使得 Dubbo 最复杂的服务间调用方式有了解决的方案。
  • 此次改造只是利用了 Dubbo 项目的代码结构,Dubbo 原有的注册中心、Dubbo 协议等功能全部都会被去掉,也就是改造后的项目跟 Dubbo 已经没有任何关系了,所以注册中心、Dubbo私有协议这两个痛点问题也就不存在了。
  • K8S 会接管服务注册发现、服务编排等工作,Istio 会接管服务治理、调用链监控、服务安全等工作,改造后的项目是一个标准的 Service Mesh 项目。

三. Dubbo 项目结构:

现有 Dubbo 项目 xyz-dubbo,包括以下三个模块:

  • dubbo-facade: dubbo 接口定义
  • dubbo-provider: 服务提供方
  • dubbo-consumer: 服务调用方

四. 具体改造步骤:

4.1 Dubbo 项目根 POM 改造:

  • 根 pom.xml 引入 SpringBoot parent,增加 spring-cloud-dependencies import 引用。
  • 删除所有 dubbo 相关引用。
  • 虽然 pom 文件改动很大,但属于一次性改动,改造工作量较小。

改造前的 Dubbo 项目根 pom.xml:

代码语言:txt复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>io.xyzdemo.dubbo</groupId>
    <artifactId>xyz-dubbo</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>xyz-dubbo</name>

    <modules>
        <module>dubbo-consumer</module>
        <module>dubbo-provider</module>
        <module>dubbo-facade</module>
    </modules>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>io.xyzdemo.dubbo</groupId>
                <artifactId>dubbo-facade</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-web</artifactId>
                <version>4.2.5.RELEASE</version>
            </dependency>
            <dependency>
                <groupId>org.apache.dubbo</groupId>
                <artifactId>dubbo</artifactId>
                <version>2.7.6</version>
            </dependency>
            <dependency>
                <groupId>com.ecwid.consul</groupId>
                <artifactId>consul-api</artifactId>
                <version>1.4.5</version>
            </dependency>
            <dependency>
                <groupId>com.orbitz.consul</groupId>
                <artifactId>consul-client</artifactId>
                <version>1.4.2</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

改造后的 SpringBoot 项目根 pom.xml:

代码语言:txt复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>io.xyzdemo.dubbo</groupId>
    <artifactId>xyz-dubbo</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <name>xyz-dubbo</name>

    <modules>
        <module>dubbo-consumer</module>
        <module>dubbo-provider</module>
        <module>dubbo-facade</module>
    </modules>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>io.xyzdemo.dubbo</groupId>
                <artifactId>dubbo-facade</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

4.2 dubbo-facade 项目改造:

  • pom.xml 增加 spring-cloud-starter-openfeign 引用。
  • 删除所有 Dubbo 相关引用、Dubbo 相关配置文件。
  • Dubbo 原有 facade 接口是标准的 JAVA 接口定义,与 Feign Restful 接口定义十分类似。这里可以在原有的 facade 接口基础上增加 @FeignClient、@RequestMapping 等注解,将一个普通的 facade 接口改造成一个 Feign Restful 接口,后续会使用 Feign 这个 Restful 框架来处理服务间调用等问题。
  • 由于 Feign 本身是自带了 Ribbon 负载均衡,服务访问者经过负载均衡后会找到服务提供者的一个 IP Port 进行调用,这与 K8S Service 要求的服务名调用的方式相冲突,所以必须想办法去掉 Feign 自带的负载均衡。好在 @FeignClient 可以手工指定一个固定的调用地址,这里可以把这个地址设置成 K8S Service 的 name 名称,从而实现了通过 Feign 对 K8S Service 服务名调用的能力。此部分需要每个 facade 接口增加注解一次,改造工作量相对可控。
  • 由于 Feign 要求接口使用 Restful 格式,所以接口中的每个抽象方法都必须添加 @RequestMapping、@GetMapping、@PostMapping 等注解暴露成一个 Restful 资源地址。此部分改造涉及到每个 facade 接口的每个抽象方法,是整个方案里改动量最大的一部分。
  • 此部分整体改造工作量取决于原有的 Dubbo 项目包含多少个 facade 接口,以及每个 facade 包含多少个抽象方法。

改造前的 Dubbo facade 接口示例:

代码语言:txt复制
public interface HelloService {
	String sayHello(String name);
}

改造后的 Feign Restful 接口示例:

代码语言:txt复制
@FeignClient(name = "dubbo-provider", url = "http://dubbo-provider:8001")
public interface HelloService {
	@GetMapping(value = "/apis/hello/{name}")
	String sayHello(@PathVariable String name);
}

4.3 dubbo-provider 项目改造

  • pom.xml 增加 spring-boot-starter-web、spring-cloud-starter-openfeign 等引用,同时增加 SpringBoot mainClass 标准启动项配置。
  • 删除所有 Dubbo 相关引用、Dubbo 相关配置文件。
  • 增加 SpringBoot 启动类,增加 @SpringBootApplication、@EnableFeignClients 两个注解,配置 dubbo-provider 服务端口号。
  • xxxServiceImpl 服务实现类上增加 @RestController 注解,提供 consumer Restful 访问的能力。 这个需要每个服务实现类都加上 @RestController 注解,不要遗漏。
  • 此部分大都属于一次性改动,改造工作量相对可控。

改造前的 dubbo-provider 模块 pom.xml 配置:

代码语言:txt复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <artifactId>xyz-dubbo</artifactId>
        <groupId>io.xyzdemo.dubbo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>dubbo-provider</artifactId>
    <packaging>war</packaging>
    <name>dubbo-provider</name>

    <dependencies>
        <dependency>
            <groupId>io.xyzdemo.dubbo</groupId>
            <artifactId>dubbo-facade</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.dubbo</groupId>
            <artifactId>dubbo</artifactId>
        </dependency>
        <dependency>
            <groupId>com.ecwid.consul</groupId>
            <artifactId>consul-api</artifactId>
        </dependency>
        <dependency>
            <groupId>com.orbitz.consul</groupId>
            <artifactId>consul-client</artifactId>
        </dependency>
    </dependencies>
</project>

改造后的 dubbo-provider 模块 pom.xml 配置:

代码语言:txt复制
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <artifactId>xyz-dubbo</artifactId>
        <groupId>io.xyzdemo.dubbo</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>dubbo-provider</artifactId>
    <name>dubbo-provider</name>

    <dependencies>
        <dependency>
            <groupId>io.xyzdemo.dubbo</groupId>
            <artifactId>dubbo-facade</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>

    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <mainClass>io.xyzdemo.dubbo.provider.ProviderApplication</mainClass>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

改造后的 HelloServiceImpl.java 代码示例(改造前只是缺少 @RestController 注解,其他代码完全一致):

代码语言:txt复制
@RestController
public class HelloServiceImpl implements HelloService {
	public String sayHello(String name) {
		return String.format("hello %s! podIP is %s!", name, CommonUtils.getLocalIP());
	}
}

4.4 dubbo-consumer 项目改造

  • pom.xml 增加 spring-boot-starter-web、spring-cloud-starter-openfeign 等引用,同时增加 SpringBoot mainClass 标准启动项配置。
  • 删除所有 Dubbo 相关引用、Dubbo 相关配置文件。
  • 增加 SpringBoot 启动类,增加 @SpringBootApplication、@EnableFeignClients(需要配置 basePackages 扫描包路径) 两个注解,并配置 dubbo-consumer 服务端口号。
  • 此部分大都属于一次性改动,改造工作量相对可控。

由于dubbo-consumer 项目改造与dubbo-provider 改造极其相似,这里不再贴出代码示例。

五. 将改造后的项目部署到 K8S Istio:

将下面三个配置文件通过 kubectl 在 K8S Istio 的集群中执行,即可完成改造后的项目在 K8S Istio 集群中的部署。

5.1 创建 dubbo-provider K8S Deployment、K8S Service(ClusterIP),提供集群内访问服务

dubbo-provider-service.yml 配置文件示例:

代码语言:txt复制
apiVersion: v1
kind: Service
metadata:
  name: dubbo-provider
spec:
  type: ClusterIP
  ports:
    - name: http
      port: 8001
  selector:
    k8s-app: dubbo-provider
    qcloud-app: dubbo-provider

---

apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: dubbo-provider-deployment
  labels:
    k8s-app: dubbo-provider
    qcloud-app: dubbo-provider
spec:
  replicas: 2 # 副本数量
  selector:
    matchLabels:
      k8s-app: dubbo-provider
      qcloud-app: dubbo-provider
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        k8s-app: dubbo-provider
        qcloud-app: dubbo-provider
    spec:
      containers:
        - name: dubbo-provider
          image: ccr.ccs.tencentyun.com/axlyzhang-images/dubbo-provider:v1.1 # 镜像地址
          env:
            - name: PATH
              value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
            - name: LANG
              value: C.UTF-8
            - name: JAVA_HOME
              value: /usr/lib/jvm/java-8-openjdk-amd64
            - name: JAVA_VERSION
              value: 8u111
          resources:
            limits:
              cpu: 500m
              memory: 1Gi
            requests:
              cpu: 250m
              memory: 256Mi
          securityContext:
            privileged: false
            procMount: Default
      imagePullSecrets:
        - name: qcloudregistrykey
        - name: tencenthubkey

5.2 创建 dubbo-consumer K8S Deployment、K8S Service(ClusterIP),提供集群内访问服务:

dubbo-consumer-service.yml 配置文件示例:

代码语言:txt复制
apiVersion: v1
kind: Service
metadata:
  name: dubbo-consumer
spec:
  type: ClusterIP
  ports:
    - name: http
      port: 8002
  selector:
    k8s-app: dubbo-consumer
    qcloud-app: dubbo-consumer

---

apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: dubbo-consumer-deployment
  labels:
    k8s-app: dubbo-consumer
    qcloud-app: dubbo-consumer
spec:
  replicas: 2 # 副本数量
  selector:
    matchLabels:
      k8s-app: dubbo-consumer
      qcloud-app: dubbo-consumer
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        k8s-app: dubbo-consumer
        qcloud-app: dubbo-consumer
    spec:
      containers:
        - name: dubbo-consumer
          image: ccr.ccs.tencentyun.com/axlyzhang-images/dubbo-consumer:v1.0 # 镜像地址
          env:
            - name: PATH
              value: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
            - name: LANG
              value: C.UTF-8
            - name: JAVA_HOME
              value: /usr/lib/jvm/java-8-openjdk-amd64
            - name: JAVA_VERSION
              value: 8u111
          resources:
            limits:
              cpu: 500m
              memory: 1Gi
            requests:
              cpu: 250m
              memory: 256Mi
          securityContext:
            privileged: false
            procMount: Default
      imagePullSecrets:
        - name: qcloudregistrykey
        - name: tencenthubkey

5.3 创建 istio ingressgateway、VirtualService,提供公网访问入口,并进行流量治理测试:

代码语言:txt复制
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: dubbo-consumer-gateway
spec:
  selector:
    istio: ingressgateway # use istio default controller
  servers:
    - port:
        number: 80
        name: http
        protocol: HTTP
      hosts:
        - "*"

---

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: consumer-vs
spec:
  hosts:
    - "*"
  gateways:
    - dubbo-consumer-gateway
  http:
    - match:
        - uri:
            prefix: /hello/error
      route:
        - destination:
            host: dubbo-consumer
            port:
              number: 8003   -- 错误的服务端口
    - route:
        - destination:
            host: dubbo-consumer
            port:
              number: 8002   -- 正确的服务端口

六. 改造成果验证及总结:

  • http://111.230.187.114/hello/world (virtualservice 配置路由到一个正确的服务端口 8002,正常访问)
  • http://111.230.187.114/hello/error (virtualservice 配置路由到一个错误的服务端口 8003,访问出错,证明 VirtualService 已经起到了服务治理的作用,项目改造验证成功)

经过上面几步操作,我们成功的将一个 Dubbo 项目改造成了一个 Service Mesh 项目,并在 K8S Istio 集群中部署成功、测试通过。

当然,真实业务系统中的架构复杂度是远高于这个 Demo 的,实际改造的难度要比改造这个 Demo 大得多。这篇文章只是抱砖引玉,希望可以跟大家继续探讨 Dubbo 向 Service Mesh 改造的更好方式。

0 人点赞