Dubbo 3.0 前瞻之对接 Kubernetes 原生服务

Kubernetes 是当前全球最流的容器服务平台,在 Kubernetes 集群中,Dubbo 应用的部署方式往往需要借助第三方注册中心实现服务发现。Dubbo 与 Kubernetes 的调度体系的结合,可以让原本需要管理两套平台的运维成本大大减低,而且 Dubbo 适配了 Kubernetes 原生服务也可以让框架本身更加融入云原生体系。基于 Dubbo 3.0 的全新应用级服务发现模型可以更容易对齐 Kubernetes 的服务模型。

Kubernetes Native Service

Dubbo 3.0 前瞻之对接 Kubernetes 原生服务

在 Kubernetes 中,Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。通常一个 Pod 由一个或多个容器组成,应用则部署在容器内。

对于一组具有相同功能的 Pod,Kubernetes 通过 Service 的概念定义了这样一组 Pod 的策略的抽象,也即是 Kubernetes Service。这些被 Kubernetes Service 标记的 Pod 一般都是通过 Label Selector 决定的。

在 Kubernetes Service 内,服务节点被称为 Endpoint,这些 Endpoint 也就是对应提供服务的应用单元,通常一对一对应了 Pod。

此,我们可以将微服务中的应用本身使用 Service 来进调度,而微服务间的调用通过 Service 的一系列机制来实现服务发现,进而将微服务整合进 Kubernetes Service 的体系中。

Dubbo 应用级服务发现

在 Dubbo 体系结构内,为了更好地符合 Java 开发人员的编程习惯,Dubbo 底层以接口粒度作为注册对象。但是这个模型对现在主流的 Spring Cloud 注册模型和 Kubernetes Service 注册模型有很大的区别。

目前在非 Dubbo 体系外的注册模型主要是以服务粒度作为注册对象,为了打通 Dubbo 与其他体系之间的注册发现壁垒,Dubbo 在 2.7.5 版本以后引入了服务自省的架构,主要通过元数据服务实现从服务粒度到接口粒度的过渡。在 2.7.5 版本以后到 3.0 版本,服务自省模型进行了很多方面的优化,并且在生产环境下进行了验证

1. 元数据服务

Dubbo 3.0 前瞻之对接 Kubernetes 原生服务

元数据服务主要是存储了服务(Instance)与接口(Interface)的映射关系,通过将原本的写入到注册中心的接口信息抽象到元数据进行存储,一方面可以大大减少注册中心存储的数据量、降低服务更新时集群的网络通信压力,另一方面,实现了注册中心层面只存储应用粒度信息的目标,对齐了其他注册模型。

在服务自省模型中,服务提供者不仅仅往注册中心写入当前实例的信息,还需要往(本地或者远程的)元数据服务写入暴露的服务 URL 信息等;而对于服务消费者,在从注册中心获取实例信息后,还需要(通过 RPC 请求内建或者中心化配置中心获取)元数据服务获取服务提供者的服务 URL 信息等来生成接口粒度体系下的接口信息。

2. Revision 信息

Dubbo 3.0 前瞻之对接 Kubernetes 原生服务

Revision 信息是元数据服务引入的一种数据缓存机制,对于同一组应用很多情况下暴露的接口其实都是一样的,在进行服务(Instance)与接口(Interface)映射的时候会有许多重复的冗余数据,因此可以使用类似对元数据信息进行 MD5 计算的方式来对实例本身加上版本号,如果多个实例的版本号一致可以认为它们的元数据信息也一致,那么只需要随机选择一台来获取元数据信息即可,可以实现把通行量从一组实例都需要通信到只需要与一个实例通信的压缩

如上图所示,服务消费者注册中心的工作机制可以总结为:

  • 服务消费者向注册中心获取服务实例列表。
  • 注册中心向服务消费者返回服务实例信息,在实例列表中包括了服务提供者向注册中心写入的 Revision 参数。
  • 服务消费者根据获取到实例信息的 Revision 参数进行分组,分别从每组实例中随机选择一台获取元数据服务。
  • 服务消费者通过 RPC 发起调用或者通过配置中心获取得到指定实例的元数据信息。
  • 服务消费者根据获取到的元数据信息组建接口粒度的服务信息。

关于应用级服务发现更多的信息可以参考:《Dubbo 迈出云原生重要一步 - 应用级服务发现解析》
Dubbo 3.0 前瞻之对接 Kubernetes 原生服务

对接实现方式

本次实现的与 Kubernetes 对接的方式有两种,一种是通过 Kubernetes API Client 的形式获取信息,另外一种是通过 DNS Client 的形式获取。

1. Kubernetes API Client

Kubernetes 控制平面的核心是 API Server,API Server 提供了 HTTP API,以供用户、集群中的不同部分和集群外部组件相互通信。对于 Dubbo 来说,通过使用 Kubernetes API Client 便可以做到与 Kubernetes 控制平面通信。

1)Provider 侧细节

Dubbo 3.0 前瞻之对接 Kubernetes 原生服务

根据前文说到 Dubbo 应用级服务发现模型,对于 Provider 侧在应用启动、接口更新时需要向注册中心写入 Revision 信息,因此大致的逻辑如上图所示。

Label 标签作为 Selector 与 Service 进行匹配,Annotation 中则主要存储了 Revision 等信息,其中 Revision 信息需要由 Dubbo 应用主动向 Kubernetes API Server 发起更新请求写入,这也对应了服务注册的流程。

