spring-cloud-kubernetes背後的三個關鍵知識點

《你好spring-cloud-kubernetes》一文中,對spring-cloud-kubernetes這個SpringCloud官方kubernetes服務框架有了基本瞭解,今天來小結此框架涉及的關鍵技術,爲後面的深刻學習作準備;java

系列文章列表

本文是《spring-cloud-kubernetes實戰系列》的第三篇,全文連接以下:mysql

  1. 《spring-cloud-kubernetes官方demo運行實戰》
  2. 《你好spring-cloud-kubernetes》
  3. 《spring-cloud-kubernetes背後的三個關鍵知識點》
  4. 《spring-cloud-kubernetes的服務發現和輪詢實戰(含熔斷)》
  5. 《spring-cloud-kubernetes與SpringCloud Gateway》
  6. 《spring-cloud-kubernetes與k8s的configmap》

概覽

總結下來有三個關鍵知識點須要深刻理解:git

  1. DiscoveryClient是個接口,對應的實現類是哪一個?
  2. discoveryClient.getServices()方法取得了kubernetes的service信息,這背後的機制是什麼?java應用是怎樣取得所在kubernetes的服務信息的?
  3. kubernetes的service信息存在哪裏?如何將這些信息給出去?

接下來咱們逐一分析每一個知識點;程序員

DiscoveryClient接口的實現類實例從何而來

先來回顧一下上一章的DiscoveryController.java的內容:github

@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從何而來?spring

這一切,要從DiscoveryController.java所在項目的pom.xml提及;sql

  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: 在這裏插入圖片描述
  2. 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加載過程》
  3. spring.factories文件中有兩個類:KubernetesDiscoveryClientAutoConfiguration和KubernetesDiscoveryClientConfigClientBootstrapConfiguration都會被實例化;
  4. 先看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源碼,注意<font color="blue">kubernetesDiscoveryClient</font>方法,這裏面實例化了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實例;

接下來看第二個問題;shell

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());
    }

如上所示,<font color="blue">discoveryClient.getServices()</font>方法返回了全部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());
	}

這段代碼的關鍵在於<font color="blue">this.kubernetesClientServicesFunction.apply(this.client).list()</font>,先看KubernetesClientServicesFunction實例的初始化過程,在KubernetesDiscoveryClientAutoConfiguration類中:api

@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中的標籤選擇器的效果)

所以,數據來源其實就是上面的<font color="blue">this.client</font>,調用其services方法的返回結果; 3. KubernetesDiscoveryClient.getServices方法中的<font color="blue">this.client</font>是什麼呢?分析前面的問題時已經提到過了,就是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);
    }
  }

可見最終的地址應該是:<font color="blue">xxxxxx/api/v1</font>或者<font color="blue">xxxxxx/apis/xx/v1</font>這樣的字符串。

這樣的字符串意味着什麼呢?<font color="red">這是訪問kubernetes的API Server時用到的URL標準格式</font>,有關API Server服務的詳情請參考官方文檔,地址是:https://kubernetes.io/docs/reference/using-api/api-concepts/

以下圖,用OperationSupport類的源碼和官方文檔的URL截圖作個對比,你們就一目瞭然了: 在這裏插入圖片描述 5. 還剩個小問題,上圖中,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資源的數據列表;

接下來,該最後一個問題了;

API Server收到請求後作了什麼?

關於API Server如何響應各種http請求,本文只作一些簡單的說明,詳細信息還請參考官方文檔,地址是:https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/

以下圖所示,在kubernetes環境中,pod、service這些資源的數據都存儲在etcd,任何服務想要增刪改查etcd的數據,都只能經過向API Server發起RestFul請求的方式來完成,我們的DiscoveryController類獲取全部service也是發請求到API Server,由API Server從etcd中取得service的數據返回給DiscoveryController: 在這裏插入圖片描述

若是您想弄清楚service數據在etcd中如何存儲的,能夠參考《查看k8s的etcd數據》一文,親自動手鍊接etcd查看裏面的service內容;

至此,spring-cloud-kubernetes背後的三個關鍵知識點都已經學習了,下圖算是對這些問題的一個小結:

在這裏插入圖片描述

但願以上的分析總結能對您有參考做用,因爲對基本原理都已經瞭解,後面的spring-cloud-kubernetes實戰能夠更順暢,也能從原理出發繼續深刻的分析和學習。

歡迎關注個人公衆號:程序員欣宸

在這裏插入圖片描述

相關文章
相關標籤/搜索