cover

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

  1. DNS 記錄類型:DNS 不只是「域名對應 IP」,不同的記錄類型有不同的用途:

    記錄類型用途範例
    A域名 → IPv4 位址api.example.com → 203.0.113.10
    AAAA域名 → IPv6 位址api.example.com → 2001:db8::1
    CNAME域名 → 另一個域名(別名)www.example.com → example.com
    MX指定郵件伺服器example.com → mail.example.com (priority 10)
    TXT任意文字,常用於驗證與安全策略SPF: v=spf1 include:_spf.google.com ~all
    SRV指定服務的 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)時會用到。

  2. 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,有人要等幾小時。

  3. 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 被公網查到。

  4. 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-IP header。某些服務(例如 SSH、非 HTTP 協定)不支援 Proxy 模式,需要關閉。

核心概念 — Load Balancing

  1. 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)通常用在更底層的場景。

  2. 負載均衡演算法

    • 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 數量變動時,大量使用者會被重新分配。
  3. Health Check:主動 vs 被動

    • Active Health Check:Load Balancer 定時主動發 HTTP 請求到後端的健康檢查端點(例如 GET /health),如果連續 N 次回傳非 200 就標記為不健康,停止分配流量。恢復後再重新加入。
    • Passive Health Check:Load Balancer 不主動探測,而是根據實際請求的回應結果判斷。如果某台 Server 連續回傳 502/503 或逾時,就標記為不健康。
    • 建議兩者搭配使用:Active 用來快速偵測明確的故障(服務掛了),Passive 用來偵測隱性的問題(服務還活著但回應異常慢或錯誤率飆升)。
  4. 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 場景)
  5. Nginx upstream 設定:Nginx 是最常用的 L7 Load Balancer。透過 upstream 區塊定義後端伺服器群組,在 server 區塊中用 proxy_pass 轉發。支援 Round Robin、Weighted、Least Connections、IP Hash 等演算法,以及 health check 參數。

  6. HAProxy 作為替代方案:HAProxy 是另一個非常成熟的 Load Balancer,相比 Nginx 在純負載均衡場景下有一些優勢:更精細的 health check 設定、原生的連線排空(connection draining)支援、即時的統計儀表板(stats page)。如果你的場景是高流量且需要精細控制,HAProxy 值得考慮。但如果你已經用 Nginx 做反向代理和 TLS 終止,加 Load Balancing 只需要多寫幾行設定,不需要額外引入 HAProxy。

核心概念 — CDN

  1. CDN 的運作原理:CDN(Content Delivery Network)在全球部署了大量的邊緣節點(Edge Server / PoP, Point of Presence)。當使用者請求一個資源時:

    • Cache HIT:邊緣節點已經有這個資源的快取,直接回傳,不需要回源。延遲最低。
    • Cache MISS:邊緣節點沒有快取,向你的源站(Origin Server)請求資源,拿到後快取一份再回傳給使用者。下次同樣的請求就會變成 Cache HIT。
    • Cache EXPIRED:快取已過期,邊緣節點會向源站發送條件式請求(If-Modified-SinceIf-None-Match),如果資源沒變就回傳 304 Not Modified,邊緣節點更新過期時間繼續使用快取。

    CDN 的最大價值是把靜態資源「推到離使用者最近的地方」。一個台灣的使用者存取放在美國的伺服器,延遲可能 200ms+。如果靜態資源已經快取在台灣的邊緣節點,延遲可以降到 10ms 以內。

  2. 快取策略:什麼該快取、什麼不該快取

    應該快取的

    • 靜態資源:CSS、JavaScript、圖片、字型、影片
    • 公開的 HTML 頁面(如果內容不頻繁變動)
    • API 回應中的公開且不常變動的資料(例如國家列表、分類清單)

    不應該快取的

    • 含有個人資料的 API 回應(使用者資訊、訂單、帳單)
    • 需要即時更新的動態內容
    • 需要認證才能存取的資源
    • POST/PUT/DELETE 請求

    黃金法則:靜態資源全部快取,動態 API 回應預設不快取,除非你明確知道可以快取且設定了正確的失效策略。

  3. 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 就等於自動失效舊快取,這是最可靠的策略
  4. 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/* 路徑;使用 Vary header 區分不同使用者的回應。

  • 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)

延伸閱讀