乐趣区

关于后端:Proxyless-Mesh-在-Dubbo-中的实践

背景

随着 Dubbo 3.1 的 release,Dubbo 在云原生的路上又迈出了重要的一步。在这个版本中增加了 Proxyless Mesh 的新个性,Dubbo Proxyless Mesh 间接实现 xDS 协定解析,
实现 Dubbo 与 Control Plane 的间接通信,进而实现管制面对流量管控、服务治理、可观测性、平安等的对立管控,躲避 Sidecar 模式带来的性能损耗与部署架构复杂性。

什么是 Service Mesh

Service Mesh 又译作“服务网格”,作为服务间通信的基础设施层。Buoyant 公司的 CEO Willian Morgan 在他的这篇文章 WHAT’S A Service Mesh? AND WHY DO I NEED ONE?
中解释了什么是 Service Mesh,为什么云原生利用须要 Service Mesh。

上面是 Willian Morgan 对 Service Mesh 的解释。

A Service Mesh is a dedicated infrastructure layer for handling service-to-service communication. 
It’s responsible for the reliable delivery of requests through the complex topology of services 
that comprise a modern, cloud native application. In practice, the Service Mesh is typically implemented 
as an array of lightweight network proxies that are deployed alongside application code, without the 
application needing to be aware.

翻译成中文

 服务网格(Service Mesh)是解决服务间通信的基础设施层。它负责形成古代云原生应用程序的简单服务拓扑来牢靠地交付申请。在实践中,Service Mesh 通常以轻量级网络代理阵列的模式实现,这些代理与利用程序代码部署在一起,对应用程序来说无需感知代理的存在。

说到 Service Mesh 肯定离不开 Sidecar 经典架构模式。它通过在业务 Pod 中注入 Sidecar 容器,接管业务容器的通信流量,同时 Sidecar 容器与网格平台的管制立体对接,
基于管制立体下发的策略,对代理流量施行治理和管控,将原有服务框架的治理能力上层到 Sidecar 容器中,从而实现了根底框架能力的下沉,与业务零碎解耦。

经典的 Sidecar Mesh 部署架构有很多劣势,如平滑降级、多语言、业务侵入小等,但也带来了一些额定的问题,比方:

  • Proxy 带来的性能损耗,在简单拓扑的网络调用中将变得尤其显著
  • 流量拦挡带来的架构复杂性
  • Sidecar 生命周期治理
  • 部署环境受限,并不是所有环境都满足 Sidecar 流量拦挡条件

针对 Sidecar Mesh 模型的问题,Dubbo 社区自很早之前就做了 Dubbo 间接对接到管制面的构想与思考,并在国内开源社区率先提出了 Proxyless Mesh 的概念,当然就 Proxyless 概念的说法而言,最开始是谷歌提出来的。

Dubbo Proxyless Mesh

Dubbo Proxyless 模式是指 Dubbo 间接与 Istiod 通信,通过 xDS 协定实现服务发现和服务治理等能力。

Proxyless 模式使得微服务又回到了 2.x 时代的部署架构,同 Dubbo 经典服务治理模式十分类似,所以说这个模式并不陈腐,Dubbo 从最开始就是这样的设计模式。
这样做能够极大的晋升利用性能,升高网络提早。有人说这种做法又回到了原始的基于 SDK 的微服务模式,其实非也,它仍然应用了 Envoy 的 xDS API,
然而因为不再须要向应用程序中注入 Sidecar 代理,因而能够缩小应用程序性能的损耗。

但相比于 Mesh 架构,Dubbo 经典服务治理模式并没有强调管制面的对立管控,而这点恰好是 Service Mesh 所强调的,强调对流量、可观测性、证书等的标准化管控与治理,也是 Mesh 理念先进的中央。

在 Dubbo Proxyless 架构模式下,Dubbo 过程将间接与管制面通信,Dubbo 过程之间也持续放弃直连通信模式,咱们能够看出 Proxyless 架构的劣势:

  • 没有额定的 Proxy 直达损耗,因而更实用于性能敏感利用
  • 更有利于遗留零碎的平滑迁徙
  • 架构简略,容易运维部署
  • 实用于简直所有的部署环境

服务发现

