java編程——基於Docker的負載均衡和服務發現

java編程——基於Docker的負載均衡和服務發現

Docker已經成為時下熱門的容器技術,各大公司,中小創業者,都選擇採用Docker技術架構其下一代的系統和應用。 隨著系統規模擴大,單一Docker容器不能應對壓力,需要橫向擴展到多個容器,我們需要考慮負載均衡的問題;單一職責的Docker容器之間需要相互通信,而容器在每一次重啟之後,它的IP都可能發生變化,那麼服務和服務之間如何發現對方? 這一次我們一起來交流基於Docker的負載均衡和服務發現。內容的主題涉及到容器集群入口流量的負載均衡方案,4層和7層協議負載的區別處理,容器集群內服務之間的互相發現等。

應用的容器化和微服務化帶來的問題

  • 在缺省網絡模型中,容器每次重啟後,IP會發生變動,在一個大的分佈式系統保證IP地址不變是比較複雜的事情。
  • IP頻繁發生變動,動態應用部署無法預知容器的IP地址,client端如何發現server端的訪問端點?

解決方案(根據客戶端是否有感知進行分類)

  • 客戶端的發現。client 訂閱註冊中心,有一個固定的註冊中心地址,client訂閱某個服務的註冊中心,註冊中心根據服務的運行狀態推送某個服務的訪問端點列表給client端。 該方案的實現舉例有Dubbo、DNS的解析等。
  • 服務端的發現。服務端提供某個服務固定的訪問端點,客戶端直接訪問該端點即可達到與服務端通信的目的,該訪問端口對接後端具有動態IP的容器,作為請求的入口,負責請求轉發到後端的容器。該方案的實現舉例就是各種對後端負載均衡的實現,包括LVS/Nginx/HAProxy等。我們可以認為如下公式基本成立:
  • “服務端的發現” = “負載均衡器” + “路由配置自動更新”

對比客戶端的發現,服務端發現對客戶端無感知,由於很多已有的應用或者系統並不是按照類似Dubbo這種服務化的框架實現的,這些應用或者系統的客戶端對服務發現都是無感知的,因此服務端的發現就表現出了獨特的優勢。客戶端的服務發現方法中,DNS是一個例外,幾乎所有的客戶端都支持DNS。下面介紹客戶端DNS和服務端的負載均衡器做服務發現的幾個方案。

服務發現方案

DNS解析到多個IP

  • 優點:Docker版本大於1.10即原生支持容器集群內部DNS的服務發現。
  • 缺點:由於DNS TTL生效時間的存在,解析的結果不能做到實時,即使TTL設置為0,某些應用或者方法庫會緩存DNS解析的結果,導致會解析到已經失效的地址上。

內核空間 LVS/IPVS

  • 優點:IPVS的方案是Docker在未來會正式發佈的1.12版本中採用的方案,主要是做到了4層的負載均衡,請求的轉發實現在內核中,不需要二次拷貝請求和響應的內容,不需要解析和處理7層HTTP協議,效率高。
  • 缺點:缺少7層負載均衡的支持,一個服務的負載均衡會佔用主機的一個端口,服務與服務之間暴露的端口如果相同會產生衝突。

用戶空間Nginx

  • 優點:同時支持4層和7層負載均衡的代理,多進程模型方便利用多核性能,具有cache功能,亦可作為靜態文件Web服務器,7層可以根據區分HOST字段來複用同一個80端口,解決端口衝突的問題。7層負載會解析HTTP協議,支持多層路由,包括根據域名不同進行路由,根據路徑不同進行路由,內部重定向等多種HTTP協議層的功能。
  • 缺點:負載均衡調度算法較少,對後端進行健康檢查的策略較少。

用戶空間 HAProxy

  • 優點:同時支持用戶空間4層和7層負載均衡的代理,是一個純粹的軟負載均衡器,支持Round-Robin,static-rr,least-conn,source-IP等多種調度算法,對後端進行健康檢查的策略完備,包括TCP端口檢查,HTTP請求檢查,可執行程序檢查等帶外檢查。7層可以根據區分HOST字段來複用同一個80端口,解決端口衝突的問題。7層負載會解析HTTP協議,支持多層路由,包括根據域名不同進行路由,根據路徑不同進行路由,內部重定向等多種HTTP協議層的功能。
  • 缺點:不能作為靜態文件服務器,不支持Cache緩存功能。

aliyun容器服務的負載均衡解決方案

經過前面優缺點的分析和結合aliyun自身產品的優勢,提供了以下解決方案:

  • Docker自帶的DNS 服務發現,只要Docker版本大於1.10(目前阿里雲支持的Docker最新版本為1.11),就支持DNS的服務發現。其特點是為:1)每個容器提供獨立的DNS解析;2)可以通過容器名與別名(Aliases)方式在整個網絡作用域內解析到某個容器的IP;3)通過鏈接別名的方式(Link Aliases)在容器的作用域內設置DNS解析,好處是不同容器的相同別名不會衝突。4)對外部的DNS解析請求進行代理。如下圖所示:
java編程——基於Docker的負載均衡和服務發現


  • 4層,提供阿里雲的雲產品SLB的負載均衡方案,底層是基於LVS做了改造和增強,SLB經受了長期線上實戰的考驗,穩定性和正確性可以得到保證。同時,容器服務在此基礎上能做到監控容器部署的情況,隨著服務的啟停,動態綁定SLB後端的端口(稍後解釋原理)。
  • 7層,提供基於HAProxy的負載均衡方案,支持靈活的配置。容器服務在此基礎上能做到監控容器部署的情況,隨著服務的健康狀況,動態調整後端的負載(稍後解釋原理)。
  • 雖然我們4層主推SLB來做負載均衡,同時7層主推HAProxy來做負載均衡,但是實際上SLB支持7層的負載均衡,底層基於Tengine,而HAProxy也支持用戶空間4層的負載均衡,用戶可以根據自己的情況,靈活選擇/組合方案。
  • SLB做到動態綁定的原理:Swarm監聽容器的狀態,如果容器正常運行,則把容器加入到SLB的後端,如果容器發現異常,則把容器從SLB的後端摘下來。
