LINUX平臺的開源多層負載均衡

原標題《Multi-tier load-balancing with Linux》做者:Vincent Bernat 發佈時間:2018年5月23日前端

要提供高可用性和可擴展性服務的常看法決方案是接入負載平衡層,以將用戶的請求分配、轉發到後端服務器。原文注1咱們一般對負載均衡層有幾個目標:node

  • 可擴展性
    容許經過將流量推送到新配置的後端服務器來擴展服務。當性能成爲瓶頸時,可以自動擴展。
  • 可用性
    它爲服務提供高可用性。若是一臺服務器不可用,則應迅速將流量轉移至另外一臺服務器。負載均衡層自己也應具備高可用性。
  • 靈活性
    可以處理短鏈接和長鏈接。有足夠的靈活性來提供後端的全部特性,通常但願從負載均衡器中獲得TLS或HTTP路由等功能。
  • 可操做性
    接入負載均衡層後,如在後端推出一個新軟件,添加或刪除後端,或者擴展或縮小負載平衡層自己,這些預期的變化都應該讓服務保持連續性。

做者注1:在本文中,「 後端服務器 」是負載平衡層背後的服務器。爲避免混淆,咱們不會使用「 前端 」 一詞。python

問題及其解決方案衆所周知。參見本做者在本博客翻譯的文章《 網絡負載均衡和代理技術 》提供了對現有技術的概述。谷歌發佈了《Maglev:快速可靠的軟件網絡負載均衡器 》,詳細描述了他們的內部解決方案。基本上,使用商用服務器構建負載平衡解決方案包括組裝三個組件:nginx

  • ECMP路由
  • 無狀態L4負載平衡
  • 有狀態的L7負載平衡

    在本文中,我將描述並使用Linux和開源組件的多層解決方案。它是您構建生產環境負載均衡層的基礎。
    因爲原文注2中引入另外一篇文章,因此在翻譯期間,未保留原文注2
    譯註:文章做者的負載架構模型視角爲從上而下。負載均衡模型分別爲0層:DNS負載,第1層:ECMP路由;第二層:L4負載層;第三層:L7負載層,參考以下圖,從此將再也不贅述。git

LINUX平臺的開源多層負載均衡

第3層:L7負載均衡

它的做用是經過將請求轉發到健康的後端來提供高可用性,以及在它們之間均衡請求來提供可擴展性。L7在OSI 模型的最高層工做,它還能夠提供其餘服務,如TLS -termination,HTTP 路由,標頭重寫,未經身份驗證的用戶的速率限制等。做爲有狀態層,它能夠利用複雜的負載平衡算法。做爲與後端服務器的直連(譯註:邏輯上的直連),它可以減輕維護成本並最大限度地減小平常變化中的影響。
LINUX平臺的開源多層負載均衡
負載均衡解決方案第3層是一組L7負載均衡器,用於接收用戶鏈接並將其轉發到後端。L7:七層負載;www:表示後端服務github

L7負責終止與客戶端的TCP鏈接。這引入了負載均衡組件和後端之間的鬆耦合,具備如下優勢:web

  • 與後端建立鏈接並保持打開狀態,以下降資源使用率和延遲,
  • 若是鏈接失敗或中斷,請求能夠透明地重試
  • 客戶端與後端使用不一樣的IP協議
  • 後端沒必要關心路徑MTU發現,TCP擁塞控制算法,避免TIME - WAIT狀態和各類其餘底層(譯註:應用層如下)細節。
    許多開源軟件都適合這一層,而且有大量文章提到如何配置。你能夠看看HAProxy,Envoy或Træfik。如下是HAProxy的配置示例:
# 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

此配置是本指南中最不完整的一部分。可是,它說明了可操做(譯註:既服務連續)性的兩個關鍵概念:算法

  • Web服務器的健康檢查在HTTP -level(with check 和option httpchk)和使用輔助代理檢查(with agent-check)完成。後者使服務器易於維護或編排逐步提交。在每一個後端,須要一個進程偵聽端口5555和報告服務的狀態(UP在線, DOWN離線,MAINT維護)。一個簡單的socat過程能夠作到這一點:原文注3
    socat -ly \ 
    TCP6-LISTEN:5555,ipv6only = 0,reuseaddr,fork \ 
    OPEN:/ etc / lb / agent-check,rdonly

    當服務處於額定模式時,執行/etc/lb/agent-check檢查.若是常規健康檢查後端也正常,HAProxy將向此節點發送請求。當您須要維護時,請寫入MAINT(維護)並等待現有鏈接關閉。使用READY取消此模式。後端

  • 負載均衡器自己應該爲上層提供運行情況健康檢查點(/healthcheck)。若是沒有可用的後端服務器或經過代理檢查來設置啓用器後端,它將返回503錯誤。可使用與常規後端相同的機制來表示該負載均衡器的不可用性。

    做者注 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;
  }
}

也就是說,這個解決方案並不完整。咱們剛剛將可用性和可伸縮性問題轉移到其餘地方。咱們如何在負載均衡器之間對請求進行負載均衡?

第1層:ECMP路由

在大多數現代IP路由網絡上,客戶端和服務器之間存在冗餘路徑。對於每一個數據包,路由器必須選擇路徑。當關聯路徑成本相等時,輸入流原文注4在可用目的地之間進行負載均衡(鏈路負載)。此特性可用於均衡健康的負載均衡器之間的鏈接:
LINUX平臺的開源多層負載均衡
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路由層計劃性或意外的變化發生時,此解決方案不具備彈性。在添加或刪除負載均衡器時,目標的可用路由數會發生變化。路由器使用的散列算法不一致,流量在可用的負載均衡器之間從新分配,破壞了現有的鏈接:
LINUX平臺的開源多層負載均衡
發生變動時,ECMP路由不穩定。將額外的負載均衡器添加到池中,並將數據包路由到不一樣的負載均衡器,這些負載均衡器在其路由表中沒有相應的記錄
並且,每一個路由器能夠選擇本身的路由。當一個路由器變得不可用時,另外一個路由器將相同的流路由到不一樣路徑:
LINUX平臺的開源多層負載均衡
部分路由器變得不可用,其他路由器負載均衡其流量。其中一個路由到不一樣的負載均衡器,其路由表中沒有相應的記錄。
若是您認爲這不是可接受的結果,特別是若是您須要處理文件下載,視頻流或websocket等長鏈接,則須要額外的層。繼續閱讀!