xDS 接入以注册核心的模式对接,节点发现同其余注册核心的服务自省模型一样,对于 xDS 的负载平衡和路由配置通过 ServiceInstance 的动静运行时配置传出,
在构建 Invoker 的时候将配置参数传入配置地址。

证书治理

零信赖架构下,须要严格辨别工作负载的辨认和信赖,而签发 X.509 证书是举荐的一种认证形式。在 Kubernetes 集群中,服务间是通过 DNS 名称相互拜访的,而网络流量可能被 DNS 坑骗、BGP/ 路由劫持、ARP 坑骗等伎俩劫持,为了将服务名称(DNS 名称)与服务身份强关联起来,Istio 应用置于 X.509 证书中的平安命名机制。SPIFFE 是 Istio 所采纳的平安命名的标准,它也是云原生定义的一种标准化的、可移植的工作负载身份标准。

Secure Production Identity Framework For Everyone (SPIFFE) 是一套服务之间互相进行身份辨认的规范,次要蕴含以下内容:

  • SPIFFE ID 规范,SPIFFE ID 是服务的惟一标识,具体实现应用 URI 资源标识符
  • SPIFFE Verifiable Identity Document (SVID) 规范,将 SPIFFE ID 编码到一个加密的可验证的数据格式中
  • 颁发与撤销 SVID 的 API 规范(SVID 是 SPIFFE ID 的辨认凭证)

SPIFFE ID 规定了形如 spiffe://<trust domain>/<workload identifier> 的 URI 格局,作为工作负载(Workload)的惟一标识。
而 Istio 在本身的生态中只应用到了 SPIFFE ID 作为平安命名,其数据格式由本人实现,通信格局采纳 CNCF 反对的 xDS 协定标准(证书认证通信更具体来说是 xDS 的 SDS)。

Istio 应用形如 spiffe://<trust_domain>/ns/<namespace>/sa/<service_account> 格局的 SPIFFE ID 作为平安命名,注入到 X.509 证书的 subjectAltName 扩大中。
其中 ”trust_domain” 参数通过 Istiod 环境变量 TRUST_DOMAIN 注入,用于在多集群环境中交互。

以下是 Dubbo Proxyless Mesh 证书颁发的过程

  • 创立 RSA 私钥(Istio 还不反对 ECDSA 私钥)
  • 构建 CSR(Certificate signing request)模板
  • 自签名 CSR 生成证书
  • 创立 Kubernetes Secret 资源贮存 CA 证书和私钥(CA Service 解决)

案例实际

接下来我将率领大家通过一个例子使已有的我的项目疾速跑在 Proxyless Mesh 模式下。

环境筹备

装置 docker

https://www.docker.com/

装置 minikube

墙裂举荐:https://kubernetes.io/zh-cn/docs/tutorials/hello-minikube/

装置 istio

https://istio.io/latest/docs/setup/getting-started/

❗❗❗ 装置 Istio 的时候须要开启 first-party-jwt 反对(应用 istioctl 工具装置的时候加上 –set values.global.jwtPolicy=first-party-jwt 参数),否则将导致客户端认证失败的问题。
参考命令如下:

curl -L https://istio.io/downloadIstio | sh -
cd istio-1.xx.x
export PATH=$PWD/bin:$PATH
istioctl install --set profile=demo --set values.global.jwtPolicy=first-party-jwt -y

代码筹备

xds-provider

定义一个接口

public interface GreetingService {String sayHello(String name);
}

实现对应的接口

@DubboService(version = "1.0.0")
public class AnnotatedGreetingService implements GreetingService {

    @Override
    public String sayHello(String name) {System.out.println("greeting service received:" + name);
        return "hello," + name + "! from host:" + NetUtils.getLocalHost();}
}

编写启动类

public class ProviderBootstrap {public static void main(String[] args) throws Exception {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class);
        context.start();
        System.out.println("dubbo service started");
        new CountDownLatch(1).await();}

    @Configuration
    @EnableDubbo(scanBasePackages = "org.apache.dubbo.samples.impl")
    @PropertySource("classpath:/spring/dubbo-provider.properties")
    static class ProviderConfiguration {}}

编写配置信息