java編程——基於Docker的負載均衡和服務發現


  • HAProxy實現動態服務發現的原理:HAProxy容器內除了有HAProxy軟件,還有腳本程序監聽容器的狀態,根據容器的健康狀況重新生成負載均衡信息,然後重新加載(reload)HAProxy,使得新的負載均衡信息生效。
java編程——基於Docker的負載均衡和服務發現


  • 實現不停服rolling_update原理:平滑升級的關鍵在於每一時刻均有至少一個容器還能正常提供服務。1)需要部署多個容器,將容器分為A、B兩批更新。2)更新容器時,先將A批容器的路由從SLB或者HAProxy上面摘下來。3) 更新A批容器 4)A批容器健康檢查正常後,重新加入路由 5)摘下B批容器的路由 6)更新B批容器。
  • 實現灰度發佈原理:不通版本的服務可以共享同一路由信息,通過調整SLB或者HAProxy權重的方式來做到灰度發佈。
java編程——基於Docker的負載均衡和服務發現

根據場景提供給用戶的服務形態

簡單路由服務:基於HAProxy,我們加了一層Wrapper,做到動態發現處於運行狀態的容器,加入到負載均衡中,我們稱之為簡單路由服務(routing service),其公網IP通過一個SLB對外進行暴露。主要解決如下需求:

  • 7層服務端點對公網暴露,即承接公網訪問集群內使用7層協議的服務的流量。
  • 7層服務端點對內網暴露,即容器集群內的負載均衡和服務發現:如下圖所示,集群內的服務發現利用了Docker自帶的DNS resolver配合了HAProxy的負載均衡和健康檢查。圖中的LB即為簡單路由服務下的HAProxy容器,1)首先通過Docker自帶的DNS resolver將域名restserver.local解析到HAProxy容器的IP,此處會優先選擇當前節點的HAProxy容器進行負載均衡;2)RestClient請求域名restserver.local時,請求先走到代理容器LB;3)LB根據從Discovery Service獲取到的負載均衡信息代理到提供相應服務的容器後端RestServer Contaienr。
java編程——基於Docker的負載均衡和服務發現


  • 不支持4層的路由

自定義路由服務:作為一個可選的容器,實現跟簡單路由服務類似,解決如下需求:

  • 通過環境變量和標籤提供強大且靈活的配置支持,同時支持用戶基於該容器進行擴展(通過Dockerfile FROM的方式)
  • 支持用戶空間的4層協議轉發。

SLB路由服務:將SLB綁定到某個服務上面,後端隨服務的啟停動態配置。主要解決如下需求:

  • 對外暴露的服務,使用4層協議,通過自定義SLB做4層的轉發。
  • 容器間的4層或者7層通信,流量特別大時,推薦使用自定義SLB(內網)服務。

場景和對應的路由服務總結

java編程——基於Docker的負載均衡和服務發現

java編程——基於Docker的負載均衡和服務發現

如上述圖表所示,我們將容器集群外進入容器集群內的入口通信稱為南北通信,將集群內容器和容器之間的流量成為東西通信。我們根據不同的通信形式和協議層提供不同的服務來滿足用戶的需求,例如對應南北通信,如果是使用7層協議的服務,我們推薦用戶使用集群的SLB進行流量轉發,最終的流量會轉發到每個主機的HAProxy容器上面,然後在分發到相應的處理請求的服務上。

  • 南北通信:指的是整個容器集群入口的通信。南北通信的特點往往是通信量比較大,因此我們首先用SLB將流量分散到各個主機節點。
  • 東西通信:指的是集群內部,容器和容器間的通信方式。

Q&A

Q:同一個域名指向一臺主機內的容器服務,部署了n個相同的容器,怎麼根據他們的負載來分配到不同的容器中?

A:7層負載均衡麻煩參考下Nginx和HAProxy的使用方法即可,注意,我們的HAProxy容器跟其他目標服務的容器是直接在同一個網絡裡面的。你說的根據負載分配,可以使用最小連接數算法,即leastconn算法,需要查看HAProxy幫助手冊進行設置。

Q:Docker 1.2 IPVS的方案,是否可以實現完全取代Keepalived+Nginx的效果?

A:不能, IPVS是4層協議的,在很多場景下,需要7層協議的功能,例如共享同一個端口,提供7層協議解析,gzip壓縮,cookie注入,session保持等功能。

Q:有沒有評估過Kubernetes在host上用iptables做lb的方案?為什麼不用那種?

A:因為我們希望提供7層的路由和負載均衡,iptables的方案是基於4層的,不能共享同一個端口,不能提供7層協議解析,壓縮,cookie注入,session保持等功能。

Q:問下灰度發佈具體是怎麼實現的?

A:灰度發佈的具體實現方式如下: 例如有服務的兩個版本A 和 a,他們使用相同的路由,掛在同一個域名(例如www.example.com)的後端,但是A和a的權重不一樣,通過調整權重來做到灰度發佈,流量分配到不同的後端。

Q:請問haproxy訪問其它主機容器網絡怎樣通信?

A:一個容器集群有多臺機器,通過Overlay網絡,可以做到跨主機節點在同一個網絡中。

java編程——基於Docker的負載均衡和服務發現

java編程——基於Docker的負載均衡和服務發現


分享到:


相關文章: