原標題《Multi-tier load-balancing with Linux》做者:Vincent Bernat 發佈時間:2018年5月23日前端
要提供高可用性和可擴展性服務的常看法決方案是接入負載平衡層,以將用戶的請求分配、轉發到後端服務器。原文注1咱們一般對負載均衡層有幾個目標:node
做者注1:在本文中,「 後端服務器 」是負載平衡層背後的服務器。爲避免混淆,咱們不會使用「 前端 」 一詞。python
問題及其解決方案衆所周知。參見本做者在本博客翻譯的文章《 網絡負載均衡和代理技術 》提供了對現有技術的概述。谷歌發佈了《Maglev:快速可靠的軟件網絡負載均衡器 》,詳細描述了他們的內部解決方案。基本上,使用商用服務器構建負載平衡解決方案包括組裝三個組件:nginx
在本文中,我將描述並使用Linux和開源組件的多層解決方案。它是您構建生產環境負載均衡層的基礎。
因爲原文注2中引入另外一篇文章,因此在翻譯期間,未保留原文注2
譯註:文章做者的負載架構模型視角爲從上而下。負載均衡模型分別爲0層:DNS負載,第1層:ECMP路由;第二層:L4負載層;第三層:L7負載層,參考以下圖,從此將再也不贅述。git
它的做用是經過將請求轉發到健康的後端來提供高可用性,以及在它們之間均衡請求來提供可擴展性。L7在OSI 模型的最高層工做,它還能夠提供其餘服務,如TLS -termination,HTTP 路由,標頭重寫,未經身份驗證的用戶的速率限制等。做爲有狀態層,它能夠利用複雜的負載平衡算法。做爲與後端服務器的直連(譯註:邏輯上的直連),它可以減輕維護成本並最大限度地減小平常變化中的影響。
負載均衡解決方案第3層是一組L7負載均衡器,用於接收用戶鏈接並將其轉發到後端。L7:七層負載;www:表示後端服務github
L7負責終止與客戶端的TCP鏈接。這引入了負載均衡組件和後端之間的鬆耦合,具備如下優勢:web
# L7 load-balancer endpoint frontend l7lb # Listen on both IPv4 and IPv6 bind :80 v4v6 # Redirect everything to a default backend default_backend servers # Healthchecking acl dead nbsrv(servers) lt 1 acl disabled nbsrv(enabler) lt 1 monitor-uri /healthcheck monitor fail if dead || disabled # IPv6-only servers with HTTP healthchecking and remote agent checks backend servers balance roundrobin option httpchk server web1 [2001:db8:1:0:2::1]:80 send-proxy check agent-check agent-port 5555 server web2 [2001:db8:1:0:2::2]:80 send-proxy check agent-check agent-port 5555 server web3 [2001:db8:1:0:2::3]:80 send-proxy check agent-check agent-port 5555 server web4 [2001:db8:1:0:2::4]:80 send-proxy check agent-check agent-port 5555 # Fake backend: if the local agent check fails, we assume we are dead backend enabler server enabler [::1]:0 agent-check agent-port 5555
此配置是本指南中最不完整的一部分。可是,它說明了可操做(譯註:既服務連續)性的兩個關鍵概念:算法
socat -ly \ TCP6-LISTEN:5555,ipv6only = 0,reuseaddr,fork \ OPEN:/ etc / lb / agent-check,rdonly
當服務處於額定模式時,執行/etc/lb/agent-check檢查.若是常規健康檢查後端也正常,HAProxy將向此節點發送請求。當您須要維護時,請寫入MAINT(維護)並等待現有鏈接關閉。使用READY取消此模式。後端
做者注 3:若是您以爲這個解決方案很脆弱,請自行設計本身的代理方案。它能夠與鍵值存儲協調以肯定服務器的所需狀態。能夠將代理集中在一個位置,可是您可能會遇到雞蛋共存問題以確保其可用性。api
此外,該send-proxy指令使代理協議可以傳輸真實客戶端的IP地址。此協議也適用於非HTTP 鏈接,並受各類服務器支持,包括nginx:
http { server { listen [::]:80 default ipv6only=off proxy_protocol; root /var/www; set_real_ip_from ::/0; real_ip_header proxy_protocol; } }
也就是說,這個解決方案並不完整。咱們剛剛將可用性和可伸縮性問題轉移到其餘地方。咱們如何在負載均衡器之間對請求進行負載均衡?
在大多數現代IP路由網絡上,客戶端和服務器之間存在冗餘路徑。對於每一個數據包,路由器必須選擇路徑。當關聯路徑成本相等時,輸入流原文注4在可用目的地之間進行負載均衡(鏈路負載)。此特性可用於均衡健康的負載均衡器之間的鏈接:
ECMP路由用做第1層。流量分佈在可用的L7負載均衡器中。路由是無狀態和非對稱的。後端服務器未表示。
做者注:4:流一般由源和目標IP以及L4協議肯定。或者,也可使用源端口和目標端口。路由器對這些信息進行哈希處理以選擇目的地。對於Linux,您能夠在「 Celebrating ECMP in Linux.」中找到有關此主題的更多信息。
ECMP路由對負載均衡的控制不多,但能夠帶來了兩層(L7和ECMP)上水平擴展。實現這種解決方案的經常使用方法是使用BGP,一種路由協議來在網絡設備之間交換路由。每一個負載均衡器向其鏈接的路由器通告它所持有的服務IP地址。
假設您已經擁有支持BGP的路由器,ExaBGP是一種靈活的解決方案,可以讓負載均衡器發佈其可用性。如下是其中一個負載均衡器的配置:
# Healthcheck for IPv6 process service-v6 { run python -m exabgp healthcheck -s --interval 10 --increase 0 --cmd "test -f /etc/lb/v6-ready -a ! -f /etc/lb/disable"; encoder text; } template { # Template for IPv6 neighbors neighbor v6 { router-id 192.0.2.132; local-address 2001:db8::192.0.2.132; local-as 65000; peer-as 65000; hold-time 6; family { ipv6 unicast; } api services-v6 { processes [ service-v6 ]; } } } # First router neighbor 2001:db8::192.0.2.254 { inherit v6; } # Second router neighbor 2001:db8::192.0.2.253 { inherit v6; }
若是/etc/lb/v6-ready存在而/etc/lb/disable不存在,則將在兩個路由器上通知接口配置的全部IP地址lo。若是其餘負載均衡器使用相似的配置,則路由器將在它們之間分配輸入流。一些外部進程應該經過檢查負載平衡器的健康情況(例如,使用/healthcheck)來管理/etc/lb/v6-ready文件的存在。經過建立/etc/lb/disable文件。運維人員能夠從輪詢中刪除負載均衡器。
要了解有關此部分的更多詳細信息,請查看「 使用ExaBGP實現高可用性 」 。若是您位於雲中,則此層一般由您的雲提供商實施,使用任播IP地址或基本L4負載平衡器。
值得注意的是,一旦負載層或ECMP路由層計劃性或意外的變化發生時,此解決方案不具備彈性。在添加或刪除負載均衡器時,目標的可用路由數會發生變化。路由器使用的散列算法不一致,流量在可用的負載均衡器之間從新分配,破壞了現有的鏈接:
發生變動時,ECMP路由不穩定。將額外的負載均衡器添加到池中,並將數據包路由到不一樣的負載均衡器,這些負載均衡器在其路由表中沒有相應的記錄。
並且,每一個路由器能夠選擇本身的路由。當一個路由器變得不可用時,另外一個路由器將相同的流路由到不一樣路徑:
部分路由器變得不可用,其他路由器負載均衡其流量。其中一個路由到不一樣的負載均衡器,其路由表中沒有相應的記錄。
若是您認爲這不是可接受的結果,特別是若是您須要處理文件下載,視頻流或websocket等長鏈接,則須要額外的層。繼續閱讀!
第2層是IP路由的無狀態和L7負載均衡的有狀態之間的粘合劑。它經過L4負載平衡實現。粘合劑這個術語可能有點使人困惑:此層IP路由數據報(無 TCP終止),但L4負載均衡這個調度使用目標IP和端口來選擇可用的L7負載均衡器。此層的目的是確保全部成員對傳入數據包採起相同的調度策略。
有兩種選擇:
做者注五:在Linux上,它能夠經過使用Netfilter實現負載平衡並使用conntrackd來實現同步狀態。IPVS僅提供主動/備份同步。
咱們使用IPVS,一個在Linux內核中運行的高性能L4負載均衡器,使用Keepalived,一個IPVS的前端,帶有一組健康檢查程序來啓動一個不健康的組件。IPVS配置爲使用Maglev調度程序,這是Google提供的一致哈希算法。在它的系列中,這是一個很好的算法,由於它能夠均衡地傳播鏈接,最大限度地減小更改期間的中斷,而且在構建查找表時很是快。最後,爲了提升性能,咱們讓第3層--L7負載均衡器 - 直接向客戶端應答,而不涉及第2層--L4負載均衡器。這被稱爲直接服務器返回(DSR)或直接路由( DR)。
IPVS和一致哈希的L4負載均衡做爲第1層和第2層之間的粘合劑。後端服務器已被省略。虛線表示返回數據包的路徑。
經過這樣的設置,咱們但願來自輸入流的數據包可以在兩個第1層(ECMP路由)之間自由移動,同時堅持使用相同的L7負載均衡器。
假設已經按照上一節中的描述配置了ExaBGP,那麼讓咱們從Keepalived的配置開始:
virtual_server_group VS_GROUP_MH_IPv6 { 2001:db8::198.51.100.1 80 } virtual_server group VS_GROUP_MH_IPv6 { lvs_method TUN # Tunnel mode for DSR lvs_sched mh # Scheduler: Maglev sh-port # Use port information for scheduling protocol TCP delay_loop 5 alpha # All servers are down on start omega # Execute quorum_down on shutdown quorum_up "/bin/touch /etc/lb/v6-ready" quorum_down "/bin/rm -f /etc/lb/v6-ready" # First L7 load-balancer real_server 2001:db8::192.0.2.132 80 { weight 1 HTTP_GET { url { path /healthcheck status_code 200 } connect_timeout 2 } } # Many others... }
quorum_up和quorum_down語句定義了當服務分別變爲可用和不可用要執行的命令。該 /etc/lb/v6-ready文件用做ExaBGP的信號,將服務IP地址通告給相鄰路由器。
此外,IPVS須要配置爲繼續路由來自另外一個L4負載均衡器的數據包。它還應該繼續從不可用的目的地路由數據包,以確保咱們能夠正確地轉包到L7負載均衡器。
# Schedule non-SYN packets sysctl -qw net.ipv4.vs.sloppy_tcp=1 # Do NOT reschedule a connection when destination # doesn't exist anymore sysctl -qw net.ipv4.vs.expire_nodest_conn=0 sysctl -qw net.ipv4.vs.expire_quiescent_template=0
Maglev 調度算法將在Linux 4.18中可用,這要歸功於 Inju Song。對於較舊的內核,我準備了一個backport。原文注6使用源哈希做爲調度算法會破壞設置的彈性。
做者注6:backport並不徹底等同於其原始版本。請務必檢查README文件以瞭解其中的差別。
簡而言之,在Keepalived配置中,您應該:
sysctl -qw net.ipv4.vs.pmtu_disc = 0
做者注7:IPv4至少1520,IPv6 1540。
您還須要配置L7負載平衡器來處理封裝流量:原文注8
做者注8:一樣的,這種配置是不安全的。您須要確保只有L4負載均衡器才能發送IPIP流量。
# Setup IPIP tunnel to accept packets from any source ip tunnel add tunlv6 mode ip6ip6 local 2001:db8 :: 192.0.2.132 ip link set dev tunlv6 ip addr add 2001:db8 :: 198.51.100.1/128 dev tunlv6
根據配置,第2層增長了此設置的彈性,緣由有兩個:
若是咱們添加或刪除L4負載均衡器,則現有流量不會受到影響,由於每一個負載均衡器都會採起相同的策略,只要存在同一組L7負載均衡器:
丟失的L4負載均衡器對現有流量沒有影響。每一個箭頭都是流程的一個例子。圓點表示綁定到關聯負載均衡器的流端點。若是他們已經轉移到另外一個負載均衡器,則鏈接將丟失。
若是咱們添加L7負載均衡器,則現有流量也不會受到影響,由於只會安排新鏈接。對於現有鏈接,IPVS將查看其本地鏈接表並繼續將數據包轉發到原始目標。一樣,若是咱們刪除L7負載均衡器,則只會影響在此負載均衡器處終止的現有流。其餘現有鏈接將正確轉發:
丟失L7負載平衡器只會影響綁定到它的流量。
只有在兩層(L4和L7)上同時變動才能產生明顯的影響。例如,當同時添加L4負載均衡器和L7負載均衡器時,只有鏈接到L4負載均衡器且沒有狀態並安排到新負載均衡器的鏈接將被中斷。因爲採用了一致的散列算法,其餘鏈接將保持與正確的L7負載均衡器綁定。在有計劃的變動期間,能夠經過先添加新的L4負載平衡器,等待幾分鐘,而後添加新的L7負載平衡器來最小化鏈接中斷。
L4負載均衡器和L7負載均衡器都恢復了。一致的哈希算法確保只有五分之一的現有鏈接將被轉移到傳入的L7負載均衡器。其中一些繼續經過其原始的L4負載平衡器進行路由,從而減輕了影響。
此外,IPVS正確地將ICMP消息路由到與關聯鏈接相同的L7負載均衡器。這確保了路徑MTU 發現的顯着效果,而且不須要智能解決方法。
這實際上是一個可選層。您能夠將DNS負載均衡添加到負載架構中。若是您的環境跨越多個數據中心或多個雲區域,或者您但願將大型負載均衡集羣分解爲較小的集羣,則此功能很是有用。但它不能替代第1層,由於它具備不一樣的特色:沒有負載均衡的特性(它不是基於流的),而且從故障中恢復很慢。
跨兩個數據中心的完整負載平衡解決方案。
gdnsd是一個具備集成健康檢查的權威DNS服務端。它可使用RFC 1035區域格式從主文件服務區域:
@ SOA ns1 ns1.example.org。1 7200 1800 259200 900 @ NS ns1.example.com。 @ NS ns1.example.net。 @ MX 10 smtp @ 60 DYNA multifo!web www 60 DYNA multifo!web smtp A 198.51.100.99
查詢制定的插件後,特殊的RR類型DYNA將返回A和AAAA記錄。在這裏,multifo插件實現了監控地址的全部活動故障轉移:
service_types => { web => { plugin => http_status url_path => /healthcheck down_thresh => 5 interval => 5 } ext => { plugin => extfile file => /etc/lb/ext def_down => false } } plugins => { multifo => { web => { service_types => [ ext, web ] addrs_v4 => [ 198.51.100.1, 198.51.100.2 ] addrs_v6 => [ 2001:db8::198.51.100.1, 2001:db8::198.51.100.2 ] } } }
在正常狀態下,A請求將返回兩個DNS解析198.51.100.1和 198.51.100.2。運行情況檢查失敗將相應地更新返回的值。還能夠經過修改/etc/lb/ext文件刪除記錄 。例如,使用如下內容,198.51.100.2將再也不進行發佈:
198.51.100.1 => UP 198.51.100.2 => DOWN 2001:db8 :: c633:6401 => UP 2001:db8 :: c633:6402 => UP
您能夠在GitHub存儲庫中找到全部配置文件和每一個層的設置 。若是要以較小的規模複製此設置,可使用localnode或網絡命名空間來整合第1層和第2層 。即便您不須要其花哨的負載均衡服務,您也應該保留最後一層(第3層):當後端服務器不斷變化時,L7負載平衡器帶來穩定性,這轉化爲彈性。