Kubernetes 是当前全球最流行的容器服务平台,在 Kubernetes 集群中,Dubbo 应用的部署方式每每须要借助第三方注册中心实现服务发现。Dubbo 与 Kubernetes 的调度体系的结合,可让本来须要管理两套平台的运维成本大大减低,并且 Dubbo 适配了 Kubernetes 原生服务也可让框架自己更加融入云原生体系。基于 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 体系结构内,为了更好地符合 Java 开发人员的编程习惯,Dubbo 底层以接口粒度做为注册对象。可是这个模型对如今主流的 Spring Cloud 注册模型和 Kubernetes Service 注册模型有很大的区别。框架
目前在非 Dubbo 体系外的注册模型主要是以服务粒度做为注册对象,为了打通 Dubbo 与其余体系之间的注册发现壁垒,Dubbo 在 2.7.5 版本之后引入了服务自省的架构,主要经过元数据服务实现从服务粒度到接口粒度的过渡。在 2.7.5 版本之后到 3.0 版本,服务自省模型进行了不少方面的优化,而且在生产环境下进行了验证。运维
元数据服务主要是存储了服务(Instance)与接口(Interface)的映射关系,经过将本来的写入到注册中心的接口信息抽象到元数据进行存储,一方面能够大大减小注册中心存储的数据量、下降服务更新时集群的网络通讯压力,另外一方面,实现了注册中心层面只存储应用粒度信息的目标,对齐了其余注册模型。ide
在服务自省模型中,服务提供者不只仅往注册中心写入当前实例的信息,还须要往(本地或者远程的)元数据服务写入暴露的服务 URL 信息等;而对于服务消费者,在从注册中心获取实例信息后,还须要(经过 RPC 请求内建或者中心化配置中心获取)元数据服务获取服务提供者的服务 URL 信息等来生成接口粒度体系下的接口信息。函数
Revision 信息是元数据服务引入的一种数据缓存机制,对于同一组应用不少状况下暴露的接口其实都是同样的,在进行服务(Instance)与接口(Interface)映射的时候会有许多重复的冗余数据,所以可使用相似对元数据信息进行 MD5 计算的方式来对实例自己加上版本号,若是多个实例的版本号一致能够认为它们的元数据信息也一致,那么只须要随机选择一台来获取元数据信息便可,能够实现把通行量从一组实例都须要通讯到只须要与一个实例通讯的压缩。微服务
如上图所示,服务消费者注册中心的工做机制能够总结为:
关于应用级服务发现更多的信息能够参考:《Dubbo 迈出云原生重要一步 - 应用级服务发现解析》
本次实现的与 Kubernetes 对接的方式有两种,一种是经过 Kubernetes API Client 的形式获取信息,另一种是经过 DNS Client 的形式获取。
Kubernetes 控制平面的核心是 API Server,API Server 提供了 HTTP API,以供用户、集群中的不一样部分和集群外部组件相互通讯。对于 Dubbo 来讲,经过使用 Kubernetes API Client 即可以作到与 Kubernetes 控制平面通讯。
根据前文说到 Dubbo 应用级服务发现模型,对于 Provider 侧在应用启动、接口更新时须要向注册中心写入 Revision 信息,所以大体的逻辑如上图所示。
Label 标签做为 Selector 与 Service 进行匹配,Annotation 中则主要存储了 Revision 等信息,其中 Revision 信息须要由 Dubbo 应用主动向 Kubernetes API Server 发起更新请求写入,这也对应了服务注册的流程。
在目前版本的实现中,Kubernetes Service 的建立工做是交由运维侧实现的,也便是 Label Selector 是由运维侧去管理的,在 Dubbo 应用启动前就已经配置完毕了,Service 的名字也便是对应接口注解中的 Services 字段(对于不依赖任何第三方配置中心的须要在接口级别手动配置此字段)。
对于 Consumer 侧的逻辑大体上与应用级服务发现的模型设计的同样,在经过 API 获取到服务信息后经过获取对应 Pod 的 Annotation 信息补齐 ServiceInstance 信息,后续逻辑与服务自省一致。
Kubernetes DNS 是 Kubernetes 提供的一种经过 DNS 查询的方式获取 Kubernetes Service 信息的机制,经过普通的 DNS 请求就能够获取到服务的节点信息。
因为 DNS 协议自己限制,目前并无一个统一的较为简单的方式向 DNS 附加更多的信息用于写入 Revision 信息。对于 DNS 的实现方案咱们将元数据服务进行了改造,使其再也不强依赖往注册中心写入 Revision 信息,实现只须要知道 Dubbo 应用的 IP 便可以实现正常的服务发现功能。
改造后的元数据服务能够分为两大功能,一个是基于 revision 的获取 URL 信息等的能力,另一个是获取对应 revision 的能力。Dubbo 服务消费者在经过注册中心获取到实例 IP 后会主动去与每个实例的元数据服务进行链接,获取 revision 信息后,对于 revision 信息一致的实例随机选择一个去获取完整的元数据信息。
因为 Revision 信息采用了点对点的方式获取,当这个信息更新时要及时通知给消费者端进行更新。在当前版本的实现中,咱们依赖了 参数回调 机制实现服务者主动推送给消费者。将来会基于 3.0 的全新 Triple 协议,实现流式推送。
与 Kubernetes API Client 实现方式相似的,组建服务的过程均须要由运维侧进行,在服务提供者启动的时候会进行元数据信息本地缓存、对外暴露元数据服务接口的机制。
这里须要特别注意的是,为了使服务发现过程与业务服务自己解耦,元数据服务接口与业务接口对应的端口能够不一致,在使用 DNS 实现方案的时候全集群全部应用的元数据服务端口都须要统一, 经过 metadataServicePort 参数进行配置。这样亦能够适配那些不支持经过 SRV 获取端口的 DNS。
对于 DNS 注册中心来讲,获取实例的流程主要经过向 DNS 发起 A (或 AAAA)查询请求来获取,而 Kubernetes DNS 也提供了 SRV 记录请求来获取服务的信息。
结合前文说到的全去中心化的元数据服务机制,Consumer 会去主动链接获取到的每个实例的元数据服务,获取对应的 Revision 信息,同时以参数回调的形式向提供者提交回调函数用于更新本地信息。
在获取到 Revision 信息以后,对于具备相同 Revision 信息的实例,Dubbo 会随机选择其中一个获取完整元数据信息,至此完成服务发现的全过程。
本次 Dubbo 对接 Kubernetes 原生服务是 Dubbo 往云原生化发展的一次尝试,将来咱们将基于 xDS 协议实现与 Service Mesh 控制平面的交互,相关功能正在紧锣密鼓的筹划中。同时,在将来 Dubbo 3.0 的发版上,Java 社区和 Dubbo-go 社区将同步发版,本次 Kubernetes 的功能也将对齐上线。
系列文章推荐:
江河清,Github 帐号 AlbumenJ,Apache Dubbo Committer。在读本科生,目前主要参与 Dubbo 社区云原生 Kubernetes 和 Service Mesh 模块对接。