
DNS、負載均衡與 CDN:每個 Web 應用背後的隱形骨幹
使用者在瀏覽器輸入網址、按下 Enter 到看到頁面內容,中間經過的路徑遠比想像中複雜。DNS 負責把人類看得懂的域名翻譯成 IP 位址、CDN 決定使用者要從哪個邊緣節點拿到快取過的靜態資源、Load Balancer 則把請求分配到健康的後端伺服器。這三者是每個 Web 應用背後的隱形骨幹——平常運作正常時沒有人會注意到它們,但只要其中一個環節出問題,整個服務就會癱瘓。
2021 年 Fastly CDN 的一個設定錯誤導致全球大量網站(包括 Amazon、Reddit、GitHub)同時無法存取,停擺約一小時。2016 年 Dyn DNS 遭受大規模 DDoS 攻擊,Twitter、Netflix、Spotify 等服務全部受影響。這些事件都證明了同一件事:DNS、Load Balancing、CDN 不是「選配」,而是「必配」。
這篇文章完整介紹這三個核心元件的概念、原理與實作,幫助你建立一套穩定、高效能、可擴展的網路層基礎設施。
架構概覽
flowchart LR User["使用者"] -->|1. DNS 查詢| DNS["DNS\nCloudflare"] DNS -->|2. 回傳 IP| User User -->|3. HTTPS 請求| CDN["CDN Edge\n邊緣節點"] CDN -->|Cache HIT| User CDN -->|4. Cache MISS\n回源請求| LB["Load Balancer\nNginx / HAProxy"] LB --> S1["Server 1"] LB --> S2["Server 2"] LB --> S3["Server 3"] S1 -->|回應| CDN S2 -->|回應| CDN S3 -->|回應| CDN
架構概覽
flowchart LR User[使用者\nBrowser / App] -->|1. 域名查詢| DNS[DNS Provider\nCloudflare DNS] DNS -->|2. 回傳 IP / CNAME| User User -->|3. HTTPS 請求| CDN[CDN Edge Server\nCloudflare PoP] CDN -->|Cache HIT| User CDN -->|4. Cache MISS\n回源| LB[Load Balancer\nNginx / HAProxy] LB -->|5. 分配請求| S1[Backend Server 1\nApp :8000] LB -->|5. 分配請求| S2[Backend Server 2\nApp :8001] LB -->|5. 分配請求| S3[Backend Server 3\nApp :8002] subgraph Health["Health Check"] LB -.->|active probe| S1 LB -.->|active probe| S2 LB -.->|active probe| S3 end
完整流量路徑:使用者的瀏覽器先向 DNS 查詢域名對應的 IP,DNS 回傳 CDN 邊緣節點的 IP。請求到達 CDN 後,如果是已快取的靜態資源就直接回傳(Cache HIT),否則回源到 Load Balancer。Load Balancer 根據預設的演算法和健康狀態,將請求分配到其中一台後端伺服器處理。
核心概念 — DNS
-
DNS 記錄類型:DNS 不只是「域名對應 IP」,不同的記錄類型有不同的用途:
記錄類型 用途 範例 A 域名 → IPv4 位址 api.example.com → 203.0.113.10AAAA 域名 → IPv6 位址 api.example.com → 2001:db8::1CNAME 域名 → 另一個域名(別名) www.example.com → example.comMX 指定郵件伺服器 example.com → mail.example.com (priority 10)TXT 任意文字,常用於驗證與安全策略 SPF: v=spf1 include:_spf.google.com ~allSRV 指定服務的 host 與 port _sip._tcp.example.com → 5060 sip.example.com實務上最常用的是 A 和 CNAME。A 記錄用於主入口(指向固定 IP),其他子域名則用 CNAME 指向主入口,這樣換 IP 時只需要改一筆 A 記錄。TXT 記錄在設定 SPF/DKIM/DMARC(郵件安全)和域名驗證(Let’s Encrypt DNS-01 challenge、Google Search Console)時會用到。
-
TTL 管理與傳播:TTL(Time To Live)決定 DNS 記錄被快取多久。TTL = 300 代表各級 DNS resolver 會快取這筆記錄 5 分鐘,期間不會重新查詢。TTL 的管理策略:
- 平時:設定 TTL = 300(5 分鐘),在即時性和查詢效能之間取得平衡
- 遷移前:提前 24-48 小時將 TTL 降到 60 秒,確保舊快取過期
- 遷移後:切換 IP,因為 TTL 只有 60 秒,大部分使用者幾分鐘內就會解析到新 IP
- 穩定後:將 TTL 調回 300 或更高
DNS 傳播不是「推送」而是「過期」——你改了記錄後,全球的 DNS resolver 不會立即更新,而是等各自的快取過期後才會重新查詢。這就是為什麼改 DNS 後有人馬上看到新 IP,有人要等幾小時。
-
Internal DNS vs External DNS:
- External DNS:面向公網的 DNS 記錄,所有人都能查詢到。例如
api.example.com → 203.0.113.10。由 Cloudflare、Route53 等公共 DNS provider 管理。 - Internal DNS:只在內網可解析的 DNS 記錄,例如
db.internal → 10.10.2.50。可以用 CoreDNS、dnsmasq、或雲端的 Private DNS Zone 實現。好處是內部服務之間不需要記 IP,改 IP 時也不用逐台改設定。
建議將所有對外服務的域名放在 External DNS,所有內部服務(資料庫、Redis、監控)放在 Internal DNS。兩者分開管理,避免內部 IP 被公網查到。
- External DNS:面向公網的 DNS 記錄,所有人都能查詢到。例如
-
Cloudflare DNS 設定與優勢:Cloudflare 是目前最受歡迎的 DNS provider 之一,免費方案就提供:
- Anycast DNS:全球超過 300 個節點,DNS 查詢延遲極低
- DDoS 防護:自動偵測並阻擋 DNS 層的 DDoS 攻擊
- Proxy 模式(橘色雲朵):開啟後流量會經過 Cloudflare,隱藏真實 IP,自動提供 WAF 和 CDN。關閉(灰色雲朵)則只做 DNS 解析,不經過 Cloudflare
- DNSSEC:一鍵啟用,防止 DNS 污染和中間人攻擊
Proxy 模式的注意事項:開啟 Proxy 後,
dig查到的 IP 會是 Cloudflare 的 IP,不是你的真實 IP。如果後端需要知道使用者的真實 IP,要讀取CF-Connecting-IPheader。某些服務(例如 SSH、非 HTTP 協定)不支援 Proxy 模式,需要關閉。
核心概念 — Load Balancing
-
L4 vs L7 負載均衡:這是最基本也最重要的區分:
- L4(Transport Layer):在 TCP/UDP 層做負載均衡。Load Balancer 只看到 IP 和 port,不解析 HTTP 內容。優點是效能極高(不需要解析封包),缺點是無法根據 URL、Header、Cookie 做路由決策。適合高吞吐量、不需要內容感知的場景(例如資料庫連線池、gRPC)。
- L7(Application Layer):在 HTTP 層做負載均衡。Load Balancer 完全解析 HTTP 請求,可以根據 URL path、Host header、Cookie 等做精細的路由。例如
/api/*轉到 API server、/static/*轉到靜態資源伺服器。缺點是相比 L4 有更高的延遲和資源消耗。
大部分 Web 應用使用 L7 負載均衡(Nginx、HAProxy、AWS ALB),因為需要根據請求內容做路由。L4 負載均衡(LVS、AWS NLB)通常用在更底層的場景。
-
負載均衡演算法:
- Round Robin:輪流分配,第一個請求給 Server 1、第二個給 Server 2、第三個給 Server 3,依此循環。最簡單,適合所有 Server 規格相同的場景。
- Weighted Round Robin:加權輪流。可以設定 Server 1 權重 5、Server 2 權重 3、Server 3 權重 2,流量比例就是 5:3:2。適合 Server 規格不同的場景(例如有些是 4 核、有些是 8 核)。
- Least Connections:分配給目前連線數最少的 Server。適合請求處理時間差異大的場景(例如有些 API 回應 10ms、有些要 5 秒)。
- IP Hash:根據使用者 IP 的 hash 值決定分配到哪台 Server,同一個 IP 永遠會被分到同一台。這是實現 session 固定的一種方式,但缺點是當 Server 數量變動時,大量使用者會被重新分配。
-
Health Check:主動 vs 被動:
- Active Health Check:Load Balancer 定時主動發 HTTP 請求到後端的健康檢查端點(例如
GET /health),如果連續 N 次回傳非 200 就標記為不健康,停止分配流量。恢復後再重新加入。 - Passive Health Check:Load Balancer 不主動探測,而是根據實際請求的回應結果判斷。如果某台 Server 連續回傳 502/503 或逾時,就標記為不健康。
- 建議兩者搭配使用:Active 用來快速偵測明確的故障(服務掛了),Passive 用來偵測隱性的問題(服務還活著但回應異常慢或錯誤率飆升)。
- Active Health Check:Load Balancer 定時主動發 HTTP 請求到後端的健康檢查端點(例如
-
Session Persistence(Sticky Sessions):某些應用需要同一個使用者的所有請求都打到同一台 Server(例如 Server 端存了 session 狀態)。實現方式包括 Cookie-based(Load Balancer 在回應中植入一個 cookie 記錄對應的 Server)和 IP Hash。
何時使用 Sticky Sessions:
- 應用使用 Server-side session 且沒有共用 session store(如 Redis)
- WebSocket 連線需要固定到同一台 Server
- 某些有狀態的多步驟流程(例如多步驟表單)
何時不該使用:
- 應用是 stateless 的(所有狀態存在 DB 或 Redis)— 這是理想架構
- 需要真正的均勻負載分配
- Server 可能隨時被移除或新增(auto-scaling 場景)
-
Nginx upstream 設定:Nginx 是最常用的 L7 Load Balancer。透過
upstream區塊定義後端伺服器群組,在server區塊中用proxy_pass轉發。支援 Round Robin、Weighted、Least Connections、IP Hash 等演算法,以及 health check 參數。 -
HAProxy 作為替代方案:HAProxy 是另一個非常成熟的 Load Balancer,相比 Nginx 在純負載均衡場景下有一些優勢:更精細的 health check 設定、原生的連線排空(connection draining)支援、即時的統計儀表板(stats page)。如果你的場景是高流量且需要精細控制,HAProxy 值得考慮。但如果你已經用 Nginx 做反向代理和 TLS 終止,加 Load Balancing 只需要多寫幾行設定,不需要額外引入 HAProxy。
核心概念 — CDN
-
CDN 的運作原理:CDN(Content Delivery Network)在全球部署了大量的邊緣節點(Edge Server / PoP, Point of Presence)。當使用者請求一個資源時:
- Cache HIT:邊緣節點已經有這個資源的快取,直接回傳,不需要回源。延遲最低。
- Cache MISS:邊緣節點沒有快取,向你的源站(Origin Server)請求資源,拿到後快取一份再回傳給使用者。下次同樣的請求就會變成 Cache HIT。
- Cache EXPIRED:快取已過期,邊緣節點會向源站發送條件式請求(
If-Modified-Since或If-None-Match),如果資源沒變就回傳 304 Not Modified,邊緣節點更新過期時間繼續使用快取。
CDN 的最大價值是把靜態資源「推到離使用者最近的地方」。一個台灣的使用者存取放在美國的伺服器,延遲可能 200ms+。如果靜態資源已經快取在台灣的邊緣節點,延遲可以降到 10ms 以內。
-
快取策略:什麼該快取、什麼不該快取:
應該快取的:
- 靜態資源:CSS、JavaScript、圖片、字型、影片
- 公開的 HTML 頁面(如果內容不頻繁變動)
- API 回應中的公開且不常變動的資料(例如國家列表、分類清單)
不應該快取的:
- 含有個人資料的 API 回應(使用者資訊、訂單、帳單)
- 需要即時更新的動態內容
- 需要認證才能存取的資源
- POST/PUT/DELETE 請求
黃金法則:靜態資源全部快取,動態 API 回應預設不快取,除非你明確知道可以快取且設定了正確的失效策略。
-
Cache-Control Header 與清除策略:
Cache-Control: public, max-age=31536000, immutable:公開快取,有效期一年,不需要重新驗證。適合帶 hash 的靜態資源(app.a1b2c3.js)。Cache-Control: public, max-age=3600:公開快取一小時。適合不常變動的頁面。Cache-Control: no-store:完全不快取。適合敏感資料。Cache-Control: no-cache:可以快取,但每次使用前必須向源站驗證是否過期。
清除策略(Cache Purge):
- 手動清除:透過 CDN 的管理介面或 API 清除特定 URL 的快取
- Tag-based purge:為快取內容加標籤,清除時按標籤批次清除(Cloudflare Enterprise 支援)
- 版本化 URL:靜態資源 URL 帶 hash(
style.a1b2c3.css),更新時改 hash 就等於自動失效舊快取,這是最可靠的策略
-
Cloudflare CDN 設定與 Page Rules:Cloudflare 免費方案就包含 CDN 功能。開啟 Proxy 模式後,所有經過 Cloudflare 的流量自動獲得 CDN 快取。可以透過 Page Rules 或 Cache Rules 針對不同路徑設定不同的快取行為:
/static/*:快取所有內容,Edge Cache TTL 設定為 30 天/api/*:繞過快取,直接回源/assets/*:快取所有內容並啟用 Brotli 壓縮
使用情境
-
新服務上線:部署一個新的 Web 應用,需要在 Cloudflare 加 DNS 記錄指向 Load Balancer,設定 Nginx upstream 指向後端伺服器群組,配置 CDN 快取規則讓靜態資源走邊緣節點。整個流程標準化後,新服務上線只需 15 分鐘。
-
流量突增應對:活動或促銷期間流量暴增 10 倍。CDN 吸收了 80%+ 的靜態資源請求,Load Balancer 根據 Least Connections 演算法將動態請求分配到後端伺服器。如果搭配 auto-scaling,Load Balancer 會自動感知新加入的伺服器。
-
故障自動切換:某台後端伺服器掛了,Nginx 的 Active Health Check 在 10 秒內偵測到,自動停止分配流量到該伺服器,使用者完全無感。修復後伺服器重新通過 Health Check,自動重新加入流量池。
-
跨區域部署:服務部署在美國和亞洲兩個區域。DNS 的 GeoDNS(或 Cloudflare 的 Load Balancing 功能)根據使用者地理位置,將請求導向最近的區域。CDN 在各區域的邊緣節點快取靜態資源,進一步降低延遲。
實作範例 / 設定範例
Cloudflare DNS 設定(Terraform)
# cloudflare-dns.tf — 使用 Terraform 管理 Cloudflare DNS 記錄
# 好處:版本控制、可審計、可重現
terraform {
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
}
}
variable "cloudflare_zone_id" {
description = "Cloudflare Zone ID"
type = string
}
variable "server_ip" {
description = "Origin server public IP"
type = string
default = "203.0.113.10"
}
# 主入口 A 記錄 — 指向 Load Balancer 的固定 IP
resource "cloudflare_record" "root" {
zone_id = var.cloudflare_zone_id
name = "example.com"
content = var.server_ip
type = "A"
ttl = 1 # 1 = Auto(Proxied 模式下由 Cloudflare 控制)
proxied = true # 開啟 Proxy:隱藏真實 IP + CDN + DDoS 防護
}
# API 子域名 — CNAME 到主入口
resource "cloudflare_record" "api" {
zone_id = var.cloudflare_zone_id
name = "api"
content = "example.com"
type = "CNAME"
ttl = 1
proxied = true
}
# 靜態資源子域名
resource "cloudflare_record" "static" {
zone_id = var.cloudflare_zone_id
name = "static"
content = "example.com"
type = "CNAME"
ttl = 1
proxied = true # 靜態資源一定要開 Proxy 以獲得 CDN 快取
}
# 郵件記錄
resource "cloudflare_record" "mx" {
zone_id = var.cloudflare_zone_id
name = "example.com"
content = "mail.example.com"
type = "MX"
ttl = 3600
priority = 10
proxied = false # MX 記錄不支援 Proxy
}
# SPF 記錄 — 防止郵件偽造
resource "cloudflare_record" "spf" {
zone_id = var.cloudflare_zone_id
name = "example.com"
content = "v=spf1 include:_spf.google.com ~all"
type = "TXT"
ttl = 3600
proxied = false
}
# 內部服務 — 不開 Proxy(只做 DNS 解析)
resource "cloudflare_record" "gitlab" {
zone_id = var.cloudflare_zone_id
name = "gitlab"
content = "10.10.1.50"
type = "A"
ttl = 300
proxied = false # 內部服務不經過 Cloudflare
}Nginx Upstream + Health Check 設定
# /etc/nginx/conf.d/loadbalancer.conf
# Nginx L7 Load Balancer 設定 — 含 health check 與多種演算法範例
# === 方案 1: Weighted Round Robin + Passive Health Check ===
upstream app_backend {
# 權重分配:server1 處理 50% 流量,server2 和 server3 各 25%
server 10.10.1.101:8000 weight=5 max_fails=3 fail_timeout=30s;
server 10.10.1.102:8000 weight=3 max_fails=3 fail_timeout=30s;
server 10.10.1.103:8000 weight=2 max_fails=3 fail_timeout=30s;
# max_fails=3: 連續 3 次失敗後標記為不健康
# fail_timeout=30s: 標記不健康後 30 秒內不分配流量,30 秒後重新嘗試
# 備用伺服器 — 只有上面全掛時才啟用
server 10.10.1.199:8000 backup;
# 長連接池 — 減少 TCP 建立連線的開銷
keepalive 32;
}
# === 方案 2: Least Connections ===
upstream app_least_conn {
least_conn;
server 10.10.1.101:8000;
server 10.10.1.102:8000;
server 10.10.1.103:8000;
keepalive 32;
}
# === 方案 3: IP Hash(Session Persistence) ===
upstream app_sticky {
ip_hash;
server 10.10.1.101:8000;
server 10.10.1.102:8000;
server 10.10.1.103:8000;
keepalive 32;
}
# === 前端 Server 設定 ===
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
# 真實 IP(Cloudflare Proxy 模式下需要)
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 2400:cb00::/32;
real_ip_header CF-Connecting-IP;
# API 請求 → app_backend(Weighted Round Robin)
location /api/ {
proxy_pass http://app_backend;
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_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
# 如果當前 Server 回傳錯誤,自動嘗試下一台
proxy_next_upstream error timeout http_502 http_503;
proxy_next_upstream_tries 2;
}
# WebSocket — 需要 sticky session
location /ws/ {
proxy_pass http://app_sticky;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400s; # WebSocket 長連接 24 小時
}
# 健康檢查端點 — 不轉發到 upstream
location /nginx-health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
}
}Nginx 靜態資源快取設定
# /etc/nginx/conf.d/static-cache.conf
# 靜態資源快取策略 — 搭配 CDN 使用
# 快取路徑設定
proxy_cache_path /var/cache/nginx/static
levels=1:2
keys_zone=static_cache:10m # 10MB 記憶體存放 key
max_size=10g # 最大磁碟用量 10GB
inactive=60d # 60 天沒被存取就移除
use_temp_path=off;
server {
listen 443 ssl http2;
server_name static.example.com;
ssl_certificate /etc/nginx/ssl/example.com.crt;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
# === 帶 hash 的靜態資源(JS/CSS/Font)— 長期快取 ===
# 檔名範例: app.a1b2c3d4.js, style.e5f6g7h8.css
location ~* \.(js|css|woff2?|ttf|eot)$ {
root /usr/share/nginx/html;
# 瀏覽器快取 1 年 + immutable(不需要重新驗證)
add_header Cache-Control "public, max-age=31536000, immutable";
add_header X-Cache-Status "STATIC";
# Nginx proxy cache 也快取 30 天
proxy_cache static_cache;
proxy_cache_valid 200 30d;
# 啟用 gzip 壓縮
gzip on;
gzip_types text/css application/javascript application/x-javascript;
gzip_min_length 1024;
# CORS — 允許字型跨域載入
add_header Access-Control-Allow-Origin "*";
}
# === 圖片 — 中期快取 ===
location ~* \.(jpg|jpeg|png|gif|webp|avif|svg|ico)$ {
root /usr/share/nginx/html;
add_header Cache-Control "public, max-age=2592000"; # 30 天
add_header X-Cache-Status "STATIC";
# 圖片通常已壓縮,不需要 gzip
gzip off;
}
# === HTML — 短期快取或不快取 ===
location ~* \.html$ {
root /usr/share/nginx/html;
# 可以快取但每次需要重新驗證
add_header Cache-Control "no-cache";
add_header X-Cache-Status "DYNAMIC";
}
# === API Proxy — 不快取 ===
location /api/ {
proxy_pass http://app_backend;
proxy_no_cache 1;
proxy_cache_bypass 1;
add_header Cache-Control "no-store";
add_header X-Cache-Status "BYPASS";
}
# === 快取狀態 Header(方便 debug) ===
add_header X-Cache $upstream_cache_status;
}Cloudflare Cache Rules(Terraform)
# cloudflare-cache-rules.tf — CDN 快取規則設定
# 取代舊版 Page Rules,使用新版 Cache Rules(更靈活)
resource "cloudflare_ruleset" "cache_rules" {
zone_id = var.cloudflare_zone_id
name = "Cache Rules"
description = "CDN cache strategy for different content types"
kind = "zone"
phase = "http_request_cache_settings"
# Rule 1: 靜態資源 — 積極快取
rules {
action = "set_cache_settings"
action_parameters {
cache = true
browser_ttl {
mode = "override_origin"
default = 2592000 # 瀏覽器快取 30 天
}
edge_ttl {
mode = "override_origin"
default = 2592000 # Edge 快取 30 天
}
}
expression = "(http.request.uri.path.extension in {\"js\" \"css\" \"png\" \"jpg\" \"jpeg\" \"gif\" \"webp\" \"svg\" \"woff\" \"woff2\" \"ttf\" \"eot\" \"ico\"})"
description = "Cache static assets for 30 days"
enabled = true
}
# Rule 2: API 路徑 — 完全繞過快取
rules {
action = "set_cache_settings"
action_parameters {
cache = false
}
expression = "(starts_with(http.request.uri.path, \"/api/\"))"
description = "Bypass cache for API endpoints"
enabled = true
}
# Rule 3: HTML 頁面 — 短期快取
rules {
action = "set_cache_settings"
action_parameters {
cache = true
browser_ttl {
mode = "override_origin"
default = 0 # 瀏覽器不快取(每次重新驗證)
}
edge_ttl {
mode = "override_origin"
default = 3600 # Edge 快取 1 小時
}
}
expression = "(http.request.uri.path.extension eq \"html\" or http.request.uri.path eq \"/\")"
description = "Cache HTML with short TTL"
enabled = true
}
# Rule 4: 使用者特定內容 — 不快取
rules {
action = "set_cache_settings"
action_parameters {
cache = false
}
expression = "(starts_with(http.request.uri.path, \"/user/\") or starts_with(http.request.uri.path, \"/account/\") or starts_with(http.request.uri.path, \"/admin/\"))"
description = "Bypass cache for user-specific content"
enabled = true
}
}
# Cloudflare Cache Purge — 透過 API 清除快取
# 使用 curl 範例:
# 清除所有快取:
# curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache" \
# -H "Authorization: Bearer ${CF_API_TOKEN}" \
# -H "Content-Type: application/json" \
# --data '{"purge_everything":true}'
#
# 清除特定 URL:
# curl -X POST "https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/purge_cache" \
# -H "Authorization: Bearer ${CF_API_TOKEN}" \
# -H "Content-Type: application/json" \
# --data '{"files":["https://example.com/static/style.css"]}'驗證指令
# === DNS 驗證 ===
# 查詢 A 記錄
dig api.example.com A +short
# 查詢 CNAME 記錄
dig www.example.com CNAME +short
# 驗證 Cloudflare Proxy 是否生效(應該看到 Cloudflare 的 IP)
dig api.example.com +short
# 回傳類似 104.21.x.x 或 172.67.x.x 就是 Cloudflare IP
# 檢查 DNS 傳播狀態(使用不同 DNS resolver)
dig @8.8.8.8 api.example.com +short # Google DNS
dig @1.1.1.1 api.example.com +short # Cloudflare DNS
# === Load Balancer 驗證 ===
# 連續發請求,觀察是否分配到不同後端
for i in $(seq 1 10); do
curl -s -o /dev/null -w "%{http_code} %{remote_ip}\n" https://api.example.com/health
done
# 檢查 Nginx upstream 狀態
nginx -T | grep -A 10 "upstream"
# === CDN 快取驗證 ===
# 檢查回應 header 中的快取狀態
curl -sI https://static.example.com/assets/app.js | grep -E "(cf-cache|cache-control|age|x-cache)"
# cf-cache-status: HIT → CDN 快取命中
# cf-cache-status: MISS → 未命中,已回源
# cf-cache-status: EXPIRED → 快取已過期
# 檢查 TTL 和 Cache-Control
curl -sI https://api.example.com/api/users | grep -i cache-control
# 應該看到 no-store(API 不快取)常見問題與風險
-
DNS 快取導致切換延遲:這是最常見的問題。改了 DNS 記錄但使用者還是連到舊 IP,因為 TTL 還沒過期。更麻煩的是,有些 ISP 的 DNS resolver 不遵守 TTL,會強制快取更長時間。避免方式:遷移前提前降低 TTL;不依賴純 DNS 切換做 failover,搭配 Cloudflare 的 Load Balancing 或 health check。
-
CDN 快取了不該快取的內容:這是安全問題。如果 API 回應被 CDN 快取,使用者 A 的個人資料可能被使用者 B 看到。避免方式:API 回應一律加上
Cache-Control: no-store;在 CDN 設定中明確排除/api/*路徑;使用Varyheader 區分不同使用者的回應。 -
Load Balancer 單點故障:如果只有一台 Nginx 做 Load Balancing,它本身就是單點。避免方式:部署兩台 Nginx + keepalived(VRRP)做 Active-Passive;或使用雲端的 Load Balancer 服務(AWS ALB/NLB),它們本身就是高可用的。
-
Health Check 過於寬鬆或嚴格:Health Check 間隔太長或容錯次數太多,會導致故障伺服器長時間仍在接收流量。反之,太敏感(例如 1 次 timeout 就標記不健康)則會導致暫時性的網路波動觸發不必要的切換。建議設定:每 5 秒檢查一次,連續 3 次失敗標記不健康,連續 2 次成功標記恢復。
-
CDN Cache Stampede(快取雪崩):大量使用者同時存取的熱門資源快取同時過期,所有請求同時回源到後端,瞬間流量可能壓垮伺服器。避免方式:使用 stale-while-revalidate(Cloudflare 支援),在快取過期時先回傳舊內容,同時在背景更新;或對熱門資源設定更長的 Edge TTL。
-
Mixed Content 和協定問題:Cloudflare Proxy 模式下,Cloudflare 到你的伺服器可能是 HTTP(Flexible mode),但使用者看到的是 HTTPS。如果你的應用根據 protocol 生成連結,可能會產生 mixed content 問題。建議設定為 Full (Strict) 模式:Cloudflare 到源站也走 HTTPS。
-
Origin IP 洩漏:開了 Cloudflare Proxy 但真實 IP 從其他管道洩漏(例如 MX 記錄、歷史 DNS 記錄、email header),攻擊者可以直接繞過 Cloudflare 攻擊源站。避免方式:源站防火牆只允許 Cloudflare 的 IP range 存取 80/443;不要在同一個域名下有 proxied 和 non-proxied 的子域名指向同一台伺服器。
優點
- DNS + CDN + Load Balancing 三者搭配,可以同時解決延遲、可用性和擴展性的問題
- Cloudflare 免費方案就提供 DNS + CDN + DDoS 防護,對中小型團隊非常友善
- 使用 Terraform 管理 DNS 和 CDN 規則,可以版本控制、審計、快速重建
- Load Balancer 的 Health Check 機制讓故障自動切換,不需要人工介入
缺點 / 限制
- 多一層就多一個可能出問題的環節,故障排查時需要逐層檢查(DNS → CDN → LB → Backend)
- CDN 快取引入了資料一致性的問題,快取更新有延遲
- Cloudflare Proxy 模式對非 HTTP 協定的支援有限
- Sticky Session 限制了 Load Balancer 的負載均衡效果,應該盡量讓應用 stateless
- 免費版 Cloudflare 的功能有限(例如沒有 WAF 自訂規則、沒有 Tag-based cache purge)