首先為自己打個廣告,我目前在某互聯網公司做架構師,已經有5年經驗,每天都會寫架構師系列的文章,感興趣的朋友可以關注我和我一起探討,關注我,免費分享Java基礎教程,以及進階的高級Java架構師教程,全部免費送

概覽

總結下來有三個關鍵知識點需要深入理解:

1、DiscoveryClient是個介面,對應的實現類是哪個?

2、discoveryClient.getServices()方法取得了kubernetes的service信息,這背後的機制是什麼?java應用是怎樣取得所在kubernetes的服務信息的?

3、kubernetes的service信息存在哪裡?如何將這些信息給出去?

接下來我們逐一分析每個知識點;

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>

2、打開spring-cloud-kubernetes-discovery的源碼,地址是:github.com/spring-cloud ,在這個工程中發現了文件spring.factories:

3、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載入過程》

4、spring.factories文件中有兩個類:KubernetesDiscoveryClientAutoConfiguration和KubernetesDiscoveryClientConfigClientBootstrapConfiguration都會被實例化;

5、先看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 {
}

6、在KubernetesAutoConfiguration的源碼中,會實例化一個重要的類:DefaultKubernetesClient,如下:

@Bean
@ConditionalOnMissingBean
public KubernetesClient kubernetesClient(Config config) {
return new DefaultKubernetesClient(config);
}

7、再看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);
}

8、至此,第一個問題算是弄清楚了:我們編寫的DiscoveryController類所需的DiscoveryClient介面實現類是KubernetesDiscoveryClient,用到的是spring規範中的spring.factories

9、另外有一點很重要,下面要用到的: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());
}

4、接著看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服務的詳情請參考官方文檔,地址是:kubernetes.io/docs/refe

如下圖,用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請求,本文只做一些簡單的說明,詳細信息還請參考官方文檔,地址是:kubernetes.io/docs/refe

如下圖所示,在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實戰可以更順暢,也能從原理出發繼續深入的分析和學習。

以下是分享的部分架構師的學習資料和部分零基礎學習Java的視頻資料,附帶練習題和課堂筆記,需要的朋友可以私信我免費獲取

Java架構進階必備學習資源免費獲取?

shimo.im

推薦閱讀:

雁高飛:ActiveMQ|客戶端原理及源碼分析?

zhuanlan.zhihu.com
圖標
雁高飛:Spring Cloud、K8S、Netflix OSS三者啥關係????

zhuanlan.zhihu.com
圖標

推薦閱讀:
相關文章