Nginx 的 SSL 憑證& Load balance 演練
- 啟動多個後端實體:
使用 Python 腳本,分別啟動在 Port9090與 Port9091(可透過設定檔或參數區分印出標題)。 - 建置 Upstream 負載平衡:
設定 Nginxupstream backend_cluster包含127.0.0.1:9090與127.0.0.1:9091。 - 申請與掛載 SSL 憑證:
綁定 Domain 名稱(或使用 DuckDNS 等免費 DDNS 網域)。
執行sudo certbot --nginx完成 SSL 憑證部署與 HTTP ➔ HTTPS 自動轉址。 - 驗證高可用容錯(Failover):
在瀏覽器多次重新整理,觀察頁面上的回應 Port 在9090與9091之間輪流切換。
模擬故障:直接kill掉 9090 的 Python 程序,再次重新整理,確認網頁依然可正常存取(自動切換至 9091)。執行
sudo certbot renew --dry-run確保未來憑證自動續簽測試成功。
以上有關 ssl 的申請, 有幾個前提要滿足才行, 你有一台在 internet 上服務 http 的機器, 另外還需有綁定該機器 ip 的 domain. 這樣才能進行申請與設定, 如你沒有此環境則可以跳過該功能的申請.
演練過程:
Part 1
準備 cluster_node.py 來運作後端的多個 http server 實體, 以下是 python demo code
記得每一個執行個體的 node_name 與 port 都必須要不一樣喔!!!
#!/usr/bin/env python3
import argparse
import http.server
import socketserver
from datetime import datetime
class LoadBalanceHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
# 類別變數,由外部傳入設定
node_name = "Node-1"
port = 9090
color = "#3498db" # 預設藍色
def do_GET(self):
# 1. 讀取 Nginx 傳遞的真實客戶端 IP
client_ip = self.headers.get("X-Real-IP")
if not client_ip:
client_ip = self.client_address[0]
# 2. 獲取 Host 與時間
current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
host_header = self.headers.get("Host", "Unknown")
# 3. 繪製回應頁面(包含醒目的節點標籤與顏色區隔)
response_content = f"""<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="UTF-8">
<title>Week 9 Load Balancer Demo - {self.node_name}</title>
<style>
body {{ font-family: Arial, sans-serif; background-color: #f0f2f5; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }}
.card {{ background: white; padding: 2.5rem; border-radius: 12px; box-shadow: 0 8px 16px rgba(0,0,0,0.1); max-width: 500px; width: 100%; border-top: 8px solid {self.color}; }}
.node-badge {{ display: inline-block; background-color: {self.color}; color: white; padding: 6px 16px; border-radius: 20px; font-weight: bold; font-size: 1.1rem; margin-bottom: 1rem; }}
h1 {{ color: #2c3e50; font-size: 1.6rem; margin-top: 0; }}
p {{ color: #4a5568; line-height: 1.6; font-size: 1.05rem; }}
.highlight {{ color: #e53e3e; font-weight: bold; background: #fff5f5; padding: 2px 6px; border-radius: 4px; }}
.code {{ font-family: monospace; background: #edf2f7; padding: 2px 6px; border-radius: 4px; font-weight: bold; color: #2d3748; }}
</style>
</head>
<body>
<div class="card">
<div class="node-badge">運作節點:{self.node_name}</div>
<h1>⚖️ Nginx 負載平衡測試頁面</h1>
<p><strong>服務回應 Port:</strong> <span class="code">{self.port}</span></p>
<p><strong>請求域名/Host:</strong> <span class="code">{host_header}</span></p>
<p><strong>真實 Client IP:</strong> <span class="highlight">{client_ip}</span></p>
<p><strong>回應時間:</strong> {current_time}</p>
</div>
</body>
</html>
"""
# 4. 發送 HTTP 回應
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.send_header("Content-Length", str(len(response_content.encode("utf-8"))))
self.end_headers()
self.wfile.write(response_content.encode("utf-8"))
# 5. 印出 Terminal 控制台日誌
print(f"[{current_time}] [{self.node_name}] 收到來自真實 IP: {client_ip} 的請求")
def run():
# 使用 argparse 解析命令行參數
parser = argparse.ArgumentParser(description="Week 9 多節點測試用 HTTP Server")
parser.add_argument("-p", "--port", type=int, default=9090, help="指定監聽 Port (預設: 9090)")
parser.add_argument("-n", "--name", type=str, default="Node-A", help="指定節點名稱 (預設: Node-A)")
parser.add_argument("-c", "--color", type=str, default="#3498db", help="指定 UI 色彩 Hex Code (預設: #3498db)")
args = parser.parse_args()
# 建立自訂 Handler 並注入參數
handler = LoadBalanceHTTPRequestHandler
handler.node_name = args.name
handler.port = args.port
handler.color = args.color
# 綁定 127.0.0.1 本機內部存取,由 Nginx 反向代理進入
server_address = ("127.0.0.1", args.port)
with socketserver.TCPServer(server_address, handler) as httpd:
print(f"🚀 節點【{args.name}】已啟動,監聽於 127.0.0.1:{args.port}")
print("等待 Nginx 流量分發中...")
try:
httpd.serve_forever()
except KeyboardInterrupt:
print(f"\n🛑 節點【{args.name}】已停止")
if __name__ == "__main__":
run()
Part 2
同時啟動兩個(或三個)不同 Port 的節點
# 視窗 1:啟動 Node-A (Port 9090, 藍色)
python3 cluster_node.py -p 9090 -n "Node-A" -c "#3498db"
# 視窗 2:啟動 Node-B (Port 9091, 綠色)
python3 cluster_node.py -p 9091 -n "Node-B" -c "#2ecc71"
# 視窗 3 (可選):啟動 Backup-Node (Port 9092, 橙色)
python3 cluster_node.py -p 9092 -n "Backup-Node" -c "#e67e22"
Part 3
設定 Nginx Upstream
修改 /etc/nginx/sites-available/myapp.conf 設定檔:
upstream backend_cluster {
# 預設 Round Robin 輪詢
server 127.0.0.1:9090 max_fails=3 fail_timeout=10s;
server 127.0.0.1:9091 max_fails=3 fail_timeout=10s;
# 備援節點 (當 9090 與 9091 都掛掉時才會啟用)
server 127.0.0.1:9092 backup;
}
server {
listen 80;
server_name example.com; # 或伺服器 IP
location / {
proxy_pass http://backend_cluster;
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;
}
}
Part 4
重新載入 Nginx:
sudo nginx -t && sudo systemctl reload nginx
驗證與展示效果
- 驗證輪詢分流(Round Robin):
開啟瀏覽器訪問網站,連續重新整理(Ctrl + R/Cmd + R)。
觀看視覺體驗:畫面頂部的卡片會隨著每次重新整理,在 【Node-A (藍色 / 9090)】 與 【Node-B (綠色 / 9091)】 之間交替切換。 - 驗證故障轉移(Failover / Passive Health Check):
在視窗 1 按下Ctrl + C直接關閉Node-A (9090)。
再次在瀏覽器刷頁面,觀察流量是否全數由 【Node-B (9091)】 接手,且完全不會出現 502 錯誤。 - 驗證 Backup 備援機制:
接著按下Ctrl + C關閉Node-B (9091)。重新整理頁面,流量將瞬間轉導至 【Backup-Node (橙色 / 9092)】。
以上即可驗證 , Nginx 在前端分配流量到後端可以運作的 server, 避免後端某一台機器掛掉之後服務終止的狀況