Ubuntu 22.04 Nginx 負載平衡設定

Ubuntu 22.04 Nginx Load Balancing Configuration

負載平衡(Load Balancing)是現代高可用性架構中不可或缺的技術。本文將詳細介紹如何在 Ubuntu 22.04 上使用 Nginx 設定負載平衡,包含 Upstream 設定、負載平衡演算法、健康檢查及 Session 持久性等進階功能。

負載平衡簡介

什麼是負載平衡?

負載平衡是一種將網路流量分散到多個伺服器的技術,目的是確保沒有單一伺服器承擔過多的請求負載。透過負載平衡,可以提高應用程式的可用性、可靠性和效能。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
                    ┌─────────────────┐
                    │  後端伺服器 1   │
                    │  192.168.1.10   │
                    └─────────────────┘
用戶端 ──────► Nginx LB ──────┼──────► 後端伺服器 2
                              │        192.168.1.11
                    ┌─────────────────┐
                    │  後端伺服器 3   │
                    │  192.168.1.12   │
                    └─────────────────┘

負載平衡的優點

優點說明
高可用性當一台伺服器故障時,流量自動導向其他正常伺服器
擴展性可輕鬆新增或移除後端伺服器以應對流量變化
效能提升將請求分散到多台伺服器,提高整體處理能力
彈性部署可進行滾動更新,不影響服務運作
資源優化根據伺服器能力分配適當的流量比例

前置需求

確保已安裝 Nginx:

1
2
sudo apt update
sudo apt install nginx -y

確認 Nginx 已啟動:

1
2
3
sudo systemctl enable nginx
sudo systemctl start nginx
sudo systemctl status nginx

Upstream 設定

Upstream 是 Nginx 中用於定義後端伺服器群組的區塊。所有負載平衡的設定都基於 upstream 區塊進行配置。

基本 Upstream 設定

建立負載平衡設定檔:

1
sudo nano /etc/nginx/sites-available/load-balancer

基本的 upstream 設定:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
upstream backend_cluster {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend_cluster;
        proxy_http_version 1.1;
        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;
    }
}

Upstream 伺服器參數

每個 server 指令都可以配置多種參數:

1
2
3
4
5
6
7
upstream backend_cluster {
    server 192.168.1.10:8080 weight=3;
    server 192.168.1.11:8080 weight=2;
    server 192.168.1.12:8080 weight=1;
    server 192.168.1.13:8080 backup;
    server 192.168.1.14:8080 down;
}
參數說明
weight=n設定伺服器權重,預設為 1
max_conns=n限制與該伺服器的最大同時連線數
max_fails=n允許的最大失敗次數,預設為 1
fail_timeout=time失敗後暫停使用的時間,預設為 10 秒
backup標記為備援伺服器,僅在主要伺服器都失敗時使用
down標記伺服器為永久離線

使用 Unix Socket

除了 TCP 連線,也可以使用 Unix Socket:

1
2
3
4
5
6
7
upstream php_backend {
    server unix:/var/run/php/php8.1-fpm.sock;
}

upstream node_backend {
    server unix:/var/run/node/app.sock;
}

負載平衡演算法

Nginx 提供多種負載平衡演算法,可根據不同場景選擇適合的策略。

Round Robin(輪詢)

預設的負載平衡演算法,依序將請求分配到每個伺服器:

1
2
3
4
5
6
upstream backend_cluster {
    # Round Robin 是預設演算法,不需要額外指令
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

特點:

  • 簡單且公平
  • 適合伺服器效能相近的情況
  • 不考慮伺服器當前負載

Weighted Round Robin(加權輪詢)

根據伺服器效能差異分配不同權重:

1
2
3
4
5
upstream backend_cluster {
    server 192.168.1.10:8080 weight=5;  # 高效能伺服器
    server 192.168.1.11:8080 weight=3;  # 中等效能伺服器
    server 192.168.1.12:8080 weight=1;  # 低效能伺服器
}

特點:

  • 適合伺服器效能不一致的情況
  • 權重越高,分配到的請求越多
  • 可根據伺服器規格調整權重

Least Connections(最少連線)

將請求分配給當前連線數最少的伺服器:

1
2
3
4
5
6
upstream backend_cluster {
    least_conn;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

特點:

  • 適合請求處理時間差異較大的場景
  • 動態平衡伺服器負載
  • 避免單一伺服器過載

Weighted Least Connections(加權最少連線)

結合權重和最少連線:

1
2
3
4
5
6
upstream backend_cluster {
    least_conn;
    server 192.168.1.10:8080 weight=3;
    server 192.168.1.11:8080 weight=2;
    server 192.168.1.12:8080 weight=1;
}

IP Hash

根據用戶端 IP 位址決定分配到哪台伺服器:

1
2
3
4
5
6
upstream backend_cluster {
    ip_hash;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

特點:

  • 同一用戶端 IP 總是連接到同一台伺服器
  • 提供基本的 Session 持久性
  • 注意:如果通過 CDN 或代理,所有請求可能來自相同 IP

Generic Hash

使用自定義的 key 進行 hash:

1
2
3
4
5
6
upstream backend_cluster {
    hash $request_uri consistent;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

特點:

  • 可使用任意變數作為 hash key
  • consistent 參數啟用一致性 hash,減少伺服器變動時的影響

Random(隨機)

隨機選擇伺服器:

1
2
3
4
5
6
upstream backend_cluster {
    random two least_conn;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

特點:

  • two 參數:隨機選擇兩台伺服器,然後根據指定方法選擇其一
  • 可結合 least_connleast_time 使用

演算法比較表

演算法適用場景優點缺點
Round Robin伺服器效能相近簡單公平不考慮伺服器負載
Weighted Round Robin伺服器效能不一可調整流量比例需要手動設定權重
Least Connections請求處理時間不一動態平衡負載可能造成頻繁切換
IP Hash需要 Session 黏著簡單的持久性可能分配不均
Generic Hash特定路由需求靈活可自定義配置較複雜

健康檢查

健康檢查確保流量只會被導向正常運作的伺服器。

被動健康檢查

Nginx 開源版本提供被動健康檢查,透過監控實際請求的回應來判斷伺服器狀態:

1
2
3
4
5
upstream backend_cluster {
    server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:8080 max_fails=3 fail_timeout=30s;
}

參數說明:

參數說明
max_fails=3fail_timeout 時間內允許的最大失敗次數
fail_timeout=30s失敗計數的時間窗口,以及伺服器被標記為不可用後的等待時間

運作邏輯:

  1. 當伺服器在 30 秒內失敗 3 次,則標記為不可用
  2. 等待 30 秒後,Nginx 會再次嘗試向該伺服器發送請求
  3. 如果請求成功,伺服器恢復為可用狀態

設定失敗的判斷條件

可以自定義什麼情況算作「失敗」:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
upstream backend_cluster {
    server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
}

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend_cluster;

        # 定義哪些錯誤需要嘗試下一台伺服器
        proxy_next_upstream error timeout http_500 http_502 http_503 http_504;

        # 嘗試下一台伺服器的超時時間
        proxy_next_upstream_timeout 10s;

        # 最多嘗試的伺服器數量
        proxy_next_upstream_tries 3;

        # 連線超時設定
        proxy_connect_timeout 5s;
        proxy_send_timeout 10s;
        proxy_read_timeout 10s;
    }
}

proxy_next_upstream 參數:

參數說明
error與伺服器建立連線、傳送請求或讀取回應時發生錯誤
timeout連線超時
invalid_header伺服器回傳空白或無效的回應
http_500伺服器回傳 500 錯誤
http_502伺服器回傳 502 錯誤
http_503伺服器回傳 503 錯誤
http_504伺服器回傳 504 錯誤
http_403伺服器回傳 403 錯誤
http_404伺服器回傳 404 錯誤
non_idempotent允許對非冪等請求(POST、LOCK、PATCH)重試
off禁用重試功能

慢啟動(Slow Start)

防止剛恢復的伺服器立即承受大量流量(需要 Nginx Plus):

1
2
3
4
5
upstream backend_cluster {
    server 192.168.1.10:8080 slow_start=30s;
    server 192.168.1.11:8080 slow_start=30s;
    server 192.168.1.12:8080;
}

使用第三方模組進行主動健康檢查

開源版本可以使用 nginx_upstream_check_module 模組:

編譯安裝包含健康檢查模組的 Nginx:

1
2
3
4
# 下載並編譯(僅供參考)
git clone https://github.com/yaoweibin/nginx_upstream_check_module.git
./configure --add-module=/path/to/nginx_upstream_check_module
make && sudo make install

配置主動健康檢查:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
upstream backend_cluster {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;

    check interval=3000 rise=2 fall=3 timeout=1000 type=http;
    check_http_send "GET /health HTTP/1.0\r\n\r\n";
    check_http_expect_alive http_2xx http_3xx;
}

server {
    listen 80;

    # 健康檢查狀態頁面
    location /upstream_status {
        check_status;
        access_log off;
        allow 127.0.0.1;
        deny all;
    }
}

Session 持久性

Session 持久性(也稱為 Session 黏著或 Sticky Session)確保同一用戶的請求總是被導向同一台後端伺服器。

為什麼需要 Session 持久性?

在以下情況需要 Session 持久性:

  • 應用程式將 Session 資料儲存在本地記憶體
  • 購物車、登入狀態等需要保持一致
  • 長時間連線的應用(如 WebSocket)

方法一:IP Hash

最簡單的 Session 持久性方法:

1
2
3
4
5
6
upstream backend_cluster {
    ip_hash;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

優點:

  • 設定簡單
  • 不需要修改應用程式

缺點:

  • 通過 NAT 或代理的用戶可能都被分配到同一台伺服器
  • 無法在伺服器故障時保持 Session

方法二:Cookie-based Hash

使用 Cookie 進行 hash:

1
2
3
4
5
6
upstream backend_cluster {
    hash $cookie_jsessionid consistent;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

方法三:Sticky Cookie(Nginx Plus)

Nginx Plus 提供更強大的 Sticky Session 功能:

1
2
3
4
5
6
7
upstream backend_cluster {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;

    sticky cookie srv_id expires=1h domain=.example.com path=/;
}

方法四:使用外部 Session 存儲

最推薦的方式是使用共享 Session 存儲(如 Redis):

1
2
3
4
5
用戶端 ──► Nginx LB ──┬──► 後端伺服器 1 ──┐
                      │                    │
                      ├──► 後端伺服器 2 ──┼──► Redis Session Store
                      │                    │
                      └──► 後端伺服器 3 ──┘

這樣就不需要 Session 持久性,因為所有伺服器都可以存取相同的 Session 資料。

開源 Nginx Sticky Module

可以使用 nginx-sticky-module-ng 來實現 Sticky Session:

1
2
3
4
# 編譯安裝(僅供參考)
git clone https://bitbucket.org/nginx-goodies/nginx-sticky-module-ng.git
./configure --add-module=/path/to/nginx-sticky-module-ng
make && sudo make install

配置範例:

1
2
3
4
5
6
upstream backend_cluster {
    sticky;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

或使用更詳細的設定:

1
2
3
4
5
6
upstream backend_cluster {
    sticky name=route expires=1h domain=.example.com path=/ secure httponly;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

完整設定範例

以下是一個包含所有功能的完整負載平衡設定:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
# 定義 upstream 群組
upstream backend_cluster {
    least_conn;

    server 192.168.1.10:8080 weight=3 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 weight=2 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:8080 weight=1 max_fails=3 fail_timeout=30s;
    server 192.168.1.13:8080 backup;

    keepalive 32;
}

# HTTP 重導向到 HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

# HTTPS 伺服器設定
server {
    listen 443 ssl http2;
    server_name example.com;

    # SSL 設定
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;

    # 安全 Headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options SAMEORIGIN;

    # 日誌設定
    access_log /var/log/nginx/example.com.access.log;
    error_log /var/log/nginx/example.com.error.log;

    # 靜態檔案處理
    location /static/ {
        alias /var/www/example.com/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # API 負載平衡
    location /api/ {
        proxy_pass http://backend_cluster;
        proxy_http_version 1.1;

        # Headers
        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_set_header Connection "";

        # 超時設定
        proxy_connect_timeout 10s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;

        # 緩衝設定
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;

        # 健康檢查相關
        proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
        proxy_next_upstream_timeout 10s;
        proxy_next_upstream_tries 3;
    }

    # WebSocket 負載平衡
    location /ws/ {
        proxy_pass http://backend_cluster;
        proxy_http_version 1.1;

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        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_read_timeout 86400;
        proxy_send_timeout 86400;
    }

    # 預設位置
    location / {
        proxy_pass http://backend_cluster;
        proxy_http_version 1.1;

        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_set_header Connection "";

        proxy_connect_timeout 10s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

啟用設定

1
2
3
4
5
6
7
8
# 建立符號連結
sudo ln -s /etc/nginx/sites-available/load-balancer /etc/nginx/sites-enabled/

# 測試設定
sudo nginx -t

# 重新載入 Nginx
sudo systemctl reload nginx

常見問題排解

檢查 Nginx 設定語法

1
sudo nginx -t

查看錯誤日誌

1
sudo tail -f /var/log/nginx/error.log

監控 upstream 狀態

可以使用 stub_status 模組監控基本狀態:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
server {
    listen 8080;
    server_name localhost;

    location /nginx_status {
        stub_status;
        allow 127.0.0.1;
        deny all;
    }
}

測試負載平衡

使用 curl 測試負載平衡是否正常運作:

1
2
# 連續發送請求觀察分配情況
for i in {1..10}; do curl -s http://example.com/api/server-id; echo; done

常見錯誤及解決方案

錯誤可能原因解決方案
502 Bad Gateway後端伺服器未啟動檢查後端服務狀態
503 Service Unavailable所有後端伺服器都不可用檢查健康檢查設定和後端服務
504 Gateway Timeout後端回應超時增加 proxy_read_timeout 值
Connection refused無法連接到後端檢查防火牆和後端服務埠

參考資料

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy