Spring Cloud Kubernetes服务注册与发现的实现原理与源码分析

关注 “Java艺术” 我们一起成长!

Spring Cloud Kubernetes服务注册与发现的实现原理与源码分析_第1张图片

前面我们已经分析完OpenFeignRibbon的源码,包括两者的整合使用,以及Ribbon的重试机制,从最顶层调用接口开始到负载均衡的实现。今天我们分析更底层的实现,即服务注册与发现。

本篇内容包括:

  • Spring Cloud Commonsserviceregistrydiscovery

  • Spring Cloud Kubernetes服务注册与发现实现原理

  • Spring Cloud Kubernetes Core源码分析

  • Spring Cloud Kubernetes Discovery源码分析

使用Spring Cloud Kubernetes搭建的demo级微服务项目sck-demoGithub地址:

https://github.com/wujiuye/share-projects/tree/master/sck-demo

Spring Cloud Commons的serviceregistry与discovery

Spring Cloud CommonsSpring Cloud的核心组件,提供各种接口。其实就是通过定义接口来定义规范,方便将各种框架整合到Spring Cloud微服务项目中,这些功能或必须或可选。

例如,通过实现 Spring Cloud 定义的服务注册和发现接口,我们可以自己实现服务注册和发现以便接入新的服务注册中心,如 Nacos Apollo

Spring Cloud Commons只定义接口,不提供实现。

我们前面分析 Ribbon 源码时就接触了其中一个接口,负载均衡接口: LoadBalancerClient ,由 spring-cloud-netflix-ribbon 提供该接口的实现。

Spring Cloud Commonsserviceregistry包与discovery包中分别定义了服务注册接口、服务发现接口。

  • 服务注册接口ServiceRegistry:定义注册方法和注销方法;

  • 服务发现接口DiscoveryClient:定义获取所有服务ID方法以及根据服务ID获取所有实例(节点)方法。


Spring Cloud Kubernetes服务注册与发现实现原理

sck-demo项目搭建之初,我们是跟着官方提供的demo去实现服务注册和发现的,也就是在每个服务的Application类上添加一个@EnableDiscoveryClient注解。

我们并未配置 Kubernetes 的地址,但我们使用 DiscoveryClient 却能获取到服务节点。要解开这个疑惑我们需要了解 Kubernetes ,以及了解 Spring Cloud Kubernetes Discovery 的源码。

前面我们在分析Ribbon的源码时也了解到,Ribbon并非通过DiscoveryClient去获取服务提供者的。

Ribbon 通过提供一个 ServerList 接口让使用者自己去实现来完成 Ribbon 的服务发现。Ribbon定时调用ServerList更新自身缓存的服务提供者列表,默认30秒更新一次。 Spring Cloud Kubernetes Ribbon 的作用就是实现 Ribbon ServerList 接口,从 Kubernetes 获取可用的服务提供者。

实际上,Spring Cloud Kubernetes Ribbon也并未使用到Spring Cloud Kubernetes Discovery提供的DiscoveryClient接口的实现来获取服务列表,而是直接调用接口从Kubernetes中获取。

正是因为如此,笔者尝试去掉 @EnableDiscoveryClient 注解后,以及去掉 Spring Cloud Kubernetes Discovery 的依赖后,项目依然能正常运作。

需要注意,Spring Cloud Kubernetes Ribbon依赖Spring Cloud Kubernetes Core,如果去掉Spring Cloud Kubernetes Discovery,可能就要自动手动添加Spring Cloud Kubernetes Core的依赖,否则服务启动失败。

Spring Cloud Kubernetes Discovery实现DiscoveryClient接口只是能够让我们通过DiscoveryClient获取服务提供者。

SpringCloud Kubernetes Discovery实现的服务注册接口也并未真正的去注册服务。

可以这么说,在 Spring Cloud Kubernetes 项目中, Spring Cloud Kubernetes Discovery 是一个多余的存在。

如果去掉Spring Cloud Kubernetes Discovery后,我们想要获取某个服务的当前可用服务提供者怎么获取呢?

我们可以通过使用 Ribbon ServerList 去获取,由 Spring Cloud Kubernetes Ribbon 实现。

我们来做个实验:

第一步:排除spring-cloud-kubernetes-discovery依赖,并且去掉@EnableDiscoveryClient注解;


    org.springframework.cloud
    spring-cloud-starter-kubernetes
    
        
            org.springframework.cloud
            spring-cloud-kubernetes-discovery
        
    

第二步:从ServerList获取某个服务的当前可用服务提供者;

@RestController
@Slf4j
@RequestMapping
public class DemoTestController {

    @Resource
    private SpringClientFactory factory;