dubbo.application.name=dubbo-samples-xds-provider
# 因为 Dubbo 3 利用级服务发现的元数据无奈从 istio 中获取,须要走服务自省模式。# 这要求了 Dubbo MetadataService 的端口在全集群的是对立的。dubbo.application.metadataServicePort=20885
# 走 xds 协定
dubbo.registry.address=xds://istiod.istio-system.svc:15012
dubbo.protocol.name=tri
dubbo.protocol.port=50051
# 对齐 k8s pod 生命周期,因为 Kubernetes probe 探活机制的工作原理限度,# 探活申请的发起方不是 localhost,所以须要配置 qosAcceptForeignIp 参数开启容许全局拜访
dubbo.application.qosEnable=true
dubbo.application.qosAcceptForeignIp=true

编写 Deployment.yml 和 Service.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dubbo-samples-xds-provider
  namespace: dubbo-demo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: dubbo-samples-xds-provider
  template:
    metadata:
      labels:
        app: dubbo-samples-xds-provider
    spec:
      containers:
        - name: server
          image: apache/dubbo-demo:dubbo-samples-xds-provider_0.0.1
          livenessProbe:
            httpGet:
              path: /live
              port: 22222
            initialDelaySeconds: 5
            periodSeconds: 5
          readinessProbe:
            httpGet:
              path: /ready
              port: 22222
            initialDelaySeconds: 5
            periodSeconds: 5
          startupProbe:
            httpGet:
              path: /startup
              port: 22222
            failureThreshold: 30
            periodSeconds: 10
apiVersion: v1
kind: Service
metadata:
  name: dubbo-samples-xds-provider
  namespace: dubbo-demo
spec:
  clusterIP: None
  selector:
    app: dubbo-samples-xds-provider
  ports:
    - name: grpc
      protocol: TCP
      port: 50051
      targetPort: 50051

编写 Dockerfile

FROM openjdk:8-jdk
ADD ./target/dubbo-samples-xds-provider-1.0-SNAPSHOT.jar dubbo-samples-xds-provider-1.0-SNAPSHOT.jar
CMD java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=31000 /dubbo-samples-xds-provider-1.0-SNAPSHOT.jar

xds-consumer

定义一个接口

public interface GreetingService {String sayHello(String name);
}

实现对应的接口

@Component("annotatedConsumer")
public class GreetingServiceConsumer {
      // 这里特地留神的是、因为以后 Dubbo 版本受限于 istio 的通信模型无奈获取接口所对应的利用名,// 因而须要配置 providedBy 参数来标记此服务来自哪个利用。@DubboReference(version = "1.0.0", providedBy = "dubbo-samples-xds-provider")
    private GreetingService greetingService;

    public String doSayHello(String name) {return greetingService.sayHello(name);
    }
}

编写启动类

public class ConsumerBootstrap {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class);
        context.start();
        GreetingServiceConsumer greetingServiceConsumer = context.getBean(GreetingServiceConsumer.class);
        while (true) {
            try {String hello = greetingServiceConsumer.doSayHello("xDS Consumer");
                System.out.println("result:" + hello);
                Thread.sleep(100);
            } catch (Throwable t) {t.printStackTrace();
            }
        }
    }

    @Configuration
    @EnableDubbo(scanBasePackages = "org.apache.dubbo.samples.action")
    @PropertySource("classpath:/spring/dubbo-consumer.properties")
    @ComponentScan(value = {"org.apache.dubbo.samples.action"})
    static class ConsumerConfiguration {}}

编写配置信息

dubbo.application.name=dubbo-samples-xds-consumer
dubbo.application.metadataServicePort=20885
dubbo.registry.address=xds://istiod.istio-system.svc:15012
dubbo.consumer.timeout=3000
dubbo.consumer.check=false
dubbo.application.qosEnable=true
dubbo.application.qosAcceptForeignIp=true

编写 Deployment.yml 和 Service.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dubbo-samples-xds-consumer
  namespace: dubbo-demo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: dubbo-samples-xds-consumer
  template:
    metadata:
      labels:
        app: dubbo-samples-xds-consumer
    spec:
      containers:
        - name: server
          image: apache/dubbo-demo:dubbo-samples-xds-consumer_0.0.1
          livenessProbe:
            httpGet:
              path: /live
              port: 22222
            initialDelaySeconds: 5
            periodSeconds: 5
          readinessProbe:
            httpGet:
              path: /ready
              port: 22222
            initialDelaySeconds: 5
            periodSeconds: 5
          startupProbe:
            httpGet:
              path: /startup
              port: 22222
            failureThreshold: 30
            periodSeconds: 10