第2層:L4負載均衡

第2層是IP路由的無狀態和L7負載均衡的有狀態之間的粘合劑。它經過L4負載平衡實現。粘合劑這個術語可能有點使人困惑:此層IP路由數據報(無 TCP終止),但L4負載均衡這個調度使用目標IP和端口來選擇可用的L7負載均衡器。此層的目的是確保全部成員對傳入數據包採起相同的調度策略。
有兩種選擇:

  • 狀態L4負載均衡與成員之間的狀態同步
  • 具備一致哈希的無狀態L4負載平衡
    第一種選擇會增長複雜性並限制可擴展性。咱們不會用它。原文注5第二種選擇在某些變化期間彈性較差,但可使用本地狀態的混合方法進行加強。

    做者注五:在Linux上,它能夠經過使用Netfilter實現負載平衡並使用conntrackd來實現同步狀態。IPVS僅提供主動/備份同步。

咱們使用IPVS,一個在Linux內核中運行的高性能L4負載均衡器,使用Keepalived,一個IPVS的前端,帶有一組健康檢查程序來啓動一個不健康的組件。IPVS配置爲使用Maglev調度程序,這是Google提供的一致哈希算法。在它的系列中,這是一個很好的算法,由於它能夠均衡地傳播鏈接,最大限度地減小更改期間的中斷,而且在構建查找表時很是快。最後,爲了提升性能,咱們讓第3層--L7負載均衡器 - 直接向客戶端應答,而不涉及第2層--L4負載均衡器。這被稱爲直接服務器返回(DSR)或直接路由( DR)。
LINUX平臺的開源多層負載均衡
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配置中,您應該:

  • 不使用 inhibit_on_failure
  • 使用 sh-port
  • 不使用 sh-fallback
    DSR使用隧道模式實現。此方法與路由數據中心和雲環境兼容。使用 IPIP封裝將請求隧道傳輸到調度的對等方。它增長了一小部分開銷,可能致使 MTU 問題。若是可能,請確保使用更大的 MTU進行第二層和第三層之間的通訊。原文注7不然,最好明確容許 IP數據包碎片:
    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層增長了此設置的彈性,緣由有兩個:

  1. 調度算法使用一致的哈希來選擇其目的地。這種算法經過最小化移動到新目的地的數據包數量來減小預期或意外變化的負面影響。「 Consistent Hashing:Algorithmic Tradeoffs 」提供了有關此主題的更多詳細信息。
  2. IPVS爲已知流保留本地鏈接表。當更改僅影響第3層時,將根據鏈接表正肯定引導現有流。

若是咱們添加或刪除L4負載均衡器,則現有流量不會受到影響,由於每一個負載均衡器都會採起相同的策略,只要存在同一組L7負載均衡器:

LINUX平臺的開源多層負載均衡
丟失的L4負載均衡器對現有流量沒有影響。每一個箭頭都是流程的一個例子。圓點表示綁定到關聯負載均衡器的流端點。若是他們已經轉移到另外一個負載均衡器,則鏈接將丟失。
若是咱們添加L7負載均衡器,則現有流量也不會受到影響,由於只會安排新鏈接。對於現有鏈接,IPVS將查看其本地鏈接表並繼續將數據包轉發到原始目標。一樣,若是咱們刪除L7負載均衡器,則只會影響在此負載均衡器處終止的現有流。其餘現有鏈接將正確轉發:
LINUX平臺的開源多層負載均衡
丟失L7負載平衡器只會影響綁定到它的流量。
只有在兩層(L4和L7)上同時變動才能產生明顯的影響。例如,當同時添加L4負載均衡器和L7負載均衡器時,只有鏈接到L4負載均衡器且沒有狀態並安排到新負載均衡器的鏈接將被中斷。因爲採用了一致的散列算法,其餘鏈接將保持與正確的L7負載均衡器綁定。在有計劃的變動期間,能夠經過先添加新的L4負載平衡器,等待幾分鐘,而後添加新的L7負載平衡器來最小化鏈接中斷。
LINUX平臺的開源多層負載均衡
L4負載均衡器和L7負載均衡器都恢復了。一致的哈希算法確保只有五分之一的現有鏈接將被轉移到傳入的L7負載均衡器。其中一些繼續經過其原始的L4負載平衡器進行路由,從而減輕了影響。
此外,IPVS正確地將ICMP消息路由到與關聯鏈接相同的L7負載均衡器。這確保了路徑MTU 發現的顯着效果,而且不須要智能解決方法。

第0層:DNS負載均衡

這實際上是一個可選層。您能夠將DNS負載均衡添加到負載架構中。若是您的環境跨越多個數據中心或多個雲區域,或者您但願將大型負載均衡集羣分解爲較小的集羣,則此功能很是有用。但它不能替代第1層,由於它具備不一樣的特色:沒有負載均衡的特性(它不是基於流的),而且從故障中恢復很慢。
LINUX平臺的開源多層負載均衡
跨兩個數據中心的完整負載平衡解決方案。
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負載平衡器帶來穩定性,這轉化爲彈性。

相關文章
相關標籤/搜索