本篇介绍如何将一个 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 改造的更好方式。