0
点赞
收藏
分享

微信扫一扫

SpringCloud和SpringBoot和k8S的融合

1. 为什么你需要 Spring Cloud Kubernetes?

Spring Cloud Kubernetes提供了众所周知的Spring Cloud接口的实现,允许开发者在Kubernetes上构建和运行Spring Cloud应用。虽然这个项目在构建云原生应用时可能对你有用,但它也不是在Kubernetes上部署Spring Boot应用的必要条件。如果你刚刚开始在Kubernetes上运行你的Spring Boot应用,你只需要一个基本的Spring Boot应用和Kubernetes本身就可以完成很多事情。

以上来自官方的描述,总结来说就是spring-cloud-kubernetes是springcloud官方推出的开源项目,用于将Spring Cloud和Spring Boot应用运行在kubernetes环境,并且提供了通用的接口来调用kubernetes服务,GitHub上官方地址是:https://github.com/spring-cloud/spring-cloud-kubernetes

2. Starter

Starter 是方便的依赖描述,你可以在你的应用程序中包含它。导入一个Starter,以获得功能集的依赖和Spring Boot自动配置。以 spring-cloud-starter-kubernetes-fabric8 开头的Starter提供了使用 Fabric8 Kubernetes Java 客户端 的实现。以 spring-cloud-starter-kubernetes-client 开头的Starter提供了使用 Kubernetes Java 客户端 的实现。

Starter

Features

Fabric8 依赖


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-fabric8</artifactId>
</dependency>


Kubernetes Client 依赖


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-client</artifactId>
</dependency>



Discovery Client 实现,将服务名称(service name)解析为Kubernetes服务。


Fabric8 依赖


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-fabric8-config</artifactId>
</dependency>


Kubernetes Client 依赖


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-client-config</artifactId>
</dependency>




从Kubernetes ConfigMap 和 Secret 加载应用application properties。当 ConfigMap 或 Secret发生变化时,重新加载 application properties。

Fabric8 依赖


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-fabric8-all</artifactId>
</dependency>


Kubernetes Client 依赖


<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes-client-all</artifactId>
</dependency>





所有 Spring Cloud Kubernetes 的特性。


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

(1) DiscoveryClient接口的实现类实例从何而来

DiscoveryController.java的内容如下

@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:

SpringCloud和SpringBoot和k8S的融合_Cloud

  1. spring容器启动时,会寻找classpath下所有spring.factories文件(包括jar文件中的),spring.factories中配置的所有类都会实例化,我们在开发springboot时常用到的XXX-starter.jar就用到了这个技术,效果是一旦依赖了某个starter.jar很多功能就在spring初始化时候自动执行了(例如mysql的starter,启动时会连接数据库)
  2. spring.factories文件中有两个类:KubernetesDiscoveryClientAutoConfiguration和KubernetesDiscoveryClientConfigClientBootstrapConfiguration都会被实例化;
  3. 先看KubernetesDiscoveryClientConfigClientBootstrapConfiguration,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);
}


因此,我们编写的DiscoveryController类所需的DiscoveryClient接口实现类是KubernetesDiscoveryClient,用到的是spring规范中的spring.factories。需要注意的是,KubernetesDiscoveryClient有个成员变量是KubernetesClient,该变量的值是DefaultKubernetesClient实例;


(2)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方法的返回结果;

  1. 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);
    }
  }


展开上面代码的handleResponse方法,可见里面是一次http请求,至于请求的地址,可以展开getNamespacedUrl()方法,里面调用的getRootUrl方法如下:

public URL getRootUrl() {
    try {
      if (apiGroup != null) {
        return new URL(URLUtils.join(config.getMasterUrl().toString(), "apis", apiGroup, apiVersion));
      }
      return new URL(URLUtils.join(config.getMasterUrl().toString(), "api", apiVersion));
    } catch (MalformedURLException e) {
      throw KubernetesClientException.launderThrowable(e);
    }
  }


可见最终的地址应该是:xxxxxx/api/v1或者xxxxxx/apis/xx/v1这样的字符串。

这样的字符串意味着什么呢?这是访问kubernetes的API Server时用到的URL标准格式,有关API Server服务的详情请参考官方文档,地址是:https://kubernetes.io/docs/reference/using-api/api-concepts/


SpringCloud和SpringBoot和k8S的融合_Cloud_02

上图中,OperationSupport类的成员变量resourceT是什么值?官方文档示例中是"pods",在获取service的时候又该是多少呢?顺着源码一路找下去,找到了类的构造方法,如下所示,第五个参数就是resourceT,这里直接被写死为"services":

public ServiceOperationsImpl(OkHttpClient client, Config config, String apiVersion, String namespace, String name, Boolean cascading, Service item, String resourceVersion, Boolean reloadingFromServer, long gracePeriodSeconds, Map<String, String> labels, Map<String, String> labelsNot, Map<String, String[]> labelsIn, Map<String, String[]> labelsNotIn, Map<String, String> fields) {
    super(client, config, null, apiVersion, "services", namespace, name, cascading, item, resourceVersion, reloadingFromServer, gracePeriodSeconds, labels, labelsNot, labelsIn, labelsNotIn, fields);
  }


因此,“controller中用到的kubernetes服务数据从何而来"已经清楚了:最终是调用okhttp的newCall方法向kubernetes的API Server发起http请求,获取service资源的数据列表;


(3)kubernetes的service信息存在哪里?如何将这些信息给出去?

在kubernetes环境中,pod、service这些资源的数据都存储在etcd,任何服务想要增删改查etcd的数据,都只能通过向API Server发起RestFul请求的方式来完成。DiscoveryController类获取所有service也是发请求到API Server,由API Server从etcd中取得service的数据返回给DiscoveryController


SpringCloud和SpringBoot和k8S的融合_spring_03





资料来源

  1. 集成k8s和spring cloud的利器:spring-cloud-kubernetes_大树叶的博客-CSDN博客
  2. Spring Cloud Kubernetes(K8s)的指南 - 掘金
  3. cloud.tencent.com
  4. 微服务 SpringCloud是什么以及和k8s关系_富士康质检员张全蛋的博客-CSDN博客
  5. 在K8S平台部署Spring Cloud微服务项目
  6. Spring Cloud + Kubernetes 微服务框架原理和实践
  7. docker+k8s+springcloud微服务集群部署实例
  8. 想了解 spring-cloud-kubernetes,那就先来实战一把官方demo - 掘金
  9. 部署微服务:Spring Cloud 和 Kubernetes 的比较 - 掘金
  10. 集成k8s和spring cloud的利器:spring-cloud-kubernetes_spring cloud kubernetes_大树叶的博客-CSDN博客
  11. 程序员都知道SpringCloud与Kubernetes可以进行生态融合吗?_springcloud kubernetes_该用户快成仙了的博客-CSDN博客
  12. Kubernetes(K8s)部署 SpringCloud 服务实战 - 追风人聊Java - 博客园
  13. spring-cloud-kubernetes之开发环境搭建-CSDN博客
  14. Spring Cloud Kubernetes 中文文档
  15. VMware安装配置Centos7(保姆级)_vmware centos7镜像-CSDN博客
  16. cloud.tencent.com
  17. developer.aliyun.com



举报

相关推荐

0 条评论