跳轉到

10.1 上線前檢查清單:從「能跑」到「敢上線」#

本節目標#

寫完程式碼、功能能跑,只是及格線;真正「上線」需要的不是再多寫一個功能,而是把前面九章散落各處的設定系統性驗證一遍。本節把 DevOps 的關鍵思維——「部署不是事件,而是可重複的程序」——落實為一份可直接照著勾的檢查清單,涵蓋 DNS、HTTPS、安全標頭、CORS、備份、監控、錯誤頁與 API 嚴格模式八個面向。

為何需要檢查清單:人的記憶不可靠#

Atul Gawande 在《The Checklist Manifesto》的核心論點:即使專家也會在例行工作中漏掉已知步驟。飛行有飛行前檢查單、手術有手術安全清單,軟體上線也應有。

上線事故的典型分類:

40%  設定疏漏(忘了開 HTTPS 重定向、忘了 CORS 白名單)
25%  資料風險(無備份、無法還原)
20%  觀測性缺失(出事時不知道,使用者先發現)
15%  權限與模式錯誤(開發模式 API 上到正式環境)

清單的存在意義:把這些「已知會忘的事」外包給一張紙。

檢查清單#

一、DNS 與網域#

  • 網域 NS 已指向 Cloudflare(查 dig NS example.com,應回 Cloudflare 的名稱伺服器)
  • 所有子網域的 DNS 記錄已建立,無遺漏(拼錯一個字母的子域會指向不存在的地方)
  • 暫存用途的萬用字元 *.example.com 記錄已確認是否真的需要(過寬 = 攻擊面)
  • TTL 已考前考量:正式環境 300–3600 秒;遷移期間調低以便回滾
  • 開啟 Cloudflare Proxy(橘雲)保護源站的記錄,僅必需直連的服務用灰雲
  • Email 相關的 SPF、DKIM、DMARC 記錄未被 CDN 遷移破壞

二、HTTPS 強制與 HTTP 重寫#

  • Zone 層級已開啟 Always Use HTTPS(HTTP 301 到 HTTPS)
  • SSL/TLS 模式為 Full (strict),絕不是 Flexible(Flexible 會讓邊緣到源站走明文,是本書反覆強調的反模式)
  • Edge Certificates:Universal SSL 已啟用;若用自有憑證,到期日已設告警
  • Tough case:API 子域(如 api.example.com)同樣有憑證覆蓋(SAN 或獨立憑證)
\[ \text{HTTPS 只是把「WWW」那行字換成鎖頭?不是——它是 TLS 握手 + 憑證驗證的家庭作業} \]

三、安全標頭與 Content Security Policy#

安全標頭是「瀏覽器的防火牆」,在 Worker 或 Middleware 統一注入:

  • CSP(Content-Security-Policy):限制腳本、樣式、圖片的來源;生產環境不該出現 'unsafe-inline'、'unsafe-eval'
  • HSTS(Strict-Transport-Security):max-age=31536000; includeSubDomains,逼瀏覽器永遠用 HTTPS
  • X-Frame-Options:DENY 或 SAMEORIGIN,防點擊劫持
  • X-Content-Type-Options: nosniff,防 MIME 偽裝
  • Referrer-Policy:strict-origin-when-cross-origin
  • Permissions-Policy:關掉用不到的相機、麥克風、地理定位

用 securityheaders.com 掃一次,等級目標至少 A。

四、CORS#

若前端與 API 不同源(例如 app.example.com 與 api.example.com),CORS 是繞不開的一關:

  • Access-Control-Allow-Origin 採明確白名單,絕不是 *(尤其 API 帶 cookie 時 * 直接無效)
  • Preflight(OPTIONS)回覆包含 Allow-Methods、Allow-Headers,且 OPTIONS 請求在 Worker 中短路回覆,不打到後端邏輯
  • Allow-Credentials: true 時,Origin 必須是具體值
  • 用 curl -X OPTIONS -H "Origin: https://app.example.com" -H "Access-Control-Request-Method: POST" 實測 preflight

五、資料備份(D1 export)#

