Dubbo-go k8s註冊中心設計方案與實現

隨着雲原生的推廣,越來越多的公司或組織將服務容器化,並將容器化後的服務部署在k8s集羣中。

k8s管理資源的哲學

k8s作爲容器集羣化管理方案可以將管理資源的維度可主觀的分爲服務實例管理和服務接入管理。

  • 服務實例管理,主要體現方式爲Pod設計模式加控制器模式。控制器保證具有特定標籤(Label)的Pod保持在恆定的數量(多刪,少補)。

  • 服務接入管理,主要爲Service,該Service默認爲具有特定標籤(Label)的一批Pod提供一個VIP(ClusterIP)作爲服務的接入點,默認會按照round-robin的負載均衡策略將請求轉發到真正提供服務的Pod。並且CoreDNS爲該Service提供集羣內唯一的域名。

k8s服務發現模型

爲了明確k8s在服務接入管理提供的解決方案,我們以kube-apiserver 提供的API(HTTPS)服務爲例。k8s集羣爲該服務分配了一個集羣內有效的ClusterIP,並通過CoreDNS爲其分配了唯一的域名 kubernetes 。

具體流程如上圖所示(紅色爲客戶端,綠色爲kube-apiserver):

  1. 首先客戶端通過CoreDNS解析域名爲kubernetes的服務獲得對應的ClusterIP爲10.96.0.1。

  2. 客戶端向10.96.0.1發起HTTPS請求。

  3. HTTPS之下的TCP連接被kube-proxy創建的iptables的PREROUTING鏈攔截並DNAT 爲 10.0.2.16或10.0.2.15。

  4. Client與最終提供服務的Pod建立連接並交互。

由此可見,k8s提供的服務發現爲域名解析級別。

Dubbo服務發現模型

同樣爲了明確Dubbo服務發現的模型,以一個簡單的Dubbo-Consumer發現並訪問Provider的具體流程爲例。

具體流程如上圖所示:

  1. Provider將本進程的元數據註冊到Registry中,包括IP,Port,以及服務名稱等。

  2. Consumer通過Registry獲取Provider的接入信息,直接發起請求

由此可見,Dubbo當前的服務發現模型是針對Endpoint級別的,並且註冊的信息不只IP和端口還包括其他的一些元數據。

K8s service vs dubbo-go 服務

通過上述兩個小節,答案基本已經比較清晰了。總結一下,無法直接使用k8s的服務發現模型的原因主要爲以下幾點:

  1. k8s的Service標準的資源對象具有的服務描述字段 中並未提供完整的Dubbo進程元數據字段因此,無法直接使用該標準對象進行服務註冊與發現。

  2. dubbo-do的服務註冊是基於每個進程的,每個Dubbo進程均需進行獨立的註冊。

  3. k8s的Service默認爲服務創建VIP,提供round-robin的負載策略也與Dubbo-go自有的Cluster模塊的負載策略形成了衝突。

Dubbo-go 當前的方案

服務註冊

K8s基於Service對象實現服務註冊/發現。可是dubbo現有方案爲每個dubbo-go進程獨立註冊,因此dubbo-go選擇將該進程具有的獨有的元數據寫入運行該dubbo-go進程的Pod在k8s中的Pod資源對象的描述信息中。每個運行dubbo進程的Pod將本進程的元數據寫入Pod的Annotations字段。爲了避免與其他使用Annotations字段的Operator或者其他類型的控制器(istio)的字段衝突。dubbo-go使用Key爲 dubbo.io/annotation value爲具體存儲的K/V對的數組的json編碼後的base64編碼。

樣例爲:

apiVersion: v1
kind: Pod
metadata:
  annotations:
    dubbo.io/annotation: W3siayI6Ii9kdWJibyIsInYiOiIifSx7ImsiOiIvZHViYm8vY29tLmlrdXJlbnRvLnVzZXIuVXNlclByb3ZpZGVyIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvY29uc3VtZXJzIiwidiI6IiJ9LHsiayI6Ii9kdWJibyIsInYiOiIifSx7ImsiOiIvZHViYm8vY29tLmlrdXJlbnRvLnVzZXIuVXNlclByb3ZpZGVyIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvcHJvdmlkZXJzIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvY29uc3VtZXJzL2NvbnN1bWVyJTNBJTJGJTJGMTcyLjE3LjAuOCUyRlVzZXJQcm92aWRlciUzRmNhdGVnb3J5JTNEY29uc3VtZXJzJTI2ZHViYm8lM0RkdWJib2dvLWNvbnN1bWVyLTIuNi4wJTI2cHJvdG9jb2wlM0RkdWJibyIsInYiOiIifV0=

由於每個dubbo-go的Pod均只負責註冊本進程的元數據,因此Annotations字段長度也不會因爲運行dubbo-go進程的Pod數量增加而增加。

服務發現

依賴kube-apiserver 提供了WATCH的功能。可以觀察特定namespace內各Pod對象的變化。dubbo-go爲了避免dubbo-go進程WATCH到與dubbo-go進程無關的Pod的變化,dubbo-go將WATCH的條件限制在當前Pod所在的namespace,以及僅WATCH具有Key爲 dubbo.io/label Value爲 dubbo.io-value 的Pod。在WATCH到對應Pod的變化後實時更新本地Cache,並通過Registry提供的Subscribe接口通知建立在註冊中心之上的服務集羣管理其他模塊。

總體設計圖

具體流程如上圖所示:

  1. 啓動dubbo-go的Deployment或其他類型控制器使用k8s Downward-Api將本Pod所在namespace通過環境變量的形式注入dubbo-go進程。

  2. Consumer/Provider進程所在的Pod啓動後通過環境變量獲得當前的namespace以及該Pod名稱, 調用kube-apiserver PATCH 功能爲本Pod添加Key爲dubbo.io/label Value爲 dubbo.io-value 的label。

  3. Consumer/Provider進程所在的Pod啓動後調用kube-apiserver將本進程的元數據通過PATCH接口寫入當前Pod的Annotations字段。

  4. Consumer進程通過kube-apiserver LIST 當前namespace下其他具有同樣標籤的Pod,並解碼對應的Annotations字段獲取Provider的信息。

  5. Consumer進程通過kube-apiserver WATCH 當前namespace下其他具有同樣label的Pod的Annotations的字段變化,動態更新本地Cache。

k8s已經爲其承載的服務提供了一套服務發現,服務註冊,以及服務集羣管理機制。而dubbo-go的同時也擁有自成體系的服務集羣管理。這兩個功能點形成了衝突,在無法調諧兩者的情況,dubbo-go團隊決定保持dubbo自有的服務集羣管理系,而選擇性的放棄了Service功能,將元數據直接寫入到Pod對象的Annotations中。

當然這只是dubbo-go在將k8s作爲服務註冊中心的方案之一,後續社區會以更加“雲原生”的形式對接k8s,讓我們拭目以待吧。

 動動小手指 瞭解更多詳情 !

相關文章