在目前版本的实现中,Kubernetes Service 的创建工作是交由运维侧实现的,也即是 Label Selector 是由运维侧去管理的,在 Dubbo 应用启动前就已经配置完毕了,Service 的名字也即是对应接口注解中的 Services 字段(对于不依赖任何第三方配置中心的需要在接口级别手动配置此字段)。

2)Consumer 侧细节

Dubbo 3.0 前瞻之对接 Kubernetes 原生服务

对于 Consumer 侧的逻辑大致上与应用级服务发现的模型设计的一样,在通过 API 获取到服务信息后通过获取对应 Pod 的 Annotation 信息补齐 ServiceInstance 信息,后续逻辑与服务自省一致。

3)优缺点

  • 需要指出的是,让应用本身直接与 Kubernetes 管理平台的 API 交互本身就存在一定安全隐患,如果配置不当有一定可能性导致拖垮整个 Kubernetes 集群。
  • 当应用大量更新时会给 Kubernetes API Server 带来一定压力
  • 本方案直接将 Dubbo 的服务发现过程对接到 Kubernetes 集群的管理上,可以在 Kubernetes 环境下进一步简化管理的复杂度。

2. DNS Client

Kubernetes DNS 是 Kubernetes 提供的一种通过 DNS 查询的方式获取 Kubernetes Service 信息的机制,通过普通的 DNS 请求就可以获取到服务的节点信息。

1)全去中心化的元数据服务

由于 DNS 协议本身限制,目前并没有一个统一的较为简单的方式向 DNS 附加更多的信息用于写入 Revision 信息。对于 DNS 的实现方案我们将元数据服务进行了改造,使其不再强依赖往注册中心写入 Revision 信息,实现只需要知道 Dubbo 应用的 IP 即可以实现正常的服务发现功能。

Dubbo 3.0 前瞻之对接 Kubernetes 原生服务

改造后的元数据服务可以分为两大功能,一个是基于 revision 的获取 URL 信息等的能力,另外一个是获取对应 revision 的能力。Dubbo 服务消费者在通过注册中心获取到实例 IP 后会主动去与每一个实例的元数据服务进行连接,获取 revision 信息后,对于 revision 信息一致的实例随机选择一个去获取完整的元数据信息。
由于 Revision 信息采用了点对点的方式获取,当这个信息更新时要及时通知给消费者端进行更新。在当前版本的实现中,我们依赖了参数回调机制实现服务者主动推送给消费者。未来会基于 3.0 的全新 Triple 协议,实现流式推送。

2)Provider 侧细节

与 Kubernetes API Client 实现方式类似的,组建服务的过程均需要由运维侧进行,在服务提供者启动的时候会进行元数据信息本地缓存、对外暴露元数据服务接口的机制。

这里需要特别注意的是,为了使服务发现过程与业务服务本身解耦,元数据服务接口与业务接口对应的端口可以不一致,在使用 DNS 实现方案的时候全集群所有应用的元数据服务端口都需要统一, 通过 metadataServicePort 参数进行配置。这样亦可以适配那些不支持通过 SRV 获取端口的 DNS。

3)Consumer 侧细节

Dubbo 3.0 前瞻之对接 Kubernetes 原生服务

对于 DNS 注册中心来说,获取实例的流程主要通过向 DNS 发起 A (或 AAAA)查询请求来获取,而 Kubernetes DNS 也提供了 SRV 记录请求来获取服务的信息。

结合前文说到的全去中心化的元数据服务机制,Consumer 会去主动连接获取到的每一个实例的元数据服务,获取对应的 Revision 信息,同时以参数回调的形式向提供者提交回调函数用于更新本地信息。

在获取到 Revision 信息之后,对于具有相同 Revision 信息的实例,Dubbo 会随机选择其中一个获取完整元数据信息,至此完成服务发现的全过程。

4)优缺点

  • 本方案与前一种方案对比起来避免了 Kubernetes API 的直接交互,避免了交互的安全问题。
  • 由于 DNS 没有像 API Watch 的通知机制,只能采用轮询的方式判断服务的变更,在没有应用变更时集群内仍有一定量的 DNS 网络查询压力。
  • 本方案设计的时候目标是对任何只要能够通过 A (或 AAAA)查询获取到 Dubbo 应用本身的 IP 的 DNS 都可以适配,对 Kubernetes DNS 并不是强依赖关系。

总结

本次 Dubbo 对接 Kubernetes 原生服务是 Dubbo 往云原生化发展的一次尝试,未来我们将基于 xDS 协议实现与 Service Mesh 控制平面的交互,相关功能正在紧锣密鼓的筹划中。同时,在未来 Dubbo 3.0 的发版上,Java 社区和 Dubbo-go 社区将同步发版,本次 Kubernetes 的功能也将对齐上线。

系列文章推荐:

  • 《Dubbo 云原生之路:ASF 毕业一周年、3.0 可期》

  • 《Dubbo 迈出云原生重要一步 - 应用级服务发现解析》

  • 《Dubbo 3.0 前瞻:重塑 Spring Cloud 服务治理》

  • 《Dubbo 3.0 前瞻:常用协议对比及 RPC 协议新形态探索》

  • 《2020 双11,Dubbo3.0 在考拉的超大规模实践》

  • 《Dubbo 3.0 前瞻系列:服务发现支持百万集群,带来可伸缩微服务架构》

  • 《Dubbo 版 Swagger 来啦!Dubbo-Api-Docs 发布》

作者简介

江河清,Github 账号 AlbumenJ,Apache Dubbo Committer。在读本科生,目前主要参与 Dubbo 社区云原生 Kubernetes 和 Service Mesh 模块对接。