apiVersion: v1
kind: Service
metadata:
  name: dubbo-samples-xds-consumer
  namespace: dubbo-demo
spec:
  clusterIP: None
  selector:
    app: dubbo-samples-xds-consumer
  ports:
    - name: grpc
      protocol: TCP
      port: 50051
      targetPort: 50051

编写 Dockerfile

FROM openjdk:8-jdk
ADD ./target/dubbo-samples-xds-consumer-1.0-SNAPSHOT.jar dubbo-samples-xds-consumer-1.0-SNAPSHOT.jar
CMD java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=31000 /dubbo-samples-xds-consumer-1.0-SNAPSHOT.jar

✅ 到目前为止咱们的环境和代码就全都筹备结束了!

构建镜像

1、启动 docker

2、启动 minikube

因为 minikube 是一个本地的 k8s,他启动须要一个虚构引擎,这里咱们用 docker 来治理。咱们通过如下命令启动

minikube start

咱们能够在 docker 里看到 minikube

3、查看 istio 的状态

4、构建镜像

在本地找到代码所在位置、顺次执行以下命令

# 找到 provider 所在门路
cd ./dubbo-samples-xds-provider/
# 构建 provider 的镜像
docker build -t apache/dubbo-demo:dubbo-samples-xds-provider_0.0.1 .
# 找到 consumer 所在门路
cd ../dubbo-samples-xds-consumer/
# 构建 consumer 的镜像
docker build -t apache/dubbo-demo:dubbo-samples-xds-consumer_0.0.1 .

5、查看本地镜像

6、创立 namespace

# 初始化命名空间
kubectl apply -f https://raw.githubusercontent.com/apache/dubbo-samples/master/dubbo-samples-xds/deploy/Namespace.yml

# 切换命名空间
kubens dubbo-demo

如果不创立 namespace,那么会看到如下谬误

部署容器

# 找到 provider 所在门路
cd ./dubbo-samples-xds-provider/src/main/resources/k8s
# dubbo-samples-xds/dubbo-samples-xds-provider/src/main/resources/k8s/Deployment.yml
# dubbo-samples-xds/dubbo-samples-xds-provider/src/main/resources/k8s/Service.yml

# 部署 provider 的 Deployment 和 Service
kubectl apply -f Deployment.yml
kubectl apply -f Service.yml
# 找到 consumer 所在门路
cd ../../../../../dubbo-samples-xds-consumer/src/main/resources/k8s
# dubbo-samples-xds/dubbo-samples-xds-consumer/src/main/resources/k8s/Deployment.yml

# 部署 consumer 的 Deployment
kubectl apply -f Deployment.yml

在 minikube dashboard 看到咱们曾经部署的 pod

察看 consumer 成果

kubectl logs xxx

result: hello, xDS Consumer! from host: 172.17.0.5
result: hello, xDS Consumer! from host: 172.17.0.5
result: hello, xDS Consumer! from host: 172.17.0.6
result: hello, xDS Consumer! from host: 172.17.0.6

总结 & 瞻望

本文次要分析了 Dubbo Proxyless Mesh 的架构、服务发现以及证书治理等外围流程,最初通过示例给大家演示了如何应用 Dubbo Proxyless。

随着 Dubbo 3.1 的 release,Dubbo 在云原生的路上又迈出了重要的一步。在今年年底,Dubbo Mesh 将公布具备服务发现能力的版本,
届时将面向所有 Dubbo 用户提供从低版本平滑迁徙到 Mesh 架构的能力;在明年年初秋季的时候将公布带有治理能力的版本;在明年年底前公布带热插件更新能力的版本,
心愿有趣味见证 Dubbo 云原生之路的同学能够积极参与社区奉献!

欢送在 https://github.com/apache/dubbo 给 Dubbo Star。
搜寻关注官网微信公众号:Apache Dubbo,理解更多业界最新动静,把握大厂面试必备 Dubbo 技能

退出移动版