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

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

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

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

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

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

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

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

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

第3層:L7負載均衡

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

L7負責終止與客戶端的TCP連接。這引入了負載均衡組件和後端之間的鬆耦合,具有以下優點:

  • 與後端創建連接並保持打開狀態,以降低資源使用率和延遲,
  • 如果連接失敗或中斷,請求可以透明地重試
  • 客戶端與後端使用不同的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:如果您覺得這個解決方案很脆弱,請自行設計自己的代理方案。它可以與鍵值存儲協調以確定服務器的所需狀態。可以將代理集中在一個位置,但是您可能會遇到雞蛋共存問題以確保其可用性。

此外,該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負載平衡器帶來穩定性,這轉化爲彈性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章