    @GetMapping("/ServerList")
    public Object test3() {
        return factory.getInstance(ProviderConstant.SERVICE_NAME, ServerList.class)
                .getUpdatedListOfServers();
    }

}

如上代码所示,想要取得ServerList就必须要通过SpringClientFactory去拿。

通过学习前面两篇 Ribbon 源码分析的文章我们知道, Ribbon 会为每个 Client 创建一个 ApplicationContext ,目的是实现环境隔离,让我们能够为调用每个服务提供者使用不同的配置。 因此,我们需要通过 SpringClientFactory 拿到对应 Client ApplicationContext ,再从该 ApplicationContext 中获取 ServerList 最后调用 ServerList getUpdatedListOfServers 方法从注册中心获取当前可以的服务列表。

测试结果如下图所示。

Spring Cloud Kubernetes服务注册与发现的实现原理与源码分析_第2张图片

那么问题来了,服务是怎么注册到注册中心的,以及Spring Cloud Kubernetes RibbonSpring Cloud Kubernetes Discovery又是怎么从注册中心拿到服务提供者列表的?

第一个问题:服务怎么注册到注册中心的?

服务不需要注册,使用Spring Cloud Kubernetes服务不需要注册!

Pod 启动起来时,就已经“注册”到 Kubernetes etcd 了,这便是使用 Spring Cloud Kubernetes 的好处,直接使用 Kubernetes 的原生服务实现服务发现和注册。

第二个问题:怎么从注册中心拿到服务提供者列表的?

我们从minikube(使用minikube搭建的本地单节点Kubernetes集群)的dashboard就能找到服务的节点信息,如下图所示。

Spring Cloud Kubernetes服务注册与发现的实现原理与源码分析_第3张图片

这些数据都是存储在Kubernetesetcd服务上的,Spring Cloud Kubernetes RibbonSpring Cloud Kubernetes Discovery都是通过KubernetesClientKubernetes交互,调用KubernetesAPI读取服务信息的。

  • Spring Cloud Kubernetes Ribbon的实现(以KubernetesEndpointsServerList为例)

public class KubernetesEndpointsServerList extends KubernetesServerList {

	KubernetesEndpointsServerList(KubernetesClient client,
			KubernetesRibbonProperties properties) {
		super(client, properties);
	}

	@Override
	public List getUpdatedListOfServers() {
	    List result = new ArrayList<>();
            // this.getClient().endpoints().withName(this.getServiceId()).get()
	    Endpoints endpoints = StringUtils.isNotBlank(this.getNamespace())
		? this.getClient().endpoints().inNamespace(this.getNamespace())
		    .withName(this.getServiceId()).get()
		: this.getClient().endpoints().withName(this.getServiceId()).get();
            // ....
	}
}
  • Spring Cloud Kubernetes Discovery的实现

public class KubernetesDiscoveryClient implements DiscoveryClient {

	private final KubernetesDiscoveryProperties properties;
	
	private KubernetesClient client;

        //......

	@Override
	public List getInstances(String serviceId) {
            // this.client.endpoints().withName(serviceId).get()
	    List endpointsList = this.properties.isAllNamespaces()
		? this.client.endpoints().inAnyNamespace()
		    .withField("metadata.name", serviceId).list().getItems()
		: Collections.singletonList(this.client.endpoints().withName(serviceId).get());
            // ......
	    return instances;
	}
}

所以两者都是调用KubernetesClientendpoints方法获取某个服务的节点信息。

想要了解 KubernetesClient 是怎么获取服务节点信息的,我们并不需要去看 KubernetesClient 的源码,也没必要, KubernetesClient 不过是封装了 http 请求。 想要了解 Kubernetes 提供的 API 可以看下这个文档:

https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#

对应本例的API如下图所示。

Spring Cloud Kubernetes服务注册与发现的实现原理与源码分析_第4张图片

想要试一下API?可以!

本地使用 kubectl proxy 命令就会运行一个 Kubernetes API 代理服务。

例如:

wjy$ kubectl proxy --port=8004
Starting to serve on 127.0.0.1:8004

使用kubectl proxy --port=8004开启Kubernetes API代理服务,监听请求的端口为8004。代理服务启动成功后,我们就可以使用127.0.0.1:8004访问KubernetesAPI了。

例如,获取sck-demo-provider这个服务的所有endpoints,在浏览器输入:

127.0.0.1:8004/api/v1/namespaces/default/endpoints/sck-demo-provider

其中default为名称空间,sck-demo-provider为服务名。响应结果如下图所示。

Spring Cloud Kubernetes服务注册与发现的实现原理与源码分析_第5张图片

调用这个接口就相当于使用kubectl get endpoints [服务名]命令。

wjy$ kubectl get endpoints sck-demo-provider
NAME                ENDPOINTS                                         AGE
sck-demo-provider   172.17.0.2:8080,172.17.0.4:8080,172.17.0.6:8080   8d

EndpointsPod是什么关系?Endpoints是个什么概念?

Pod Kubernetes 调度的最小单位,一个 Pod 中可以运行多个 Docker 容器,即运行多个应用,但一般只会运行一个容器。 Endpoints Pod 的关系是什么?

EndpointsKubernetes集群中的一个资源对象,存储在etcd中,用来记录一个Service对应的所有Pod的访问地址。

例如, Kubernetes 集群中创建一个名为 sck-demo-provider Service ,就会生成一个同名的 Endpoint 对象, Endpoint 就是 Service 关联的 Pod IP 地址和端口( Service 配置 selector ,否则不会生成 Endpoint 对象)。

Spring Cloud Kubernetes Core源码分析

该模块是Spring Cloud Kubernetes项目的核心模块,Spring Cloud Kubernetes的其它模块都依赖它实现与Kubernetes交互。

Spring Cloud Kubernetes Core主要实现自动配置KubernetesClientKubernetesClient封装与Kubernetes交互的API请求。

调用 KubernetesClient 的方法就是向 Kubernetes API 服务发起一个请求。

该模块的spring.factories文件配置了两个配置类:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.kubernetes.KubernetesAutoConfiguration\

org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.kubernetes.profile.KubernetesProfileEnvironmentPostProcessor
  • KubernetesAutoConfiguration

读取前缀为

spring.cloud.kubernetes.client的配置项,根据配置创建KubernetesClient

你是否有疑问,为什么我们在sck-demo项目中什么也没有配置(如kubernetes的 MasterUrl),却能启动服务,且能调用kubernetesAPI

当我们不配置KubernetesMasterUrl时,默认使用的是:

https://kubernetes.default.svc,且默认API版本为V1、默认不配置名称空间。

支持配置的参数可阅读

KubernetesClientProperties或者

io.fabric8.kubernetes.client.Config的源码。

Spring Cloud Kubernetes Core还实现spring-boot-actuate健康监控的HealthIndicator接口。

KubernetesHealthIndicator实现HealthIndicator接口的health方法,在方法中判断,如果能够获取当前Pod信息则说明服务正常。

KubernetesHealthIndicatorKubernetesAutoConfiguration中实例化并注册到Spring容器。

  • KubernetesProfileEnvironmentPostProcessor

这是一个EnvironmentPostProcessor

KubernetesProfileEnvironmentPostProcessor的作用是拦截Environment的初始化,如果当前应用是运行在容器内,则会调用EnvironmentaddActiveProfile方法追加"kubernetes"服务启动时会打印如下日记:

INFO  c.w.s.p.SckProviderApplication - The following profiles are active: kubernetes,dev


Spring Cloud Kubernetes Discovery源码分析

该模块实现Spring Cloud的服务发现接口DiscoveryClient,实现类为KubernetesDiscoveryClient

KubernetesDiscoveryClient使用KubernetesClient调用Kubernetes API获取Endpoints

同时也实现了服务注册接口ServiceRegistry,有发现就有注册。

ServiceRegistry的实现类为KubernetesServiceRegistry,该类实现接口的注册、注销方法,但仅仅只是打印个日记,什么也不做。

public class KubernetesServiceRegistry
		implements ServiceRegistry {

	private static final Log log = LogFactory.getLog(KubernetesServiceRegistry.class);

	@Override
	public void register(KubernetesRegistration registration) {
		log.info("Registering : " + registration);
	}

	@Override
	public void deregister(KubernetesRegistration registration) {
		log.info("DeRegistering : " + registration);
	}

	//.......
}

所以,Spring Cloud Kubernetes Discovery模块没什么好分析的。

下一篇分析 Spring Cloud Kubernetes 动态配置的实现源码与源码分析。

往期原创精选

Ribbon重试策略RetryHandler的配置与源码分析

OpenFeign与Ribbon源码分析总结(面试题)

Spring Cloud Ribbon源码分析(Spring Cloud Kubernetes)

Spring Cloud OpenFeign源码分析

Spring Cloud kubernetes入门项目sck-demo

为什么要选择Spring Cloud Kubernetes?

Spring Cloud Kubernetes服务注册与发现的实现原理与源码分析_第6张图片

[Java艺术] 微信号:javaskill

一个只推送原创文章的技术公众号,分享Java后端相关技术。

你可能感兴趣的:(Spring Cloud Kubernetes服务注册与发现的实现原理与源码分析)