Cloudflare D1 是 SQLite 資料庫,做成備份是一個命令的事,但必須是排程化的:

npx wrangler d1 export my-db --remote --output backup-$(date +%F).sql
  • 每日自動匯出(GitHub Actions cron 或 Cloudflare Cron Trigger 觸發)
  • 備份檔存到 R2 或本地,驗證過至少一次還原練習(沒還原過的備份不算備份)
  • 保留多份版本,不只最新一份
  • KV 與 R2 的重要資料同樣有匯出策略

檢查清單的狠話:上線前做不到「隨手 一行命令還原到昨天」的資料庫,不上線。

六、監控與告警#

  • Cloudflare Analytics / GraphQL Analytics API 已瞭解,能查 5xx 上升、WAF 攔截暴增
  • 關鍵端點有可用性檢查(Cloudflare Health Checks 或外部行程如 UptimeRobot)
  • 告警能通知到人:Email 之外,至少一條即時通道(Slack / Discord webhook)
  • Workers 的 CPU time 使用量沒有觸及方案限制
  • Pages 部署失敗的事件有通知(GitHub Actions 失敗郵件或 Slack webhook)
  • Workers 的錯誤率已納入觀察(Cloudflare Analytics 或 GraphQL API 拉指標)

告警法則:會吵的告警會被靜音。告警數量、訊息、閾值都要能讓值班的人一秒判斷「要不要爬起來」。

七、自訂錯誤頁#

Cloudflare 出錯時(如 502、522 源站逾時),預設會吐無本地資訊的英文白頁:

  • 在 Rules → Custom Error Pages 上傳自訂錯誤頁(500、502、503、522)
  • 錯誤頁本身是純靜態、外部資產用絕對 URL(因為出錯時 zone 可能連 stylesheet 都載不到)
  • 給使用者下一步指引:狀態頁連結、重試時間
  • Pages Functions 中有 onError/catch-all 回覆統一 JSON 錯誤格式

八、API 的開發人員嚴格模式#

最後一項最容易被忘掉:不要把開發者工具放進正式環境。

  • API 模式嚴格化:未知 route 回 404 + 統一錯誤體,不是靜默通過
  • 錯誤不含堆疊(stack、SQL error、內部 IP)——那是攻擊者的快捷鍵
  • Swagger/OpenAPI 文件若對外公開,正式環境只讀;寫入端點需 Access 鑑證
  • Rate limiting 已上(Cloudflare WAF Rate Limiting Rule 或 Worker 層)
  • .env 與 secret 掃描:確認沒有 API key 出現在客戶端 bundle(npx gitleaks detect)

執行策略:清單要進 CI#

一張只存在 PDF 裡的清單會死的比程式碼快。把它拆成兩條層次:

層次 做法
可自動驗證 寫進 CI 腳本:curl -I 查標頭、curl -X OPTIONS 查 CORS、dig 查 DNS
必須人工判斷 製作 docs/release-checklist.md,部署 PR 模板夾帶勾選
# 前四項清單的 10 秒自動驗證
curl -sI https://example.com | grep -iE 'strict-transport|content-security|location: https'
dig +short NS example.com | grep -c cloudflare

小結與練習#

回顧#

  • 上線不是事件而是程序;檢查清單是把「已知會忘的事」外部化。
  • 八大檢查面:DNS、HTTPS、安全標頭、CORS、備份、監控、錯誤頁、API 嚴格模式。
  • 備份的定義包含「還原演練」;監控的定義包含「告警可行動」。
  • 清單的一半應該死在 CI 裡,而不是躺在 PDF 裡。

練習#

  1. 對你的網站跑 securityheaders.com 與 curl -sI,記下缺的每一項標頭,並在 Worker 中補齊大一個等級。
  2. 寫一個 GitHub Actions workflow:每日 cron 執行 wrangler d1 export 並上傳到 R2 指定前綴。
  3. 把本節清單中最容易自動化的四項做成一個 scripts/preflight.sh,未通過時 CI 失敗。
  4. 故意破壞一項(如關掉 Always Use HTTPS),再用你的腳本把它抓出來——驗證清單真的有效。