- date
- entry
- 017
- topic
- tooling
- rev
- —
部署完跑一次全站比對:用內容驗收,不用狀態碼
我在第一篇裡用三句話帶過這件事。它值得展開,因為那支腳本是我唯一信任的「部署成功」定義。
靜態站部署完,怎麼知道它真的好了?最常見的答案是打幾個網址看看有沒有 200。這個答案的問題在於,兩種最常見的壞法都回 200:
- 目錄 key 缺失——CloudFront 的 SPA fallback 把找不到的路徑換成首頁,狀態碼 200,內容是錯的
- 部署根本沒生效——舊版還在,狀態碼 200,內容是舊的
只看狀態碼,這兩種都測不到。要測到它們,得比對內容。
用 title 當指紋
不需要比對整頁。<title> 就夠了,理由是它同時滿足三個條件:每一頁都不一樣、位置固定好抽、而且不會因為快取版本號或時間戳而變動。
流程只有三步:
- 走訪本機 build 產物裡的每個
index.html,抽出 title - 把檔案路徑換算成線上網址
- 請求那個網址,抽出 title,比對
#!/usr/bin/env bash
set -uo pipefail # 注意沒有 -e,見下方說明
BASE="https://example.com"
DIST="dist"
fail=0
title_of() { # 從 stdin 抽第一個 <title>
tr '\n' ' ' | sed -n 's/.*<title>\(.*\)<\/title>.*/\1/p' | head -n1
}
while IFS= read -r f; do
rel="${f#"$DIST"/}"; rel="${rel%index.html}" # posts/foo/
url="$BASE/$rel"
want=$(title_of < "$f")
got=$(curl -fsS --max-time 10 "$url" | title_of)
if [ "$want" != "$got" ]; then
printf 'MISMATCH %s\n want: %s\n got : %s\n' "$url" "$want" "$got"
fail=$((fail + 1))
fi
done < <(find "$DIST" -name index.html)
[ "$fail" -eq 0 ] && echo "OK: all pages match" || echo "$fail page(s) wrong" >&2
exit $(( fail > 0 ))
三個刻意的決定
沒有 set -e。驗證腳本的價值在於跑完,然後給你一份完整的壞掉清單。第一個不符就中止,你只會知道有問題,不會知道有多少、在哪裡——那樣還得再跑一次才能規劃修補。
失敗時印出 want 和 got。這兩行直接告訴你是哪一種壞法:got 是首頁的標題就是目錄 key 問題,got 是這一頁的舊標題就是部署沒生效。診斷資訊要在錯誤訊息裡,不要留給下一輪調查。
curl -f 讓 4xx/5xx 直接算失敗。title 比對負責抓「回 200 但內容錯」,狀態碼失敗則由 -f 接住——兩種都要涵蓋。
再加一條:build 的身分
title 比對能證明「這一頁的內容對」,但如果一篇舊文章的內容從頭到尾沒變,它也會通過——即使這次部署完全沒生效。補上一個從 build 產生的識別碼就能封住這個缺口:
<meta name="build-sha" content="dea001c" />
EXPECTED=$(git rev-parse --short HEAD)
ACTUAL=$(curl -fsS "$BASE/" | sed -n 's/.*name="build-sha" content="\([^"]*\)".*/\1/p')
[ "$ACTUAL" = "$EXPECTED" ] || { echo "stale build: $ACTUAL != $EXPECTED" >&2; exit 1; }
這跟後端的 /version 是同一件事的靜態站版本:那個值必須由 build 產生,不能由人維護。
什麼時候跑
兩個時機,理由不同。
部署腳本的最後一步。放在 invalidation 之後——太早跑會讀到邊緣節點的舊快取,得到假的失敗,而假失敗比沒有檢查更糟,因為它會訓練你忽略這個檢查。
每天排一次。這條比較不直覺,但更重要:有些壞法是後來才發生的。下一次 sync --delete 會把上一輪補好的目錄 key 再刪一次,而那次部署的當下是通過的。只在部署時檢查,你抓不到這種。
我遇過的最貴一次是這樣:AWS session 在補目錄 key 的迴圈跑到一半過期,前面的檔案都同步完成,幾個頁面直接消失。腳本用了 set -e,所以連後面的 invalidation 都沒跑。當下沒有人發現,因為沒有東西在比對內容。
不要比對整頁
一個容易走過頭的方向是「乾脆比對整份 HTML 的雜湊」。不要——線上的頁面幾乎一定會跟本機有細微差異:注入的分析腳本、資產檔名的雜湊、伺服器加的註解。整頁比對會每天給你一堆假警報,而假警報的下場是這個檢查被關掉。
指紋要選穩定且有鑑別力的東西。title 是最好的起點;有需要再加上 <h1> 或 canonical,但每加一項就多一分假警報的機率。
如果只記得一件事
「部署完成」應該是一個可以驗證的斷言,不是腳本沒噴錯的副作用。 而在靜態站上,唯一夠強的驗證是把線上內容抓回來跟你手上那份比。三十行的事。
修訂紀錄
- 首次發布