在現代分散式系統架構中,負載平衡技術扮演著不可或缺的角色,它不僅能夠有效分散流量至多台後端伺服器,還能提供高可用性與水平擴展能力。隨著網路應用規模的持續成長,單一伺服器早已無法滿足大量並發請求的處理需求,因此負載平衡成為建構高效能網路服務的基礎設施之一。
Linux 作為伺服器作業系統的主流選擇,提供了豐富的負載平衡解決方案,從核心層級的 Linux Virtual Server(LVS)到應用層的 HAProxy 與 Nginx,每種方案都有其適用場景與技術特點。深入理解這些技術的原理與實作細節,對於設計可靠、高效的網路架構至關重要。本文將系統性地介紹 Linux 環境下的負載平衡技術,從基本概念到進階組態,協助讀者掌握建構企業級負載平衡服務所需的知識與技能。
負載平衡基礎概念
負載平衡的核心目的是將進入的網路流量有效分配到多台後端伺服器,以避免單點過載,提升整體系統的處理能力、可用性與可靠性。在開始探討具體技術之前,我們需要先了解負載平衡架構中的基本術語與概念。
架構術語與 IP 位址處理
在負載平衡架構中,前端(Frontend)指的是面向客戶端的部分,負責接收來自使用者的請求;後端(Backend)則是面向伺服器的部分,負責將請求轉發給實際處理的伺服器。這種前後端分離的架構使得流量管理與伺服器擴展更加靈活。
IP 位址的處理是負載平衡架構中的關鍵議題。前端會呈現一個虛擬 IP(Virtual IP,VIP),這個 IP 位址由所有目標伺服器共享,客戶端透過這個單一位址存取服務。而後端伺服器各自擁有的實際 IP 位址稱為真實 IP(Real IP,RIP),這些位址對客戶端是透明的。這種設計允許管理員在不影響客戶端的情況下,自由地新增、移除或更換後端伺服器。
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
title 負載平衡基本架構
actor "客戶端" as Client
rectangle "負載平衡器" as LB {
[VIP: 203.0.113.100]
}
rectangle "後端伺服器群" as Backend {
[Server 1\nRIP: 10.0.0.11] as S1
[Server 2\nRIP: 10.0.0.12] as S2
[Server 3\nRIP: 10.0.0.13] as S3
}
Client --> LB : 請求 VIP
LB --> S1 : 轉發
LB --> S2 : 轉發
LB --> S3 : 轉發
@endumlOSI 模型與負載平衡層級
負載平衡可以在 OSI 模型的不同層級實作,每個層級都有其特點與適用場景。
第四層(傳輸層)負載平衡基於 TCP 或 UDP 協定進行流量分配,負載平衡器只檢查封包的來源與目的 IP 位址以及連接埠號碼。這種方式效率高、延遲低,適合需要高吞吐量的場景。然而,由於無法檢查應用層內容,第四層負載平衡無法根據 HTTP 請求內容進行智慧路由。
第七層(應用層)負載平衡則可以檢查完整的應用層協定內容,例如 HTTP 標頭、URL 路徑、Cookie 等。這使得負載平衡器能夠進行更精細的流量控制,如根據 URL 將請求路由到不同的後端服務群組,或根據 Cookie 實現會話黏著。然而,這種深度檢查也帶來更高的 CPU 負載。
選擇適當的負載平衡層級需要考慮應用需求、效能要求以及安全性考量。對於簡單的流量分配,第四層負載平衡通常足夠且更有效率;對於需要智慧路由或內容感知的場景,第七層負載平衡則是更好的選擇。
負載平衡方法詳解
Linux 環境下常見的負載平衡方法主要有三種:反向代理(Reverse Proxy)、網路位址轉換(NAT)以及直接伺服器回傳(Direct Server Return,DSR)。每種方法在效能、功能與適用場景上都有顯著差異。
反向代理負載平衡
反向代理是最常見的負載平衡方法之一,它作為客戶端與後端伺服器之間的中介,完全代理所有請求與回應。當客戶端發送請求時,請求首先到達反向代理;代理根據負載平衡演算法選擇一台後端伺服器,建立新的連線並轉發請求;後端伺服器處理請求後,將回應返回給代理,代理再將回應轉發給客戶端。
@startuml
!define DISABLE_LINK
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
title 反向代理負載平衡流程
actor "客戶端\n192.168.1.100" as Client
participant "反向代理\nVIP: 203.0.113.100\nBackend IP: 10.0.0.1" as Proxy
participant "後端伺服器\nRIP: 10.0.0.11" as Server
Client -> Proxy : 1. 請求\nSrc: 192.168.1.100\nDst: 203.0.113.100
Proxy -> Server : 2. 代理請求\nSrc: 10.0.0.1\nDst: 10.0.0.11
Server -> Proxy : 3. 回應\nSrc: 10.0.0.11\nDst: 10.0.0.1
Proxy -> Client : 4. 代理回應\nSrc: 203.0.113.100\nDst: 192.168.1.100
@enduml反向代理的主要優點在於其功能豐富性。由於代理可以完全控制請求與回應,它能夠提供多種進階功能,包括 SSL/TLS 終止、內容壓縮、快取、請求改寫以及應用層安全檢查等。此外,反向代理架構天生具有安全優勢,因為客戶端與後端伺服器之間完全隔離,後端伺服器只接收來自代理的合法連線,大幅降低了被直接攻擊的風險。
然而,反向代理也有其缺點。首先,所有流量都必須經過代理,這使得代理本身成為潛在的效能瓶頸。對於高流量的應用,代理的 CPU 和網路頻寬可能成為限制因素。其次,由於客戶端的連線被代理終止,後端伺服器在日誌中看到的來源 IP 將是代理的後端 IP,而非客戶端的真實 IP。這個問題可以透過設定 X-Forwarded-For 標頭來解決,但需要後端應用程式的配合。
以下是使用 Nginx 作為反向代理的基本組態範例:
# Nginx 反向代理負載平衡組態
# 此組態實現基本的 HTTP 負載平衡功能
# 定義後端伺服器群組
# upstream 區塊用於定義一組可以處理請求的後端伺服器
upstream backend_servers {
# 負載平衡演算法
# least_conn 表示將請求發送到當前連線數最少的伺服器
# 其他選項包括:
# - 不指定:預設使用 round-robin 輪詢
# - ip_hash:根據客戶端 IP 進行雜湊,實現會話黏著
# - hash:根據指定的鍵進行雜湊
least_conn;
# 定義後端伺服器
# server 指令指定每台後端伺服器的位址和連接埠
# weight 參數設定伺服器權重,權重越高分配的請求越多
# max_fails 設定在指定時間內允許的最大失敗次數
# fail_timeout 設定判定失敗的逾時時間,以及伺服器被標記為不可用的時間
server 10.0.0.11:8080 weight=3 max_fails=3 fail_timeout=30s;
server 10.0.0.12:8080 weight=2 max_fails=3 fail_timeout=30s;
server 10.0.0.13:8080 weight=1 max_fails=3 fail_timeout=30s;
# backup 伺服器只在所有主要伺服器都不可用時才會被使用
server 10.0.0.14:8080 backup;
# 啟用持久連線到後端伺服器
# 這可以減少建立新連線的開銷
keepalive 32;
# 設定 keepalive 連線的請求數量上限
keepalive_requests 1000;
# 設定 keepalive 連線的閒置逾時時間
keepalive_timeout 60s;
}
# HTTP 伺服器區塊
server {
# 監聽連接埠
listen 80;
listen [::]:80;
# 伺服器名稱
server_name app.example.com;
# 存取日誌
# 使用自訂格式記錄詳細的請求資訊
access_log /var/log/nginx/app_access.log combined;
error_log /var/log/nginx/app_error.log warn;
# 設定客戶端請求主體的最大大小
client_max_body_size 100m;
# 設定讀取客戶端請求主體的逾時時間
client_body_timeout 60s;
# 設定讀取客戶端請求標頭的逾時時間
client_header_timeout 60s;
# 設定傳送回應給客戶端的逾時時間
send_timeout 60s;
# 根路徑處理
location / {
# 將請求代理到後端伺服器群組
proxy_pass http://backend_servers;
# 設定代理相關的標頭
# Host 標頭傳遞原始請求的 Host
proxy_set_header Host $host;
# X-Real-IP 傳遞客戶端的真實 IP 位址
proxy_set_header X-Real-IP $remote_addr;
# X-Forwarded-For 傳遞完整的代理鏈
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# X-Forwarded-Proto 傳遞原始請求使用的協定
proxy_set_header X-Forwarded-Proto $scheme;
# 設定與後端伺服器的連線逾時時間
proxy_connect_timeout 30s;
# 設定從後端伺服器讀取回應的逾時時間
proxy_read_timeout 60s;
# 設定傳送請求到後端伺服器的逾時時間
proxy_send_timeout 60s;
# 設定用於從後端伺服器讀取回應的緩衝區大小
proxy_buffer_size 4k;
proxy_buffers 8 16k;
proxy_busy_buffers_size 32k;
# 啟用代理重試
# 當後端伺服器返回指定的錯誤時,嘗試下一台伺服器
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
# 設定重試的次數
proxy_next_upstream_tries 3;
# 設定重試的逾時時間
proxy_next_upstream_timeout 30s;
# 使用 HTTP/1.1 協定連線到後端
# 這是啟用 keepalive 所必需的
proxy_http_version 1.1;
# 清除 Connection 標頭以啟用 keepalive
proxy_set_header Connection "";
}
# 健康檢查端點
location /health {
access_log off;
return 200 'OK';
add_header Content-Type text/plain;
}
# 靜態檔案處理
# 靜態檔案可以直接由 Nginx 提供,減輕後端伺服器負擔
location /static/ {
alias /var/www/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
}
# HTTPS 伺服器區塊
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name app.example.com;
# SSL 憑證組態
ssl_certificate /etc/ssl/certs/app.example.com.crt;
ssl_certificate_key /etc/ssl/private/app.example.com.key;
# SSL 協定版本
# 只啟用 TLS 1.2 和 1.3,停用較舊的不安全版本
ssl_protocols TLSv1.2 TLSv1.3;
# 加密套件
# 使用現代安全的加密套件
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
# 優先使用伺服器端的加密套件順序
ssl_prefer_server_ciphers on;
# SSL 會話快取
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
# 安全標頭
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
# 其餘設定與 HTTP 伺服器相同
location / {
proxy_pass http://backend_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
網路位址轉換(NAT)負載平衡
NAT 負載平衡是另一種常見的方法,它在第四層運作,透過修改封包的目的 IP 位址來實現流量分配。當客戶端請求到達負載平衡器時,負載平衡器會將封包的目的 IP 從 VIP 改為選定後端伺服器的 RIP,然後將封包轉發出去。後端伺服器處理請求後,回應封包必須經過負載平衡器,負載平衡器再將來源 IP 從 RIP 改回 VIP,確保客戶端收到的回應來自正確的 IP 位址。
NAT 負載平衡的主要優點是效率較高,因為它只需要修改 IP 位址,不需要完全終止和重建連線。此外,後端伺服器可以看到客戶端的真實 IP 位址,方便日誌記錄和分析。NAT 負載平衡也支援 SSL/TLS 終止,可以在負載平衡器上解密流量,讓後端伺服器處理明文請求。
然而,NAT 負載平衡要求所有的回應流量都必須經過負載平衡器,因為只有負載平衡器知道如何將 RIP 轉換回 VIP。這意味著負載平衡器必須處理雙向流量,可能成為頻寬瓶頸。此外,後端伺服器的預設閘道必須設定為指向負載平衡器,這在某些網路拓撲中可能造成管理上的不便。
直接伺服器回傳(DSR)負載平衡
DSR 是一種高效能的負載平衡方法,特別適合回應流量遠大於請求流量的應用場景,如影音串流或大型檔案下載。在 DSR 架構中,請求流量透過負載平衡器分配到後端伺服器,但回應流量直接從後端伺服器返回客戶端,完全繞過負載平衡器。
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
title DSR 負載平衡流程
actor "客戶端\n192.168.1.100" as Client
participant "負載平衡器\nVIP: 203.0.113.100" as LB
participant "後端伺服器\nRIP: 10.0.0.11\nLoopback: 203.0.113.100" as Server
participant "路由器" as Router
Client -> LB : 1. 請求\nDst: 203.0.113.100
LB -> Server : 2. 修改 MAC 轉發\nDst IP 不變
Server -> Router : 3. 直接回應\nSrc: 203.0.113.100
Router -> Client : 4. 路由回應
note right of Server
後端伺服器在 loopback
介面上設定 VIP,
因此可以直接回應
end note
@endumlDSR 的工作原理依賴於第二層(資料連結層)的 MAC 位址修改。當負載平衡器收到客戶端請求時,它不修改封包的 IP 位址,而是將目的 MAC 位址改為選定後端伺服器的 MAC 位址,然後在同一子網中轉發封包。後端伺服器收到封包後,發現目的 IP 是自己的 loopback 介面上設定的 VIP,因此接受並處理請求。處理完成後,伺服器直接將回應發送給客戶端,來源 IP 設定為 VIP。
要實現 DSR,後端伺服器需要進行特殊組態。首先,必須在 loopback 介面上設定與 VIP 相同的 IP 位址。其次,必須確保伺服器不會對這個 IP 位址的 ARP 請求做出回應,否則會導致 IP 衝突和路由問題。在 Linux 系統中,這可以透過設定 ARP 參數來實現。
以下是在 Linux 後端伺服器上組態 DSR 的範例:
#!/bin/bash
# DSR 後端伺服器組態腳本
# 此腳本用於在 Linux 伺服器上組態 DSR 所需的網路設定
# VIP 位址
VIP="203.0.113.100"
# 實際的網路介面名稱
INTERFACE="eth0"
# 設定 loopback 介面別名並加入 VIP
# 使用 /32 子網遮罩確保這個 IP 只用於本機
echo "設定 loopback 介面上的 VIP..."
ip addr add ${VIP}/32 dev lo scope host
# 設定 ARP 參數以防止 loopback 上的 VIP 回應 ARP 請求
# arp_ignore = 1: 只回應目標 IP 位址設定在接收介面上的 ARP 請求
# arp_announce = 2: 總是使用與目標網路匹配的最佳本地位址
echo "組態 ARP 參數..."
# 設定所有介面的 ARP 行為
sysctl -w net.ipv4.conf.all.arp_ignore=1
sysctl -w net.ipv4.conf.all.arp_announce=2
# 設定特定介面的 ARP 行為
sysctl -w net.ipv4.conf.${INTERFACE}.arp_ignore=1
sysctl -w net.ipv4.conf.${INTERFACE}.arp_announce=2
# 將設定寫入 sysctl.conf 以便重啟後持久化
echo "持久化 ARP 設定..."
cat >> /etc/sysctl.conf << EOF
# DSR 負載平衡 ARP 設定
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
net.ipv4.conf.${INTERFACE}.arp_ignore = 1
net.ipv4.conf.${INTERFACE}.arp_announce = 2
EOF
# 將 loopback VIP 設定寫入網路組態以便重啟後持久化
# 這裡假設使用 netplan(Ubuntu 18.04+)
echo "持久化 loopback VIP 設定..."
cat >> /etc/netplan/99-dsr-loopback.yaml << EOF
network:
version: 2
ethernets:
lo:
addresses:
- ${VIP}/32
EOF
# 套用 netplan 設定
netplan apply
echo "DSR 組態完成"
echo "VIP ${VIP} 已設定在 loopback 介面上"
echo "ARP 參數已組態以防止 VIP 回應 ARP 請求"
# 驗證設定
echo ""
echo "驗證設定:"
echo "Loopback IP 位址:"
ip addr show lo | grep ${VIP}
echo ""
echo "ARP 參數:"
sysctl net.ipv4.conf.all.arp_ignore
sysctl net.ipv4.conf.all.arp_announce
DSR 的主要優點是極高的效能和擴展性。由於回應流量繞過負載平衡器,負載平衡器只需要處理相對較小的請求流量,大幅降低了頻寬和 CPU 負擔。這使得單台負載平衡器能夠支援更多的後端伺服器和更高的總吞吐量。
然而,DSR 也有其限制。首先,負載平衡器和後端伺服器必須在同一個第二層網路中,因為 DSR 依賴 MAC 位址轉發。其次,負載平衡器無法看到回應流量,因此無法進行 SSL 終止或內容檢查。最後,後端伺服器需要特殊組態,增加了部署和管理的複雜性。
Linux Virtual Server(LVS)深入解析
Linux Virtual Server(LVS)是 Linux 核心內建的負載平衡解決方案,它在第四層運作,提供了極高的效能和穩定性。LVS 支援前述的三種負載平衡方法(NAT、DSR/DR、IP Tunneling),是建構高效能負載平衡系統的優秀選擇。
LVS 架構與組件
LVS 主要由兩個組件構成:核心空間的 IPVS(IP Virtual Server)和使用者空間的管理工具 ipvsadm。IPVS 在核心層級實現封包轉發,因此效能極高;ipvsadm 則用於組態和管理虛擬服務與真實伺服器。
LVS 支援多種負載平衡演算法,包括:
輪詢(Round Robin)是最簡單的演算法,它按照順序將請求分配給每台後端伺服器。這種方法假設所有伺服器具有相同的處理能力,適合後端伺服器規格一致的場景。
加權輪詢(Weighted Round Robin)在輪詢的基礎上加入權重因素,權重較高的伺服器會分配到更多的請求。這適合後端伺服器處理能力不一致的場景。
最少連線(Least Connections)將新請求分配給當前活動連線數最少的伺服器。這種方法能夠更好地平衡負載,特別是在請求處理時間差異較大的場景。
加權最少連線(Weighted Least Connections)結合了最少連線和權重因素,根據連線數與權重的比值來選擇伺服器。
來源雜湊(Source Hashing)根據客戶端 IP 位址進行雜湊,確保來自同一客戶端的請求總是被分配到同一台伺服器。這可以實現會話黏著,適合需要保持會話狀態的應用。
LVS NAT 模式組態
以下是使用 ipvsadm 組態 LVS NAT 模式的詳細範例:
#!/bin/bash
# LVS NAT 模式組態腳本
# 此腳本在 Director(負載平衡器)上執行
# 虛擬服務 IP 位址
VIP="203.0.113.100"
# 虛擬服務連接埠
VPORT="80"
# 真實伺服器 IP 位址
RS1="10.0.0.11"
RS2="10.0.0.12"
RS3="10.0.0.13"
# 真實伺服器連接埠
RPORT="8080"
# 清除現有的 IPVS 規則
echo "清除現有的 IPVS 規則..."
ipvsadm -C
# 啟用 IP 轉發
# NAT 模式需要負載平衡器進行封包轉發
echo "啟用 IP 轉發..."
echo 1 > /proc/sys/net/ipv4/ip_forward
# 持久化 IP 轉發設定
sysctl -w net.ipv4.ip_forward=1
# 新增虛擬服務
# -A:新增虛擬服務
# -t:TCP 服務
# -s:排程演算法(wlc = 加權最少連線)
echo "新增虛擬服務 ${VIP}:${VPORT}..."
ipvsadm -A -t ${VIP}:${VPORT} -s wlc
# 新增真實伺服器
# -a:新增真實伺服器到虛擬服務
# -t:TCP 服務
# -r:真實伺服器位址
# -m:NAT 模式(masquerading)
# -w:權重
echo "新增真實伺服器..."
# 第一台伺服器,權重 3(處理能力較強)
ipvsadm -a -t ${VIP}:${VPORT} -r ${RS1}:${RPORT} -m -w 3
# 第二台伺服器,權重 2
ipvsadm -a -t ${VIP}:${VPORT} -r ${RS2}:${RPORT} -m -w 2
# 第三台伺服器,權重 1(處理能力較弱)
ipvsadm -a -t ${VIP}:${VPORT} -r ${RS3}:${RPORT} -m -w 1
# 設定連線逾時
# TCP 連線逾時(秒)
# FIN 等待逾時(秒)
# UDP 逾時(秒)
echo "設定連線逾時..."
ipvsadm --set 900 60 300
# 顯示 IPVS 規則
echo ""
echo "目前的 IPVS 規則:"
ipvsadm -Ln
# 顯示連線統計
echo ""
echo "連線統計:"
ipvsadm -Ln --stats
# 儲存規則以便重啟後恢復
echo "儲存 IPVS 規則..."
ipvsadm-save > /etc/sysconfig/ipvsadm
echo ""
echo "LVS NAT 模式組態完成"
echo "虛擬服務:${VIP}:${VPORT}"
echo "真實伺服器:${RS1}:${RPORT}, ${RS2}:${RPORT}, ${RS3}:${RPORT}"
echo ""
echo "注意:真實伺服器的預設閘道必須設定為此負載平衡器的後端 IP"
在 NAT 模式下,真實伺服器的預設閘道必須設定為指向負載平衡器的後端介面 IP,以確保回應封包能夠經過負載平衡器進行位址轉換。
LVS DR 模式組態
DR(Direct Routing)模式是 LVS 中最常用的模式,它提供了比 NAT 更高的效能。以下是組態範例:
#!/bin/bash
# LVS DR 模式組態腳本 - Director(負載平衡器)端
# 虛擬服務 IP 位址
VIP="203.0.113.100"
# 虛擬服務連接埠
VPORT="80"
# 真實伺服器 IP 位址
RS1="10.0.0.11"
RS2="10.0.0.12"
RS3="10.0.0.13"
# Director 的實際介面
INTERFACE="eth0"
# 清除現有的 IPVS 規則
echo "清除現有的 IPVS 規則..."
ipvsadm -C
# 在 Director 上設定 VIP
# 將 VIP 設定在實際介面上(或其別名上)
echo "設定 VIP ${VIP} 在 ${INTERFACE} 上..."
ip addr add ${VIP}/32 dev ${INTERFACE}
# 新增虛擬服務
# -s rr:使用輪詢演算法
echo "新增虛擬服務 ${VIP}:${VPORT}..."
ipvsadm -A -t ${VIP}:${VPORT} -s rr
# 新增真實伺服器
# -g:DR 模式(gateway/direct routing)
echo "新增真實伺服器..."
ipvsadm -a -t ${VIP}:${VPORT} -r ${RS1}:${VPORT} -g -w 1
ipvsadm -a -t ${VIP}:${VPORT} -r ${RS2}:${VPORT} -g -w 1
ipvsadm -a -t ${VIP}:${VPORT} -r ${RS3}:${VPORT} -g -w 1
# 顯示 IPVS 規則
echo ""
echo "目前的 IPVS 規則:"
ipvsadm -Ln
# 儲存規則
ipvsadm-save > /etc/sysconfig/ipvsadm
echo ""
echo "LVS DR 模式 Director 組態完成"
echo "記得在每台真實伺服器上執行相應的組態腳本"
真實伺服器端的組態(與前述 DSR 組態類似):
#!/bin/bash
# LVS DR 模式組態腳本 - 真實伺服器端
# VIP 位址
VIP="203.0.113.100"
# 設定 loopback 介面上的 VIP
echo "設定 VIP ${VIP} 在 loopback 上..."
ip addr add ${VIP}/32 dev lo scope host
# 設定 ARP 參數
echo "組態 ARP 參數..."
# 防止 loopback 上的 VIP 回應 ARP 請求
sysctl -w net.ipv4.conf.lo.arp_ignore=1
sysctl -w net.ipv4.conf.lo.arp_announce=2
sysctl -w net.ipv4.conf.all.arp_ignore=1
sysctl -w net.ipv4.conf.all.arp_announce=2
# 持久化設定
cat >> /etc/sysctl.conf << EOF
# LVS DR 模式 ARP 設定
net.ipv4.conf.lo.arp_ignore = 1
net.ipv4.conf.lo.arp_announce = 2
net.ipv4.conf.all.arp_ignore = 1
net.ipv4.conf.all.arp_announce = 2
EOF
echo "LVS DR 模式真實伺服器組態完成"
HAProxy 應用層負載平衡
HAProxy 是一款高效能的開源負載平衡器,支援第四層和第七層負載平衡。它以其卓越的效能、豐富的功能和穩定性而聞名,被廣泛用於高流量網站和企業級應用。
HAProxy 基本架構
HAProxy 使用事件驅動的非阻塞 I/O 模型,能夠以極低的資源消耗處理大量並發連線。它的組態檔分為幾個主要區段:
global 區段定義整個 HAProxy 實例的全域參數,包括日誌設定、資源限制、安全相關選項等。
defaults 區段定義所有 frontend 和 backend 的預設值,可以簡化組態。
frontend 區段定義面向客戶端的監聽器,包括監聽的 IP 位址和連接埠、ACL 規則、後端選擇邏輯等。
backend 區段定義後端伺服器群組,包括伺服器列表、負載平衡演算法、健康檢查設定等。
listen 區段是 frontend 和 backend 的結合,適合簡單的代理場景。
完整 HAProxy 組態範例
以下是一個功能完整的 HAProxy 組態範例,展示了各種進階功能:
# HAProxy 完整組態範例
# 此組態展示了 HTTP/HTTPS 負載平衡、健康檢查、ACL、統計頁面等功能
# 全域設定
global
# 日誌設定
# 將日誌發送到本地 syslog 服務
log /dev/log local0
log /dev/log local1 notice
# chroot 以增強安全性
chroot /var/lib/haproxy
# 統計 socket,用於運行時管理
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
stats timeout 30s
# 以 haproxy 使用者身份運行
user haproxy
group haproxy
# 以 daemon 模式運行
daemon
# SSL/TLS 設定
# 設定預設的 SSL 材料位置
ca-base /etc/ssl/certs
crt-base /etc/ssl/private
# 現代安全的 SSL 設定
# 只使用 TLS 1.2 和 1.3
ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
ssl-default-server-options ssl-min-ver TLSv1.2 no-tls-tickets
# 設定最大連線數
maxconn 50000
# 調整緩衝區大小
tune.bufsize 32768
tune.maxrewrite 4096
# 設定 Diffie-Hellman 參數檔案
ssl-dh-param-file /etc/haproxy/dhparams.pem
# 預設設定
defaults
# 模式:http 或 tcp
mode http
# 使用全域日誌設定
log global
# 記錄 HTTP 請求
option httplog
# 不記錄空連線(健康檢查等)
option dontlognull
# 啟用連線關閉日誌
option log-health-checks
# 在連線錯誤時重新分發
option redispatch
# 重試次數
retries 3
# 逾時設定
# 連線逾時:等待與後端伺服器建立連線的時間
timeout connect 5s
# 客戶端逾時:等待客戶端資料的時間
timeout client 50s
# 伺服器逾時:等待後端伺服器資料的時間
timeout server 50s
# HTTP keep-alive 逾時
timeout http-keep-alive 10s
# HTTP 請求逾時
timeout http-request 10s
# 佇列逾時:請求在佇列中等待的最長時間
timeout queue 30s
# 設定錯誤檔案
errorfile 400 /etc/haproxy/errors/400.http
errorfile 403 /etc/haproxy/errors/403.http
errorfile 408 /etc/haproxy/errors/408.http
errorfile 500 /etc/haproxy/errors/500.http
errorfile 502 /etc/haproxy/errors/502.http
errorfile 503 /etc/haproxy/errors/503.http
errorfile 504 /etc/haproxy/errors/504.http
# 前端:HTTP(重導向到 HTTPS)
frontend http_front
bind *:80
# 定義 ACL 檢查是否為 HTTPS
acl is_https ssl_fc
# HTTP 到 HTTPS 重導向
http-request redirect scheme https unless is_https
# 前端:HTTPS
frontend https_front
# 綁定 HTTPS 連接埠並指定憑證
bind *:443 ssl crt /etc/haproxy/certs/example.com.pem alpn h2,http/1.1
# ACL 定義
# 根據主機名稱路由
acl host_api hdr(host) -i api.example.com
acl host_www hdr(host) -i www.example.com
acl host_admin hdr(host) -i admin.example.com
# 根據 URL 路徑路由
acl path_api path_beg /api
acl path_static path_beg /static
acl path_ws path_beg /ws
# 根據來源 IP 限制存取
acl internal_ip src 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
# WebSocket 檢測
acl is_websocket hdr(Upgrade) -i websocket
# 設定請求標頭
http-request set-header X-Forwarded-Proto https
http-request set-header X-Real-IP %[src]
# 根據 ACL 選擇後端
use_backend ws_servers if is_websocket
use_backend api_servers if host_api or path_api
use_backend static_servers if path_static
use_backend admin_servers if host_admin internal_ip
# 預設後端
default_backend web_servers
# 後端:Web 應用伺服器
backend web_servers
# 負載平衡演算法:輪詢
balance roundrobin
# 會話黏著:使用 Cookie
cookie SERVERID insert indirect nocache
# 健康檢查設定
option httpchk GET /health HTTP/1.1
http-check send hdr Host www.example.com
# 預設伺服器設定
default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions
# 伺服器列表
# check:啟用健康檢查
# cookie:會話黏著的 cookie 值
server web1 10.0.0.11:8080 check cookie web1 weight 100
server web2 10.0.0.12:8080 check cookie web2 weight 100
server web3 10.0.0.13:8080 check cookie web3 weight 50
# 備用伺服器:只在所有主要伺服器都不可用時使用
server web_backup 10.0.0.14:8080 check backup
# 後端:API 伺服器
backend api_servers
# 使用最少連線演算法
balance leastconn
# API 健康檢查
option httpchk GET /api/health HTTP/1.1
http-check send hdr Host api.example.com
http-check expect status 200
# 連線數限制
default-server maxconn 100
server api1 10.0.0.21:8080 check weight 100
server api2 10.0.0.22:8080 check weight 100
server api3 10.0.0.23:8080 check weight 100
# 後端:靜態內容伺服器
backend static_servers
balance roundrobin
# 靜態內容可以使用較長的逾時
timeout server 120s
# 簡單的 TCP 健康檢查
option tcp-check
server static1 10.0.0.31:80 check
server static2 10.0.0.32:80 check
# 後端:WebSocket 伺服器
backend ws_servers
balance source
# WebSocket 需要較長的逾時
timeout tunnel 1h
# 使用來源 IP 雜湊以保持連線
hash-type consistent
server ws1 10.0.0.41:8080 check
server ws2 10.0.0.42:8080 check
# 後端:管理介面伺服器
backend admin_servers
balance roundrobin
# 只允許內部 IP 存取(已在 frontend 中透過 ACL 限制)
http-request deny unless { src -f /etc/haproxy/whitelist.lst }
server admin1 10.0.0.51:8080 check
# 統計頁面
listen stats
bind *:8404
# 啟用統計頁面
stats enable
stats uri /stats
stats refresh 10s
stats show-legends
# 統計頁面認證
stats auth admin:secretpassword
# 允許透過統計頁面管理
stats admin if TRUE
HAProxy 效能調優
HAProxy 的效能調優主要涉及作業系統參數和 HAProxy 自身的組態。以下是一些關鍵的調優項目:
#!/bin/bash
# HAProxy 系統調優腳本
# 此腳本調整作業系統參數以最佳化 HAProxy 效能
echo "開始 HAProxy 系統調優..."
# 建立 sysctl 組態檔
cat > /etc/sysctl.d/99-haproxy-tuning.conf << 'EOF'
# HAProxy 效能調優參數
# TCP 緩衝區大小
# 這些值控制每個 socket 的記憶體使用量
# 格式:最小值 預設值 最大值(bytes)
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
# 允許重用處於 TIME_WAIT 狀態的 socket
# 這對於高連線速率非常重要
net.ipv4.tcp_tw_reuse = 1
# 擴展本地連接埠範圍
# 這限制了可用於連出連線的連接埠數量
net.ipv4.ip_local_port_range = 1024 65535
# 增加 SYN 待處理佇列大小
# 這有助於處理突發的大量新連線
net.ipv4.tcp_max_syn_backlog = 65535
# 設定 TCP FIN_WAIT 狀態的逾時時間
# 較低的值可以更快地釋放連線資源
net.ipv4.tcp_fin_timeout = 15
# 限制 SYN-ACK 重試次數
# 這可以減少 SYN Flood 攻擊的影響
net.ipv4.tcp_synack_retries = 2
# 允許繫結到不存在的 IP 位址
# 這在使用 VRRP 等高可用性方案時很有用
net.ipv4.ip_nonlocal_bind = 1
# 增加連線追蹤表的大小
# 對於高連線數的負載平衡器很重要
net.netfilter.nf_conntrack_max = 1000000
net.nf_conntrack_max = 1000000
# 增加 somaxconn
# 這是 listen() 系統呼叫的待處理佇列大小上限
net.core.somaxconn = 65535
# 增加 netdev_max_backlog
# 這是網路介面接收封包的佇列大小
net.core.netdev_max_backlog = 65535
# 啟用 TCP 快速開啟
# 這可以減少 TCP 握手的延遲
net.ipv4.tcp_fastopen = 3
# TCP keepalive 設定
# 減少 keepalive 間隔以更快地檢測死連線
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 60
net.ipv4.tcp_keepalive_probes = 3
# 增加檔案描述符限制
fs.file-max = 2097152
# 啟用 IP 轉發(如果使用 NAT 模式)
net.ipv4.ip_forward = 1
EOF
# 套用 sysctl 設定
sysctl -p /etc/sysctl.d/99-haproxy-tuning.conf
# 設定檔案描述符限制
cat > /etc/security/limits.d/haproxy.conf << 'EOF'
# HAProxy 檔案描述符限制
haproxy soft nofile 1000000
haproxy hard nofile 1000000
EOF
# 設定 systemd 服務的限制
mkdir -p /etc/systemd/system/haproxy.service.d
cat > /etc/systemd/system/haproxy.service.d/limits.conf << 'EOF'
[Service]
LimitNOFILE=1000000
EOF
# 重新載入 systemd 組態
systemctl daemon-reload
echo "HAProxy 系統調優完成"
echo "部分設定可能需要重新啟動系統才能生效"
Keepalived 高可用性架構
單一負載平衡器本身可能成為單點故障,因此在生產環境中通常會部署高可用性架構。Keepalived 是一款實現 VRRP(Virtual Router Redundancy Protocol)的開源軟體,可以為負載平衡器提供故障轉移功能。
Keepalived 與 HAProxy 整合
以下是 Keepalived 與 HAProxy 整合的組態範例:
# Keepalived 組態(主節點)
# /etc/keepalived/keepalived.conf
global_defs {
# 路由器 ID,在同一 VRRP 群組中必須唯一
router_id LB_MASTER
# 啟用指令碼安全
script_security
enable_script_security
# 設定郵件通知(選擇性)
notification_email {
admin@example.com
}
notification_email_from keepalived@example.com
smtp_server 127.0.0.1
smtp_connect_timeout 30
}
# HAProxy 健康檢查指令碼
vrrp_script chk_haproxy {
# 檢查 HAProxy 是否正在運行
script "/usr/bin/killall -0 haproxy"
# 檢查間隔(秒)
interval 2
# 權重調整值
# 如果檢查失敗,降低此節點的優先權
weight -20
# 失敗幾次後觸發
fall 3
# 成功幾次後恢復
rise 2
}
# VRRP 實例
vrrp_instance VI_1 {
# 狀態:MASTER 或 BACKUP
state MASTER
# 網路介面
interface eth0
# 虛擬路由器 ID,同一 VRRP 群組中必須相同
virtual_router_id 51
# 優先權,數值越高優先權越高
# 主節點應該有較高的優先權
priority 100
# 廣播間隔(秒)
advert_int 1
# 搶佔模式:如果主節點恢復,是否搶回 VIP
preempt_delay 60
# 認證
authentication {
auth_type PASS
auth_pass secret123
}
# 虛擬 IP 位址
virtual_ipaddress {
203.0.113.100/32 dev eth0
}
# 追蹤指令碼
track_script {
chk_haproxy
}
# 狀態變更通知指令碼
notify_master "/etc/keepalived/notify.sh master"
notify_backup "/etc/keepalived/notify.sh backup"
notify_fault "/etc/keepalived/notify.sh fault"
}
備用節點的組態與主節點類似,但需要修改以下參數:
# Keepalived 組態(備用節點)
# /etc/keepalived/keepalived.conf
global_defs {
router_id LB_BACKUP
script_security
enable_script_security
}
vrrp_script chk_haproxy {
script "/usr/bin/killall -0 haproxy"
interval 2
weight -20
fall 3
rise 2
}
vrrp_instance VI_1 {
# 狀態設為 BACKUP
state BACKUP
interface eth0
# 相同的虛擬路由器 ID
virtual_router_id 51
# 較低的優先權
priority 90
advert_int 1
authentication {
auth_type PASS
auth_pass secret123
}
virtual_ipaddress {
203.0.113.100/32 dev eth0
}
track_script {
chk_haproxy
}
notify_master "/etc/keepalived/notify.sh master"
notify_backup "/etc/keepalived/notify.sh backup"
notify_fault "/etc/keepalived/notify.sh fault"
}
通知指令碼範例:
#!/bin/bash
# Keepalived 狀態變更通知指令碼
# /etc/keepalived/notify.sh
TYPE=$1
NAME=$2
STATE=$3
# 記錄狀態變更
logger -t keepalived "VRRP 狀態變更: $STATE"
case $STATE in
"master")
# 成為主節點時執行的動作
logger -t keepalived "已成為 MASTER"
# 可以在這裡啟動服務或發送通知
;;
"backup")
# 成為備用節點時執行的動作
logger -t keepalived "已成為 BACKUP"
;;
"fault")
# 發生故障時執行的動作
logger -t keepalived "進入 FAULT 狀態"
# 可以在這裡發送緊急通知
;;
esac
exit 0
負載平衡監控與管理
有效的監控是維護負載平衡服務穩定運行的關鍵。本節介紹如何監控負載平衡器的各項指標以及常見的管理任務。
HAProxy 監控指標
HAProxy 提供了豐富的統計資訊,可以透過統計頁面、Unix socket 或 Prometheus 匯出器來取得。以下是一些關鍵指標:
#!/bin/bash
# HAProxy 監控指令碼
# 透過 Unix socket 取得統計資訊
SOCKET="/run/haproxy/admin.sock"
echo "=== HAProxy 統計資訊 ==="
echo ""
# 取得 CSV 格式的統計資訊
echo "show stat" | socat stdio ${SOCKET} | head -1
echo ""
# 解析並顯示關鍵指標
echo "=== 後端伺服器狀態 ==="
echo "show stat" | socat stdio ${SOCKET} | \
awk -F',' 'NR>1 && $2!="" {print $1"/"$2": "$18" (sessions: "$5", rate: "$34"/s)"}'
echo ""
echo "=== 連線統計 ==="
echo "show info" | socat stdio ${SOCKET} | grep -E "^(CurrConns|MaxConn|CumReq|ConnRate)"
echo ""
echo "=== 伺服器健康狀態 ==="
echo "show servers state" | socat stdio ${SOCKET}
echo ""
echo "=== 錯誤統計 ==="
echo "show stat" | socat stdio ${SOCKET} | \
awk -F',' 'NR>1 && $2!="" {
if ($13>0 || $14>0 || $15>0) {
print $1"/"$2": cli_err="$13" srv_err="$14" retries="$15
}
}'
Prometheus 監控整合
對於使用 Prometheus 進行監控的環境,可以使用 HAProxy Exporter 來匯出指標:
# HAProxy Exporter 組態
# docker-compose.yml
version: '3'
services:
haproxy-exporter:
image: prom/haproxy-exporter:latest
container_name: haproxy-exporter
command:
- '--haproxy.scrape-uri=http://haproxy:8404/stats?stats;csv'
ports:
- "9101:9101"
restart: unless-stopped
Prometheus 組態:
# prometheus.yml
scrape_configs:
- job_name: 'haproxy'
static_configs:
- targets: ['haproxy-exporter:9101']
scrape_interval: 15s
metrics_path: /metrics
常用的 Prometheus 告警規則:
# haproxy-alerts.yml
groups:
- name: haproxy
rules:
# 後端伺服器不可用
- alert: HAProxyBackendDown
expr: haproxy_backend_up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "HAProxy 後端 {{ $labels.backend }} 不可用"
description: "後端 {{ $labels.backend }} 已經不可用超過 1 分鐘"
# 高錯誤率
- alert: HAProxyHighErrorRate
expr: rate(haproxy_backend_http_responses_total{code=~"5.."}[5m]) / rate(haproxy_backend_http_responses_total[5m]) > 0.05
for: 5m
labels:
severity: warning
annotations:
summary: "HAProxy 後端 {{ $labels.backend }} 錯誤率過高"
description: "後端 {{ $labels.backend }} 的 5xx 錯誤率超過 5%"
# 高連線數
- alert: HAProxyHighConnections
expr: haproxy_frontend_current_sessions / haproxy_frontend_limit_sessions > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "HAProxy 前端 {{ $labels.frontend }} 連線數過高"
description: "前端 {{ $labels.frontend }} 的連線數超過限制的 80%"
網路設計考量
在設計負載平衡架構時,網路拓撲和路由是需要仔細考慮的重要因素。不當的網路設計可能導致非對稱路由、管理困難或安全問題。
管理 VLAN 設計
在使用 NAT 模式負載平衡時,後端伺服器的預設閘道必須指向負載平衡器,這會導致所有流量(包括管理流量)都經過負載平衡器。為了避免這個問題,建議使用獨立的管理 VLAN:
@startuml
!define PLANTUML_FORMAT svg
!theme _none_
skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100
title 管理 VLAN 網路設計
rectangle "網際網路" as Internet
rectangle "生產 VLAN\n10.0.0.0/24" as ProdVLAN {
[負載平衡器\n10.0.0.1] as LB
[Web Server 1\n10.0.0.11] as WS1
[Web Server 2\n10.0.0.12] as WS2
}
rectangle "管理 VLAN\n10.1.0.0/24" as MgmtVLAN {
[負載平衡器管理介面\n10.1.0.1] as LB_Mgmt
[Web Server 1 管理介面\n10.1.0.11] as WS1_Mgmt
[Web Server 2 管理介面\n10.1.0.12] as WS2_Mgmt
[管理工作站\n10.1.0.100] as Admin
}
Internet --> LB
LB --> WS1
LB --> WS2
Admin --> LB_Mgmt
Admin --> WS1_Mgmt
Admin --> WS2_Mgmt
note right of MgmtVLAN
管理流量透過獨立的
VLAN 和介面,不經過
負載平衡器
end note
@enduml策略路由組態
在某些情況下,可能需要使用策略路由來控制特定流量的路由路徑。以下是在 Linux 上組態策略路由的範例:
#!/bin/bash
# 策略路由組態腳本
# 此腳本設定策略路由,使應用流量經過負載平衡器,而管理流量直接路由
# 定義變數
LB_IP="10.0.0.1" # 負載平衡器後端 IP
GW_IP="10.0.0.254" # 子網閘道 IP
TABLE_NUM="100" # 自訂路由表編號
TABLE_NAME="lb_route" # 自訂路由表名稱
# 將自訂路由表名稱加入 rt_tables
echo "${TABLE_NUM} ${TABLE_NAME}" >> /etc/iproute2/rt_tables
# 設定自訂路由表的預設路由
# 這個表的預設路由指向負載平衡器
ip route add default via ${LB_IP} table ${TABLE_NAME}
# 使用 iptables 標記需要經過負載平衡器的流量
# 標記來自 HTTP/HTTPS 連接埠的流量
iptables -t mangle -A OUTPUT -p tcp --sport 80 -j MARK --set-mark 1
iptables -t mangle -A OUTPUT -p tcp --sport 443 -j MARK --set-mark 1
# 設定策略路由規則
# 被標記的流量使用自訂路由表
ip rule add fwmark 1 table ${TABLE_NAME}
# 確保其他流量使用正常的預設閘道
# 主路由表的預設路由指向子網閘道
ip route add default via ${GW_IP}
# 顯示組態結果
echo "策略路由組態完成"
echo ""
echo "路由表 ${TABLE_NAME}:"
ip route show table ${TABLE_NAME}
echo ""
echo "策略路由規則:"
ip rule show
echo ""
echo "iptables mangle 規則:"
iptables -t mangle -L -n -v
總結
負載平衡是建構高可用性、高效能網路服務的關鍵技術。Linux 提供了豐富的負載平衡解決方案,從核心層級的 LVS 到應用層的 HAProxy,每種方案都有其獨特的優勢和適用場景。
在選擇負載平衡方法時,需要考慮多個因素,包括流量特性、效能需求、功能需求以及管理複雜度。對於需要高吞吐量且回應流量遠大於請求流量的場景,DSR 是最佳選擇;對於需要 SSL 終止、內容路由或應用層檢查的場景,反向代理或 NAT 模式更為合適。
在實際部署中,負載平衡器本身也需要高可用性保護。使用 Keepalived 實現 VRRP 故障轉移可以有效避免單點故障,確保服務的連續性。
最後,有效的監控和管理是維護負載平衡服務穩定運行的基礎。透過收集和分析各項指標,可以及時發現潛在問題,並採取適當的措施來最佳化效能和可靠性。
負載平衡技術仍在持續演進中,特別是在容器化和微服務架構的背景下,服務網格(Service Mesh)等新技術正在興起。然而,傳統的負載平衡技術仍然是建構可靠網路服務的基石,深入理解這些技術的原理和實作,對於設計和維護現代網路架構至關重要。