写点什么

spring-cloud-kubernetes 背后的三个关键知识点

  • 2021 年 11 月 11 日
  • 本文字数:3250 字

    阅读完需:约 11 分钟

@RestController


public class DiscoveryController {


@Autowired


private DiscoveryClient discoveryClient;


/**


  • 探针检查响应类

  • @return


*/


@RequestMapping("/health")


public String health() {


return "health";


}


/**


  • 返回远程调用的结果

  • @return


*/


@RequestMapping("/getservicedetail")


public String getUri(


@RequestParam(value = "servicename", defaultValue = "") String servicename) {


return "Service [" + servicename + "]'s instance list : " + JSON.toJSONString(discoveryClient.getInstances(servicename));


}


/**


  • 返回发现的所有服务

  • @return


*/


@RequestMapping("/services")


public String services() {


return this.discoveryClient.getServices().toString()


  • ", "

  • new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());


}


}


上述代码中,我们并没有写创建 DiscoveryClient 实例的代码,discoveryClient 从何而来?


这一切,要从 DiscoveryController.java 所在项目的 pom.xml 说起;


  1. 在 pom.xml 中,有对 spring-cloud-kubernetes 框架的依赖配置:


<dependency>


<groupId>org.springframework.cloud</groupId>


<artifactId>spring-cloud-kubernetes-discovery</artifactId>


<version>1.0.1.RELEASE</version>


</dependency>


  1. 打开 spring-cloud-kubernetes-discovery 的源码,地址是:https://github.com/spring-cloud/spring-cloud-kubernetes/tree/master/spring-cloud-kubernetes-discovery ,在这个工程中发现了文件 spring.factories:



  1. spring 容器启动时,会寻找 classpath 下所有 spring.factories 文件(包括 jar 文件中的),spring.factories 中配置的所有类都会实例化,我们在开发 springboot 时常用到的 XXX-starter.jar 就用到了这个技术,效果是一旦依赖了某个 starter.jar 很多功能就在 spring 初始化时候自动执行了(例如 mysql 的 starter,启动时会连接数据库),关于此技术的详情,请参考以下三篇文章:


《自定义spring boot starter三部曲之一:准备工作》


《自定义spring boot starter三部曲之二:实战开发》


《自定义spring boot starter三部曲之三:源码分析spring.factories加载过程》


  1. spring.factories 文件中有两个类:KubernetesDiscoveryClientAutoConfiguration 和 KubernetesDiscoveryClientConfigClientBootstrapConfiguration 都会被实例化;

  2. 先看 KubernetesDiscoveryCli


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


entConfigClientBootstrapConfiguration,很简单的源码,KubernetesAutoConfiguration 和 KubernetesDiscoveryClientAutoConfiguration 这两个类会被实例化:


/**


  • Bootstrap config for Kubernetes discovery config client.

  • @author Zhanwei Wang


*/


@Configuration


@ConditionalOnProperty("spring.cloud.config.discovery.enabled")


@Import({ KubernetesAutoConfiguration.class,


KubernetesDiscoveryClientAutoConfiguration.class })


public class KubernetesDiscoveryClientConfigClientBootstrapConfiguration {


}


  1. 在 KubernetesAutoConfiguration 的源码中,会实例化一个重要的类:DefaultKubernetesClient,如下:


@Bean


@ConditionalOnMissingBean


public KubernetesClient kubernetesClient(Config config) {


return new DefaultKubernetesClient(config);


}


  1. 再看 KubernetesDiscoveryClientAutoConfiguration 源码,注意 kubernetesDiscoveryClient 方法,这里面实例化了 DiscoveryController 所需的 DiscoveryClient 接口实现,还要重点关注的地方是 KubernetesClient 参数的值,是上面提到的 DefaultKubernetesClient 对象:


@Bean


@ConditionalOnMissingBean


@ConditionalOnProperty(name = "spring.cloud.kubernetes.discovery.enabled", matchIfMissing = true)


