spring-cloud-kubernetes背后的三個關(guān)鍵知識點
歡迎訪問我的GitHub

這里分類和匯總了欣宸的全部原創(chuàng)(含配套源碼):https://github.com/zq2599/blog_demos
本篇概覽
在《你好spring-Cloud-Kubernetes》一文中,對spring-Cloud-Kubernetes這個SpringCloud官方kubernetes服務(wù)框架有了基本了解,今天來小結(jié)此框架涉及的關(guān)鍵技術(shù),為后面的深入學(xué)習(xí)做準備;
概覽
總結(jié)下來有三個關(guān)鍵知識點需要深入理解:
DiscoveryClient是個接口,對應(yīng)的實現(xiàn)類是哪個?
discoveryClient.getServices()方法取得了kubernetes的service信息,這背后的機制是什么?java應(yīng)用是怎樣取得所在kubernetes的服務(wù)信息的?
kubernetes的service信息存在哪里?如何將這些信息給出去?
接下來我們逐一分析每個知識點;
DiscoveryClient接口的實現(xiàn)類實例從何而來
先來回顧一下上一章的DiscoveryController.java的內(nèi)容:
@RestController public class DiscoveryController { @Autowired private DiscoveryClient discoveryClient; /** * 探針檢查響應(yīng)類 * @return */ @RequestMapping("/health") public String health() { return "health"; } /** * 返回遠程調(diào)用的結(jié)果 * @return */ @RequestMapping("/getservicedetail") public String getUri( @RequestParam(value = "servicename", defaultValue = "") String servicename) { return "Service [" + servicename + "]'s instance list : " + JSON.toJSONString(discoveryClient.getInstances(servicename)); } /** * 返回發(fā)現(xiàn)的所有服務(wù) * @return */ @RequestMapping("/services") public String services() { return this.discoveryClient.getServices().toString() + ", " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); } }
上述代碼中,我們并沒有寫創(chuàng)建DiscoveryClient實例的代碼,discoveryClient從何而來?
這一切,要從DiscoveryController.java所在項目的pom.xml說起;
在pom.xml中,有對spring-cloud-kubernetes框架的依賴配置:
打開spring-cloud-kubernetes-discovery的源碼,地址是:https://github.com/spring-cloud/spring-cloud-kubernetes/tree/master/spring-cloud-kubernetes-discovery ,在這個工程中發(fā)現(xiàn)了文件spring.factories:
spring容器啟動時,會尋找classpath下所有spring.factories文件(包括jar文件中的),spring.factories中配置的所有類都會實例化,我們在開發(fā)springboot時常用到的XXX-starter.jar就用到了這個技術(shù),效果是一旦依賴了某個starter.jar很多功能就在spring初始化時候自動執(zhí)行了(例如mysql的starter,啟動時會連接數(shù)據(jù)庫),關(guān)于此技術(shù)的詳情,請參考以下三篇文章:
《自定義spring boot starter三部曲之一:準備工作》
《自定義spring boot starter三部曲之二:實戰(zhàn)開發(fā)》
《自定義spring boot starter三部曲之三:源碼分析spring.factories加載過程》
spring.factories文件中有兩個類:KubernetesDiscoveryClientAutoConfiguration和KubernetesDiscoveryClientConfigClientBootstrapConfiguration都會被實例化;
先看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 { }
在KubernetesAutoConfiguration的源碼中,會實例化一個重要的類:DefaultKubernetesClient,如下:
@Bean @ConditionalOnMissingBean public KubernetesClient kubernetesClient(Config config) { return new DefaultKubernetesClient(config); }
再看KubernetesDiscoveryClientAutoConfiguration源碼,注意
kubernetesDiscoveryClient
方法,這里面實例化了DiscoveryController所需的DiscoveryClient接口實現(xiàn),還要重點關(guān)注的地方是KubernetesClient參數(shù)的值,是上面提到的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接口實現(xiàn)類是KubernetesDiscoveryClient,用到的是spring規(guī)范中的spring.factories
另外有一點很重要,下面要用到的:KubernetesDiscoveryClient有個成員變量是KubernetesClient,該變量的值是DefaultKubernetesClient實例;
接下來看第二個問題;
java應(yīng)用怎么能取得所在kubernetes的服務(wù)信息
看看DiscoveryController是如何獲取所在kubernetes的服務(wù)信息的:
@RequestMapping("/services") public String services() { return this.discoveryClient.getServices().toString() + ", " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); }
如上所示,
discoveryClient.getServices()
方法返回了所有kubernetes的服務(wù)信息;
discoveryClient對應(yīng)的類是spring-cloud-kubernetes項目的KubernetesDiscoveryClient.java,看方法:
public List
這段代碼的關(guān)鍵在于
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()的結(jié)果,如果指定了標簽過濾,就用指定的標簽來做過濾(也就是kubernetes中的標簽選擇器的效果)
因此,數(shù)據(jù)來源其實就是上面的
this.client
,調(diào)用其services方法的返回結(jié)果;
KubernetesDiscoveryClient.getServices方法中的
this.client
是什么呢?分析前面的問題時已經(jīng)提到過了,就是DefaultKubernetesClient類的實例,所以,此時要去看DefaultKubernetesClient.services方法,發(fā)現(xiàn)client是ServiceOperationsImpl實例:
@Override public MixedOperation
接著看ServiceOperationsImpl.java,我們關(guān)心的是它的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()方法,里面調(diào)用的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); } }
可見最終的地址應(yīng)該是:
xxxxxx/api/v1
或者
xxxxxx/apis/xx/v1
這樣的字符串。
這樣的字符串意味著什么呢?
這是訪問kubernetes的API Server時用到的URL標準格式
,有關(guān)API Server服務(wù)的詳情請參考官方文檔,地址是:https://kubernetes.io/docs/reference/using-api/api-concepts/
如下圖,用OperationSupport類的源碼和官方文檔的URL截圖做個對比,大家就一目了然了:
還剩個小問題,上圖中,OperationSupport類的成員變量resourceT是什么值?官方文檔示例中是"pods",在獲取service的時候又該是多少呢?順著源碼一路找下去,找到了類的構(gòu)造方法,如下所示,第五個參數(shù)就是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
至此,第二個問題“controller中用到的kubernetes服務(wù)數(shù)據(jù)從何而來"已經(jīng)清楚了:最終是調(diào)用okhttp的newCall方法向kubernetes的API Server發(fā)起http請求,獲取service資源的數(shù)據(jù)列表;
接下來,該最后一個問題了;
API Server收到請求后做了什么?
關(guān)于API Server如何響應(yīng)各類http請求,本文只做一些簡單的說明,詳細信息還請參考官方文檔,地址是:https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/
如下圖所示,在kubernetes環(huán)境中,pod、service這些資源的數(shù)據(jù)都存儲在etcd,任何服務(wù)想要增刪改查etcd的數(shù)據(jù),都只能通過向API Server發(fā)起RestFul請求的方式來完成,咱們的DiscoveryController類獲取所有service也是發(fā)請求到API Server,由API Server從etcd中取得service的數(shù)據(jù)返回給DiscoveryController:
如果您想弄清楚service數(shù)據(jù)在etcd中如何存儲的,可以參考《查看k8s的etcd數(shù)據(jù)》一文,親自動手連接etcd查看里面的service內(nèi)容;
至此,spring-cloud-kubernetes背后的三個關(guān)鍵知識點都已經(jīng)學(xué)習(xí)了,下圖算是對這些問題的一個小結(jié):
希望以上的分析總結(jié)能對您有參考作用,由于對基本原理都已經(jīng)了解,后面的spring-cloud-kubernetes實戰(zhàn)可以更順暢,也能從原理出發(fā)繼續(xù)深入的分析和學(xué)習(xí)。
歡迎關(guān)注華為云博客:程序員欣宸
學(xué)習(xí)路上,你不孤單,欣宸原創(chuàng)一路相伴…
Kubernetes Spring Spring Cloud
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實后本網(wǎng)站將在24小時內(nèi)刪除侵權(quán)內(nèi)容。