spring-cloud-kubernetes 背后的三个关键知识点
@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 说起;
在 pom.xml 中,有对 spring-cloud-kubernetes 框架的依赖配置:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-discovery</artifactId>
<version>1.0.1.RELEASE</version>
</dependency>
打开 spring-cloud-kubernetes-discovery 的源码,地址是:https://github.com/spring-cloud/spring-cloud-kubernetes/tree/master/spring-cloud-kubernetes-discovery ,在这个工程中发现了文件 spring.factories:
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加载过程》
spring.factories 文件中有两个类:KubernetesDiscoveryClientAutoConfiguration 和 KubernetesDiscoveryClientConfigClientBootstrapConfiguration 都会被实例化;
先看 KubernetesDiscoveryCli
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 {
}
在 KubernetesAutoConfiguration 的源码中,会实例化一个重要的类:DefaultKubernetesClient,如下:
@Bean
@ConditionalOnMissingBean
public KubernetesClient kubernetesClient(Config config) {
return new DefaultKubernetesClient(config);
}
再看 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);
}
至此,第一个问题算是弄清楚了:我们编写的 DiscoveryController 类所需的 DiscoveryClient 接口实现类是 KubernetesDiscoveryClient,用到的是 spring 规范中的 spring.factories
另外有一点很重要,下面要用到的:KubernetesDiscoveryClient 有个成员变量是 KubernetesClient,该变量的值是 DefaultKubernetesClient 实例;
接下来看第二个问题;
java 应用怎么能取得所在 kubernetes 的服务信息
看看 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());
}
接着看 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);
}
评论