public KubernetesDiscoveryClient kubernetesDiscoveryClient(KubernetesClient client,


KubernetesDiscoveryProperties properties,


KubernetesClientServicesFunction kubernetesClientServicesFunction,


DefaultIsServicePortSecureResolver isServicePortSecureResolver) {


return new KubernetesDiscoveryClient(client, properties,


kubernetesClientServicesFunction, isServicePortSecureResolver);


}


  1. 至此,第一个问题算是弄清楚了:我们编写的 DiscoveryController 类所需的 DiscoveryClient 接口实现类是 KubernetesDiscoveryClient,用到的是 spring 规范中的 spring.factories

  2. 另外有一点很重要,下面要用到的:KubernetesDiscoveryClient 有个成员变量是 KubernetesClient,该变量的值是 DefaultKubernetesClient 实例;


接下来看第二个问题;

java 应用怎么能取得所在 kubernetes 的服务信息

  1. 看看 DiscoveryController 是如何获取所在 kubernetes 的服务信息的:


@RequestMapping("/services")


public String services() {


return this.discoveryClient.getServices().toString()


  • ", "

  • new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());


}


如上所示,discoveryClient.getServices()方法返回了所有 kubernetes 的服务信息;


2. discoveryClient 对应的类是 spring-cloud-kubernetes 项目的 KubernetesDiscoveryClient.java,看方法:


public List<String> getServices(Predicate<Service> filter) {


return this.kubernetesClientServicesFunction.apply(this.client).list().getItems()


.stream().filter(filter).map(s -> s.getMetadata().getName())


.collect(Collectors.toList());


}


这段代码的关键在于 this.kubernetesClientServicesFunction.apply(this.client).list(),先看 KubernetesClientServicesFunction 实例的初始化过程,在 KubernetesDiscoveryClientAutoConfiguration 类中:


@Bean


public KubernetesClientServicesFunction servicesFunction(


KubernetesDiscoveryProperties properties) {


if (properties.getServiceLabels().isEmpty()) {


return KubernetesClient::services;


}


return (client) -> client.services().withLabels(properties.getServiceLabels());


}


KubernetesClientServicesFunction 是个 lambda 表达式,用于 KubernetesClient 的时候,返回 KubernetesClient.services()的结果,如果指定了标签过滤,就用指定的标签来做过滤(也就是 kubernetes 中的标签选择器的效果)


因此,数据来源其实就是上面的 this.client,调用其 services 方法的返回结果;


3. KubernetesDiscoveryClient.getServices 方法中的 this.client 是什么呢?分析前面的问题时已经提到过了,就是 DefaultKubernetesClient 类的实例,所以,此时要去看 DefaultKubernetesClient.services 方法,发现 client 是 ServiceOperationsImpl 实例:


@Override


public MixedOperation<Service, ServiceList, DoneableService, ServiceResource<Service, DoneableService>> services() {


return new ServiceOperationsImpl(httpClient, getConfiguration(), getNamespace());


}


  1. 接着看 ServiceOperationsImpl.java,我们关心的是它的 list 方法,此方法在父类 BaseOperation 中找到:


public L list() throws KubernetesClientException {


try {


HttpUrl.Builder requestUrlBuilder = HttpUrl.get(getNamespacedUrl()).newBuilder();


String labelQueryParam = getLabelQueryParam();


if (Utils.isNotNullOrEmpty(labelQueryParam)) {


requestUrlBuilder.addQueryParameter("labelSelector", labelQueryParam);


}


String fieldQueryString = getFieldQueryParam();


if (Utils.isNotNullOrEmpty(fieldQueryString)) {


requestUrlBuilder.addQueryParameter("fieldSelector", fieldQueryString);


}


Request.Builder requestBuilder = new Request.Builder().get().url(requestUrlBuilder.build());


L answer = handleResponse(requestBuilder, listType);


updateApiVersion(answer);


return answer;


} catch (InterruptedException | ExecutionException | IOException e) {


throw KubernetesClientException.launderThrowable(forOperationType("list"), e);


}

评论

发布
暂无评论
spring-cloud-kubernetes背后的三个关键知识点