背景
需求场景是,将 dubbo2 框架的应用迁移到 istio 网格中,dubbo2 使用的注册中心可能有 zookeeper、nacos、etcd,这里对 dubbo 接入网格的几种方式做个对比。
dubbo 接入 mesh 方案
dubbo 接入 mesh,这里要区分 dubbo 和 dubbo3,下面是目前的方案汇总:
- dubbo2:开源社区方案,如 Aeraki。
- dubbo3:需开启新特性(新的地址发现模型、基于 HTTP/2 的 Triple 协议、统一的路由规则),可参考 升级到 Dubbo3 来迁移 dubbo3,支持两种部署方式
- Sidecar 模式:只能用 Triple 协议,Dubbo ThinSDK 将只提供面向业务应用的编程 API、RPC 传输通信能力,其余服务治理 包括地址发现、负载均衡、路由寻址等都统一下沉到 Sidecar。
- Proxyless 模式:同时支持 Dubbo2、Triple 协议,但只支持应用级服务发现的地址模型,Dubbo3 SDK 直接实现 xDS 协议解析,Dubbo 进程可以与控制面(Control Plane)直接通信,进而实现控制面对流量管控、服务治理、可观测性、安全等的统一管控
这几种方案的对比如下:
方案 |
dubbo 版本 |
支持协议 |
支持注册中心 |
调用方式 |
优点 |
缺点| |
社区项目 aeraki |
dubbo2 |
dubbo、 thrift 和其他私有协议 |
zookeeper、nacos、etcd 等 |
interface 级 |
兼容 dubbo3、支持协议较多 |
部分功能可能受限,同时会有一定的性能和容量瓶颈 |
dubbo3 Sidecar |
dubbo3 |
triple |
只支持 k8s |
应用级,providedBy 参数指定 |
平滑升级、多语言、业务侵入小 |
不支持自定义注册中心、sidecar 的性能损耗、部署环境受限 |
dubbo3 proxyless |
dubbo3 |
triple、dubbo2 |
只支持 k8s |
应用级,providedBy 参数指定 |
没有 proxy 中转的损耗、架构简单、便于遗留系统的平滑迁移 |
不支持自定义注册中心 |
文章最后针对一个特殊场景,只需要支持 dubbo2 协议和第三方注册中心,并且使用 dubbo 自身的服务治理能力的场景,提出一个简化方案,不引入 Aeraki 组件和 MetaProtocol Proxy 代理,只使用 dubbo2istio,手动生成 EnvoyFilter 来支持 dubbo 协议。
istio 支持第三方注册中心原理
istio 1.8 不再通过 in-tree 方式支持外部注册中心,istio 1.9 改为 MCP-over-XDS 的方式支持外部注册中心。其支持的流程为:
- 为第三方注册中心编写 MCP-over-XDS server。
- MCP-over-XDS server 获取第三方注册中心的数据,并转换为 ServiceEntry。
- 推送给 pilot(如 nacos,pilot 启动时需设置 configSource 参数) 或者写到 ServiceEntry CR 中(如 dubbo2istio)。
- pilot 接受到数据后,会对比内存中的数据,保证数据一致。
通过这种方式,原有 SDK 注册和调用方式不需要做更改,能更加快速的迁移原有服务。因为 nacos 已经支持了 MCP-over-XDS,这里看下 nacos 支持时,是怎么配置的。nacos 配置:
1
|
nacos.istio.mcp.server.enabled=true # nacos 开启 istio 同步
|
istio 配置:
1
2
3
4
5
6
7
8
|
rootNamespace: istio-system
trustDomain: cluster.local
configSources: # 配置 xds 地址为 nacos
- address: xds://xxx:18848
# 开启捕获 DNS 请求,解析自定义的 ServiceEntry,参考 [dns 代理](https://preliminary.istio.io/latest/zh/docs/ops/configuration/traffic-management/dns-proxy/)
proxyMetadata: # 为代理提供的额外环境变量,会写到代理的启动配置中
ISTIO_META_DNS_AUTO_ALLOCATE: \"true\" # 为每个 ServiceEntry 自动分配一个不同的独立地址
ISTIO_META_DNS_CAPTURE: \"true\" # 开启 dns 代理,应用程序的 DNS 请求将会被重定向到 Sidecar
|
dubbo-demo 代码解读
下面示例的代码来自 aeraki-dubbo-demo,因为之前不了解 java 和 dubbo,这里整理下其中的一些知识。
Maven 和 POM 说明
Maven 是专门为 Java 项目打造的管理和构建工具,它的主要功能有:
- 提供了一套标准化的项目结构
- 提供了一套标准化的构建流程(编译,测试,打包,发布……)
- 提供了一套依赖管理机制
项目目录结构中 pom.xml 是 Maven 的配置文件,POM 是项目对象模型(Project Object Model),用于管理项目的依赖关系、构建设置、插件配置等。执行任务或目标时,Maven 会在当前目录中查找 POM。它读取 POM,获取所需的配置信息,然后执行目标。POM 中可以指定以下配置:
- 项目依赖
- 插件
- 执行目标
- 项目构建 profile
- 项目版本
- 项目开发者列表
- 相关邮件列表信息
以下是一个简单的 pom.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
<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">
<!-- 项目分组,公司或者组织的唯一标志 -->
<groupId>org.apache.dubbo</groupId>
<!-- 版本号 -->
<version>1.0-SNAPSHOT</version>
<!-- 模型版本 -->
<modelVersion>4.0.0</modelVersion>
<!-- 项目的唯一 ID -->
<artifactId>dubbo-samples-basic</artifactId>
<!-- 属性,其他地方使用${属性名}的方式引用该属性 -->
<properties>
<source.level>1.8</source.level>
<target.level>1.8</target.level>
<main-class>org.apache.dubbo.samples.basic.BasicProvider</main-class>
</properties>
<!-- 依赖 -->
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
</dependencies>
</project>
|
Maven 主要会使用到以下命令:
- mvn package:用于创建项目的可执行 JAR 或 WAR 包,通常情况下,生成的文件名遵循格式“-.”,比如上面例子中生成的名称是 dubbo-samples-basic-1.0-SNAPSHOT.jar
- mvn install: 把打好的包放入本地仓库 (~/.m2/repository)
- mvn clean: 清除各个模块 target 目录及里面的内容
- mvn deploy: 部署,把包发布到远程仓库
其中 mvn package 打包后生成的 jar 包,可以用“jar -xf xx.jar”解压,我们将上面生成的 jar 解压,目录如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
.
├── LICENSE.txt
├── META-INF
├── MapValue.proto
├── ThrowablePB.proto
├── about.html
├── about_files
│ └── LICENSE_CDDL.txt
├── afu
│ ├── org
│ └── plume
├── android
│ └── annotation
├── auth.proto
├── com
│ ├── alibaba
│ ├── embedded
│ ├── fasterxml
│ ├── google
│ └── sun
├── election.proto
├── google
│ ├── api
│ ├── cloud
│ ├── geo
│ ├── logging
│ ├── longrunning
│ ├── protobuf
│ ├── rpc
│ └── type
├── grpc
│ └── lb
├── io
│ ├── etcd
│ ├── grpc
│ ├── netty
│ ├── perfmark
│ └── prometheus
├── javassist
├── javax
├── jetty-dir.css
├── jline
├── kv.proto
├── lock.proto
├── log4j.properties
├── nacos-log4j2.xml
├── nacos-logback.xml
├── nacos-version.txt
├── net
│ ├── jcip
│ └── jodah
├── org
│ ├── aopalliance
│ ├── apache # class 位置
│ ├── checkerframework
│ ├── codehaus
│ ├── eclipse
│ ├── jboss
│ ├── reflections
│ ├── scannotation
│ ├── slf4j
│ ├── springframework
│ └── yaml
├── plugin.properties
├── rpc.proto
├── security
│ └── serialize.blockedlist
└── spring # 应用的 xml 配置
├── dubbo-demo-consumer.xml
├── dubbo-demo-provider.xml
└── dubbo-second-provider.xml
|
在运行的时候,直接执行以下类似的命令即可:
1
|
java -cp ./dubbo-samples-basic-1.0-SNAPSHOT.jar org.apache.dubbo.samples.basic.BasicProvider
|
代码解读
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public class BasicProvider {
public static void main(String[] args) throws Exception {
//new EmbeddedZooKeeper(2181, false).start();
// wait for embedded zookeeper start completely.
//Thread.sleep(1000);
// 使用到 xml 文件来描述 Spring 中 Bean,在后面可获取到相应的 Bean
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-demo-provider.xml");
context.start();
System.out.println("dubbo service started");
new CountDownLatch(1).await();
}
}
|
查看其 bean 配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://dubbo.apache.org/schema/dubbo http://dubbo.apache.org/schema/dubbo/dubbo.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 激活${...}占位符的替换,根据指定的属性文件解析 -->
<context:property-placeholder/>
<!-- 当前应用名 -->
<dubbo:application name="dubbo-sample-provider">
<dubbo:parameter key="aeraki_meta_app_namespace" value="${AERAKI_META_APP_NAMESPACE}" />
<dubbo:parameter key="aeraki_meta_app_service_account" value="${AERAKI_META_APP_SERVICE_ACCOUNT}" />
<dubbo:parameter key="aeraki_meta_app_version" value="${AERAKI_META_APP_VERSION}" />
<dubbo:parameter key="aeraki_meta_workload_selector" value="${AERAKI_META_WORKLOAD_SELECTOR}" />
<dubbo:parameter key="aeraki_meta_locality" value="${AERAKI_META_LOCALITY}" />
<dubbo:parameter key="service_group" value="${SERVICE_GROUP}" />
</dubbo:application>
<!-- 注册中心配置,address:地址 -->
<!-- register:是否向此注册中心注册服务,如果设为 false,将只订阅,不注册 -->
<!-- subscribe:是否向此注册中心订阅服务,如果设为 false,将只注册,不订阅 -->
<dubbo:registry address="${REGISTRY_ADDR}" register="${REGISTER}" subscribe="false" timeout="60000">
<!--
<dubbo:parameter key="group" value="test" />
<dubbo:parameter key="namespace" value="test" />
-->
</dubbo:registry>
<!-- bean id 和类的全限定名 -->
<bean id="demoProvider" class="org.apache.dubbo.samples.basic.impl.DemoProviderImpl"/>
<!-- 服务提供者暴露服务配置,interface:服务接口名,ref:服务对象实现引用 -->
<dubbo:service interface="org.apache.dubbo.samples.basic.api.DemoService" ref="demoProvider"/>
<dubbo:service interface="org.apache.dubbo.samples.basic.api.TestService" ref="demoProvider" />
<!-- group:接口有多个实现用分组区分,version:服务版本 -->
<dubbo:service interface="org.apache.dubbo.samples.basic.api.ComplexService" ref="demoProvider" group="test" version="1.0.0"/>
<dubbo:service interface="org.apache.dubbo.samples.basic.api.ComplexService" ref="demoProvider" group="product" version="2.0.0"/>
<!-- 服务消费者引用服务配置,id:服务引用 BeanId -->
<dubbo:reference id="secondService" check="true" interface="org.apache.dubbo.samples.basic.api.SecondService" url="dubbo://org.apache.dubbo.samples.basic.api.secondservice:20880" timeout="3000"/>
</beans>
|
dubbo2 接入 Aeraki Mesh
这里参考 Aeraki 官网实例,并将其安装步骤做了简化,下面说明了 dubbo 在 Aeraki Mesh 中,如何接入不同的注册中心 zookeeper、nacos、etcd。
安装 istio 和 Aeraki
istio 安装配置文件 istio_config.yaml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
profile: default
values:
global:
logging:
level: default:debug
meshConfig:
enableTracing: true # 是否开启 trace,需要设置 trace 收集配置
accessLogFile: /dev/stdout
accessLogFormat: "[%START_TIME%] %REQ(X-META-PROTOCOL-APPLICATION-PROTOCOL)%
%RESPONSE_CODE% %RESPONSE_CODE_DETAILS% %CONNECTION_TERMINATION_DETAILS% \"%UPSTREAM_TRANSPORT_FAILURE_REASON%\"
%BYTES_RECEIVED% %BYTES_SENT% %DURATION% \"%REQ(X-FORWARDED-FOR)%\" \"%REQ(X-REQUEST-ID)%\" %UPSTREAM_CLUSTER%
%UPSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_REMOTE_ADDRESS% %ROUTE_NAME%\n"
defaultConfig:
holdApplicationUntilProxyStarts: true # 添加一个 hook 来延迟应用启动,等 pod 代理准备好接收流量后
proxyMetadata:
ISTIO_META_DNS_CAPTURE: "true" # 开启 dns 代理
proxyStatsMatcher: # 代理统计匹配器
inclusionPrefixes:
- thrift
- dubbo
- kafka
- meta_protocol
inclusionRegexps:
- .*dubbo.*
- .*thrift.*
- .*kafka.*
- .*zookeeper.*
- .*meta_protocol.*
tracing: # tracing 配置
sampling: 100
zipkin:
address: zipkin.istio-system:9411
components: # 参考 [IstioComponentSetSpec](https://istio.io/latest/docs/reference/config/istio.operator.v1alpha1/#IstioComponentSetSpec)
pilot:
hub: istio # 镜像
tag: 1.14.5 # tag
|
1
2
3
4
5
6
7
8
9
10
|
cd aeraki
vim istio_config.yaml # 编辑 istio 配置
istioctl install -y -f istio_config.yaml # 安装 istio
make install # 安装 aeraki
# 下载代码和创建命名空间
git clone https://github.com/aeraki-mesh/dubbo2istio.git
cd dubbo2istio
kubectl create ns meta-dubbo
kubectl label namespace meta-dubbo istio-injection=enabled --overwrite=true
|
接入 zookeeper 实例
执行命令:
1
|
kubectl apply -f demo/k8s/zk -n meta-dubbo
|
等待服务启动后查看部署情况:
1
2
3
4
5
6
7
8
9
10
|
root@dev:~/zt/istio/dubbo2istio# kubens meta-dubbo
✔ Active namespace is "meta-dubbo"
root@dev:~/zt/istio/dubbo2istio# kubectl get pods
NAME READY STATUS RESTARTS AGE
dubbo2istio-5c49bc5f8d-4wns5 1/1 Running 0 79m
dubbo-sample-consumer-6749fc7df9-lc7hj 2/2 Running 0 79m
dubbo-sample-provider-v2-566c4fd8bb-9w6mg 2/2 Running 0 79m
dubbo-sample-provider-v1-86d5d5cc7c-vvg5d 2/2 Running 0 79m
zookeeper-cc7f59dd6-tlnpg 1/1 Running 0 79m
dubbo-sample-second-provider-7958bfbbdc-qnz7b 2/2 Running 0 79m
|
查看 consumer 日志:
1
2
3
4
5
6
7
|
Periodically call dubbo server
Start a http server for e2e test
...
Hello Aeraki, response from dubbo-sample-provider-v1-86d5d5cc7c-vvg5d/10.42.0.27
Hello Aeraki, response from dubbo-sample-provider-v2-566c4fd8bb-9w6mg/10.42.0.30
Hello Aeraki, response from dubbo-sample-provider-v1-86d5d5cc7c-vvg5d/10.42.0.27
Hello Aeraki, response from dubbo-sample-provider-v2-566c4fd8bb-9w6mg/10.42.0.30
|
接入 nacos 实例
执行命令:
1
2
3
|
# 先卸载上面的 zookeeper 部署
kubectl delete -f demo/k8s/zk -n meta-dubbo
kubectl apply -f demo/k8s/nacos -n meta-dubbo
|
同样查看 consumer 日志:
1
2
3
4
5
6
7
|
Periodically call dubbo server
Start a http server for e2e test
...
Hello Aeraki, response from dubbo-sample-provider-v1-76c9c7f88c-6mbbk/10.42.0.40
Hello Aeraki, response from dubbo-sample-provider-v2-5bb778d49b-ln8d4/10.42.0.39
Hello Aeraki, response from dubbo-sample-provider-v1-76c9c7f88c-6mbbk/10.42.0.40
Hello Aeraki, response from dubbo-sample-provider-v2-5bb778d49b-ln8d4/10.42.0.39
|
接入 etcd 实例
执行命令:
1
2
3
|
# 先卸载上面的 nacos 部署
kubectl delete -f demo/k8s/nacos -n meta-dubbo
kubectl apply -f demo/k8s/etcd -n meta-dubbo
|
同样查看 consumer 日志:
1
2
3
4
5
6
7
|
Periodically call dubbo server
Start a http server for e2e test
...
Hello Aeraki, response from dubbo-sample-provider-v1-5d9496c8bc-zdhdk/10.42.0.51
Hello Aeraki, response from dubbo-sample-provider-v2-59c8778bb-8hbmk/10.42.0.54
Hello Aeraki, response from dubbo-sample-provider-v1-5d9496c8bc-zdhdk/10.42.0.51
Hello Aeraki, response from dubbo-sample-provider-v2-59c8778bb-8hbmk/10.42.0.54
|
Aeraki mesh 逻辑分析
以使用 zookeeper 注册中心为例,这里先查看 dubbo2istio 的日志:
1
2
3
|
2023-02-27T11:57:25.254890Z info dubbo2istio runs in Zookeeper mode: registry: default, addr: zookeeper:2181
2023-02-27T11:57:25.257483Z info connected to 10.43.194.10:2181
2023-02-27T11:57:33.374613Z info service entry aeraki-org-apache-dubbo-samples-basic-api-demoservice has been updated: {"metadata":{"name":"aeraki-org-apache-dubbo-samples-basic-api-demoservice","namespace":"meta-dubbo","resourceVersion":"421514","creationTimestamp":null,"labels":{"manager":"aeraki","registry":"dubbo2istio"},"annotations":{"interface":"org.apache.dubbo.samples.basic.api.DemoService","workloadSelector":"dubbo-sample-provider"}},"spec":{"hosts":["org.apache.dubbo.samples.basic.api.demoservice"],"ports":[{"number":20880,"protocol":"tcp","name":"tcp-metaprotocol-dubbo","targetPort":20880}],"location":"MESH_INTERNAL","resolution":"STATIC","endpoints":[{"address":"10.42.0.64","ports":{"tcp-metaprotocol-dubbo":20880},"labels":{"anyhost":"true","application":"dubbo-sample-provider","default":"true","deprecated":"false","dubbo":"2.0.2","dynamic":"true","generic":"false","interface":"org.apache.dubbo.samples.basic.api.DemoService","metadata-type":"remote","methods":"testVoid-sayHello","pid":"8","registryName":"default","release":"1.0-SNAPSHOT","revision":"1.0-SNAPSHOT","service_group":"batchjob","side":"provider","timestamp":"1677499051587","version":"v2"},"locality":"bj/800005","serviceAccount":"default"},{"address":"10.42.0.63","ports":{"tcp-metaprotocol-dubbo":20880},"labels":{"anyhost":"true","application":"dubbo-sample-provider","default":"true","deprecated":"false","dubbo":"2.0.2","dynamic":"true","generic":"false","interface":"org.apache.dubbo.samples.basic.api.DemoService","metadata-type":"remote","methods":"testVoid-sayHello","pid":"7","registryName":"default","release":"1.0-SNAPSHOT","revision":"1.0-SNAPSHOT","service_group":"user","side":"provider","timestamp":"1677499051569","version":"v1"},"locality":"bj/800002","serviceAccount":"default"}]},"status":{}}
|
针对 demoservice 生成了一个名为 aeraki-org-apache-dubbo-samples-basic-api-demoservice 的 ServiceEntry,用命令获取其描述如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
annotations:
interface: org.apache.dubbo.samples.basic.api.DemoService
workloadSelector: dubbo-sample-provider
labels:
manager: aeraki
registry: dubbo2istio
name: aeraki-org-apache-dubbo-samples-basic-api-demoservice
namespace: meta-dubbo
spec:
# 与该服务相关的虚拟 IP 地址,可以是 CIDR 前缀。对于 HTTP 流量,生成的路由配置将包括地址和主机字段值的 http 路由域,目的地将根据 HTTP 主机/授权头来识别。如果指定了一个或多个 IP 地址,如果目的地 IP 与地址字段中指定的 IP/CIDRs 相匹配,传入的流量将被识别为属于该服务。如果地址字段为空,流量将仅根据目标端口进行识别。
addresses:
- 240.240.0.46
hosts: # 此服务关联的 hosts,可以是带通配符的 DNS 名称
- org.apache.dubbo.samples.basic.api.demoservice
location: MESH_INTERNAL # 指定服务是在网格外部还是网格内部
ports:
- name: tcp-metaprotocol-dubbo # Aeraki 会识别后面的应用协议并进行相应的七层处理
number: 20880
protocol: tcp
targetPort: 20880
resolution: STATIC
endpoints:
- address: 10.42.0.64 # dubbo-sample-provider-v2 的 pod ip
labels: # endpoints 相关的标签
anyhost: "true"
application: dubbo-sample-provider
default: "true"
deprecated: "false"
dubbo: 2.0.2
dynamic: "true"
generic: "false"
interface: org.apache.dubbo.samples.basic.api.DemoService
metadata-type: remote
methods: testVoid-sayHello
pid: "8"
registryName: default
release: 1.0-SNAPSHOT
revision: 1.0-SNAPSHOT
service_group: batchjob
side: provider
timestamp: "1677499051587"
version: v2
locality: bj/800005
ports:
tcp-metaprotocol-dubbo: 20880
serviceAccount: default
- address: 10.42.0.63 # # dubbo-sample-provider-v1 的 pod ip
# ...
serviceAccount: default
|
ServiceEntry 是可以添加额外的服务项到 istio 内部的服务注册中心,这样网格可以将请求转发到这些指定的服务,这些服务的 endpoints 可以是 VM 负载或者 kubernetes 的 pods。 其中某些 host 是一个必选字段,表示与 ServiceEntry 相关的主机名,可以是一个 DNS 域名,还可以使用前缀模糊匹配。在使用上有以下几个说明:
- HTTP 的流量,在这个字段匹配 HTTP Header 的 Host 或 Authority。
- HTTPS 或 TLS 流量,这个字段匹配 SNI。
- 其他协议的流量,这个字段不生效,使用下面的 addresses 和 port 字段。
- 当 resolution 被设置为 DNS 类型并且没有指定 endpoints 时,这个字段将作为后端的域名来进行路由。
这时在 istiod 中 pods 中通过
curl http://127.0.0.1:15014/debug/registryz
查看此注册项,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
{
"Attributes": {
"ServiceRegistry": "External",
"Name": "org.apache.dubbo.samples.basic.api.demoservice",
"Namespace": "meta-dubbo",
"Labels": {
"manager": "aeraki",
"registry": "dubbo2istio"
},
"ExportTo": null,
"LabelSelectors": null,
"ClusterExternalAddresses": {
"Addresses": null
},
"ClusterExternalPorts": null
},
"ports": [{
"name": "tcp-metaprotocol-dubbo",
"port": 20880,
"protocol": "TCP"
}],
"creationTime": "2023-03-15T06:38:02Z",
"hostname": "org.apache.dubbo.samples.basic.api.demoservice",
"clusterVIPs": {
"Addresses": null
},
"defaultAddress": "240.240.0.46",
"Resolution": 0,
"MeshExternal": false,
"ResourceVersion": ""
}
|
继续看下 consumer 中的代理配置,可以看到配置的 cluster 名称为 ServiceEntry 中的 hosts,并配置了其 endpoints 为两个 provider pods 的 ip:
1
2
3
4
5
6
|
root@dev:~/zt/istio/dubbo2istio/demo/k8s/test-zk# istioctl proxy-config cluster dubbo-sample-consumer-67fc956cc8-c8ltf | grep demo
org.apache.dubbo.samples.basic.api.demoservice 20880 - outbound EDS
root@dev:~/zt/istio/dubbo2istio/demo/k8s/test-zk# istioctl proxy-config endpoints dubbo-sample-consumer-67fc956cc8-c8ltf --cluster "outbound|20880||org.apache.dubbo.samples.basic.api.demoservice"
ENDPOINT STATUS OUTLIER CHECK CLUSTER
10.42.0.159:20880 HEALTHY OK outbound|20880||org.apache.dubbo.samples.basic.api.demoservice
10.42.0.160:20880 HEALTHY OK outbound|20880||org.apache.dubbo.samples.basic.api.demoservice
|
上面的逻辑解决了如何找到 provider 地址的问题,但是因为 consumer 中使用的是 dubbo 协议,而 istio 中目前不支持 dubbo 协议,因此 envoy 无法进行出向流量的劫持。
在 Aeraki 中通过 meta-protocol-proxy 代理来解决的,这个代理目前支持 dubbo 和 thrift 协议。 Aeraki 服务会监测上面的 ServiceEntry,生成对应的 EnvoyFilter,将 tcp 网络过滤器替换为自定义的 meta_protocol_proxy 协议,由 istiod 下发到代理中。下面示例项目中生成的 EnvoyFilter:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
labels:
manager: Aeraki
name: aeraki-outbound-org.apache.dubbo.samples.basic.api.demoservice-240.240.0.6-20880
namespace: istio-system
spec:
configPatches:
- applyTo: NETWORK_FILTER
match:
listener:
filterChain:
filter:
name: envoy.filters.network.tcp_proxy # 上面设置的是 tcp 协议,因此会生成 tcp 的 filter
name: 240.240.0.6_20880
patch:
operation: REPLACE # 替换上面的网络过滤器
value:
name: envoy.filters.network.meta_protocol_proxy # 替换 Aeraki 的自定义协议 meta_protocol_proxy
typed_config:
'@type': type.googleapis.com/udpa.type.v1.TypedStruct
type_url: type.googleapis.com/aeraki.meta_protocol_proxy.v1alpha.MetaProtocolProxy
value:
accessLog:
- name: envoy.access_loggers.file
typedConfig:
'@type': type.googleapis.com/envoy.extensions.access_loggers.file.v3.FileAccessLog
path: /dev/stdout
applicationProtocol: dubbo # 应用层协议为 dubbo
codec:
name: aeraki.meta_protocol.codec.dubbo # 应用层编码和解码协议
metaProtocolFilters: # 一个单独的七层协议过滤器列表,默认为 router 即直接路由
- name: aeraki.meta_protocol.filters.router
rds: # 设置路由由 rDS 动态获取
configSource:
apiConfigSource:
apiType: GRPC
grpcServices:
- envoyGrpc:
clusterName: aeraki-xds
transportApiVersion: V3
resourceApiVersion: V3
# routeConfigName 路由配置的名称
routeConfigName: org.apache.dubbo.samples.basic.api.demoservice_20880
statPrefix: outbound|20880||org.apache.dubbo.samples.basic.api.demoservice
|
在 EnvoyFilter 中设置了 routeConfigName,将会通过 xDS 下发给代理,代理中的路由配置如下,其中设置了路由到上面所示的 cluster 中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
{
"version_info": "1678172533",
"route_config": {
"@type": "type.googleapis.com/aeraki.meta_protocol_proxy.config.route.v1alpha.RouteConfiguration",
"name": "org.apache.dubbo.samples.basic.api.demoservice_20880",
"routes": [
{
"name": "default",
"route": {
"cluster": "outbound|20880||org.apache.dubbo.samples.basic.api.demoservice"
}
]
},
"last_updated": "2023-03-07T07:02:13.705Z"
}
|
查看 envoy 中此集群的配置:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
|
{
"version_info": "2023-03-15T06:38:56Z/18",
"cluster": {
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "outbound|20880||org.apache.dubbo.samples.basic.api.demoservice",
"type": "EDS", // 服务发现类型,用来解析 cluster,参考 [Cluster.DiscoveryType](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto.html#enum-config-cluster-v3-cluster-discoverytype)
"eds_cluster_config": { // EDS 类型时的配置
"eds_config": {
"ads": {}, // 当前为空,在这里可以指定要使用的 ADS 服务
"initial_fetch_timeout": "0s",
"resource_api_version": "V3"
},
// 可选的集群替代名,用来提交给 EDS
"service_name": "outbound|20880||org.apache.dubbo.samples.basic.api.demoservice"
},
"connect_timeout": "10s",
"lb_policy": "LEAST_REQUEST",
"circuit_breakers": {}, // 熔断设置
"metadata": { // 用来提供关于 cluster 的额外信息,可以被用来做统计、日志和改变过滤器行为
"filter_metadata": { // map<string, Struct>,key 是逆序的 DNS 过滤器名称,比如 com.acme.widget
"istio": { // 这里是指:istio 相关的过滤器?
"config": "/apis/networking.istio.io/v1alpha3/namespaces/meta-dubbo/destination-rule/dubbo-sample-provider",
"default_original_port": 20880,
"services": [
{
"name": "org.apache.dubbo.samples.basic.api.demoservice",
"host": "org.apache.dubbo.samples.basic.api.demoservice",
"namespace": "meta-dubbo"
}
]
}
}
},
"common_lb_config": { // 所有负载均衡实现的通用配置
"locality_weighted_lb_config": {} // 使用 [位置加权负载均衡](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/locality_weight#arch-overview-load-balancing-locality-weighted-lb)
},
// 有序的网络过滤器链,在 envoy 向此上游 cluster 请求时会应用
"filters": [
{
"name": "istio.metadata_exchange",
// 通过 PILOT_ENABLE_METADATA_EXCHANGE 开启的,pilot 会开启元数据交换过滤器,用来在遥测过滤器中使用
"typed_config": {
"@type": "type.googleapis.com/envoy.tcp.metadataexchange.config.MetadataExchange",
"protocol": "istio-peer-exchange"
}
}
],
"transport_socket_matches": [ //为不同的 endponints 配置不同的传输 sockets
{
"name": "tlsMode-istio",
"match": { // 可选的端点元数据匹配准则,与元数据匹配的端点的连接将使用此传输套接字配置
"tlsMode": "istio"
},
"transport_socket": {
"name": "envoy.transport_sockets.tls", // 用于对不信任的下行和上行流量进行保护
"typed_config": {
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext",
"common_tls_context": {
"tls_params": {
"tls_minimum_protocol_version": "TLSv1_2",
"tls_maximum_protocol_version": "TLSv1_3"
},
"alpn_protocols": [
"istio-peer-exchange",
"istio"
],
"tls_certificate_sds_secret_configs": [
// ...
],
"combined_validation_context": {
"default_validation_context": {
"match_subject_alt_names": [
{
"exact": "spiffe://cluster.local/ns/meta-dubbo/sa/default"
}
]
},
"validation_context_sds_secret_config": {
"name": "ROOTCA",
"sds_config": {//...}
}
}
},
// 服务器名称指示,创建 TLS 后端连接时使用,SNI 是 TLS 握手期间的一个扩展,它允许客户端在建立 TLS 连接时指定要访问的服务器名称。服务器可以使用这个信息来选择正确的证书以及配置 TLS 连接
"sni": "outbound_.20880_._.org.apache.dubbo.samples.basic.api.demoservice"
}
}
},
{
"name": "tlsMode-disabled",
"match": {},
"transport_socket": {
"name": "envoy.transport_sockets.raw_buffer",
"typed_config": {
"@type": "type.googleapis.com/envoy.extensions.transport_sockets.raw_buffer.v3.RawBuffer"
}
}
}
]
},
"last_updated": "2023-03-15T06:38:56.637Z"
},
|
使用 EnvoyFilter 接入 dubbo2
这里考虑一个场景,只需要支持 dubbo2 协议和第三方注册中心,并且使用 dubbo 自身的服务治理能力,那可以对上述逻辑进行简化,不引入 Aeraki 组件和 MetaProtocol Proxy 代理,只使用 dubbo2istio,手动生成 EnvoyFilter 来支持 dubbo 协议。
因为上面测试生成了 EnvoyFilter 和 ServiceEntry 等资源,这里先进行清理工作:
1
2
3
4
5
6
7
8
|
kubens meta-dubbo
cd dubbo2istio
kubectl delete -f demo/k8s/zk/dubbo-example.yaml
kubectl delete deploy -nistio-system aeraki
// 查看 aeraki 生成的 envoyfilter,然后删除
kubectl get envoyfilter -nistio-system | grep aeraki
// 查看 aeraki 生成的 serviceentry,然后删除
kubectl get serviceentry
|
这里将测试用例也进行了简化,provider.yaml 如下所示,去掉了 sidecar 相关的 annotations,因此默认会使用 envoy 代理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
apiVersion: apps/v1
kind: Deployment
metadata:
name: dubbo-sample-provider-v1
labels:
app: dubbo-sample-provider
spec:
selector:
matchLabels:
app: dubbo-sample-provider
replicas: 1
template:
metadata:
labels:
app: dubbo-sample-provider
version: v1
spec:
containers:
- name: dubbo-sample-provider
image: aeraki/dubbo-sample-provider
imagePullPolicy: Always
env:
- name: REGISTRY_ADDR
value: zookeeper://zookeeper:2181
- name: REGISTER
value: "true"
- name: AERAKI_META_APP_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- containerPort: 20880
|
部署 provider 后,查看 dubbo2istio 生成的 ServiceEntry 为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
apiVersion: networking.istio.io/v1beta1
kind: ServiceEntry
metadata:
annotations:
interface: org.apache.dubbo.samples.basic.api.DemoService
workloadSelector: ""
labels:
manager: aeraki
registry: dubbo2istio
name: aeraki-org-apache-dubbo-samples-basic-api-demoservice
namespace: meta-dubbo
spec:
endpoints:
- address: 10.42.0.20
labels:
anyhost: "true"
application: dubbo-sample-provider
default: "true"
deprecated: "false"
dubbo: 2.0.2
dynamic: "true"
generic: "false"
interface: org.apache.dubbo.samples.basic.api.DemoService
metadata-type: remote
methods: testVoid-sayHello
pid: "7"
registryName: default
release: 1.0-SNAPSHOT
revision: 1.0-SNAPSHOT
service_group: ""
side: provider
timestamp: "1679308798700"
ports:
tcp-metaprotocol-dubbo: 20880
serviceAccount: default
hosts:
- org.apache.dubbo.samples.basic.api.demoservice
location: MESH_INTERNAL
ports:
- name: tcp-metaprotocol-dubbo
number: 20880
protocol: tcp
targetPort: 20880
resolution: STATIC
|
编辑此 ServiceEntry 增加 addresses 字段,分配一个保留网段的全局唯一 ip 值,比如 240.240.0.2,当 consumer 端请求到 host 服务时,经过域名解析后会请求到 240.240.0.2。
分配了 addresses 后,那 EnvoyFilter 中需要替换的过滤器就确定了。部署如下所示的 EnvoyFilter,会将配置中请求的 TCP 过滤器替换为 Dubbo 过滤器,并设置了路由的匹配方法和目标集群,这样请求会被转发到目标集群对应的 Endpoints(Istio 根据 ServiceEntry 的 Endpoints 信息下发到 envoy 中)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
name: aeraki-inbound-org.apache.dubbo.samples.basic.api.demoservice-240.240.0.2-20880
spec:
configPatches:
- applyTo: NETWORK_FILTER
match:
listener:
name: 240.240.0.2_20880
filterChain:
filter:
name: "envoy.filters.network.tcp_proxy"
patch:
operation: REPLACE
value:
name: envoy.filters.network.dubbo_proxy
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.dubbo_proxy.v3.DubboProxy
protocol_type: Dubbo
serialization_type: Hessian2
statPrefix: outbound|20880||org.apache.dubbo.samples.basic.api.demoservice
route_config:
- name: outbound|20880||org.apache.dubbo.samples.basic.api.demoservice
interface: org.apache.dubbo.samples.basic.api.DemoService
routes:
- match:
method:
name:
exact: sayHello
route:
cluster: outbound|20880||org.apache.dubbo.samples.basic.api.demoservice
|
继续部署如下所示的 consumer,部署描述中也去掉了 sidecar 相关的 annotations:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
apiVersion: apps/v1
kind: Deployment
metadata:
name: dubbo-sample-consumer
labels:
app: dubbo-sample-consumer
spec:
selector:
matchLabels:
app: dubbo-sample-consumer
replicas: 1
template:
metadata:
labels:
app: dubbo-sample-consumer
spec:
containers:
- name: dubbo-sample-consumer
image: aeraki/dubbo-sample-consumer
env:
- name: mode
value: demo
ports:
- containerPort: 9009
|
查看日志可以看到,consumer 服务正常请求 provider 了。这里是通过手动修改 ServiceEntry,手动编写 EnvoyFilter,比较繁琐,可以将此过程自动化,新增一个模块 EController,整体框图如下所示。
参考