雲端架構
用 AWS 架靜態網站,一個月實際要多少錢
我目前有十個網站跑在 S3 + CloudFront 上。這篇把實際的帳單拆開來講,順便講三個我踩過、會讓帳單暴增的坑。
先講結論
一個每月 5 萬次瀏覽、傳輸 20GB 的靜態網站,AWS 的費用大約是每月 1.7 到 2.5 美元。
其中 CloudFront 的流量費是主要成本,S3 的儲存費幾乎可以忽略。真正的固定成本反而是網域(每年約 12 美元)與 Route 53 託管區(每月 0.5 美元)。
帳單拆解
以我其中一個站台上個月的實際數字為例:
| 項目 | 用量 | 費用(USD) |
|---|---|---|
| S3 標準儲存 | 240 MB | 0.006 |
| S3 PUT 請求 | 3,100 次(部署 sync) | 0.016 |
| S3 GET 請求 | 8,400 次 | 0.003 |
| CloudFront 流量傳出 | 19.2 GB | 1.63 |
| CloudFront HTTP 請求 | 412,000 次 | 0.41 |
| CloudFront Function | 412,000 次 | 0.04 |
| Route 53 託管區 | 1 個 | 0.50 |
| Route 53 查詢 | 180 萬次 | 0.72 |
| 合計 | 約 3.33 |
有幾件事值得注意。
第一,Route 53 佔了 1.22 美元,超過三分之一。 這是很多人沒預期到的。託管區本身每月 0.5 美元是固定的,跟流量無關;查詢費則會隨流量成長。如果你有十個網域,光託管區就是每月 5 美元。
第二,CloudFront 請求數的費用不可忽視。 41 萬次請求收 0.41 美元。一個頁面如果載入 30 個資源,5 萬次瀏覽就是 150 萬次請求。減少請求數 = 直接省錢,而且它同時也讓網站更快。
第三,S3 的部分幾乎是零。 240MB 的靜態檔案,儲存費不到一美分。所以不要為了省 S3 空間而壓縮圖片壓到畫質變差——那個方向省不到錢。
免費額度的真正邊界
AWS 的免費額度有兩種,很多人搞混:
十二個月免費額度(只有新帳號的前一年):S3 5GB 儲存、2 萬次 GET、2 千次 PUT。
永久免費額度(所有帳號都有):CloudFront 每月 1TB 傳出、1000 萬次 HTTP 請求、200 萬次 CloudFront Function 呼叫。
第二項才是重點。CloudFront 的永久免費額度非常大方——每月 1TB 流量,對絕大多數個人網站來說根本用不完。上面那張帳單其實有一大部分是被免費額度吸收掉的。
換句話說,一個中小型的靜態網站,實際上很可能只需要付 Route 53 的錢。
三個會讓帳單暴增的陷阱
陷阱一:Cache-Control 沒設好
如果你的 HTML 和資源檔案都沒有設 Cache-Control,CloudFront 的預設 TTL 只有 24 小時,而瀏覽器可能完全不快取。結果是每個回訪的使用者都重新下載一次全部資源。
正確的做法是把檔案分成兩類:
# 帶 hash 的資源檔:長期快取
aws s3 sync ./dist s3://bucket \
--exclude "*.html" \
--cache-control "public,max-age=31536000,immutable"
# HTML:不快取,每次都驗證
aws s3 sync ./dist s3://bucket \
--exclude "*" --include "*.html" \
--cache-control "public,max-age=0,must-revalidate"
帶內容 hash 的檔名(app.a3f9c2.js)可以安全地設一年,因為內容變了檔名就會變。HTML 則必須每次驗證,否則使用者會拿到舊版。
這一項設好之後,我某個站的流量費降了約 40%。
陷阱二:無節制的 Invalidation
CloudFront 的快取失效,每月前 1000 個路徑免費,超過的部分每個路徑 0.005 美元。
聽起來很便宜,但如果你的部署腳本寫 --paths "/*",那每次部署算一個路徑——這其實很划算。真正的問題是相反的寫法:有人為了「精確」而列出全部 300 個檔案路徑,一天部署五次,一個月就是 45,000 個路徑,收費 220 美元。
規則很簡單:要嘛用 /*,要嘛只列真的必要的少數幾個。 不要列出全部檔案。
陷阱三:S3 bucket 被公開直連
如果你的 S3 bucket 設成公開讀取,而有人(或搜尋引擎)直接抓 S3 的網址而不是 CloudFront 的網址,那些流量就完全不受 CloudFront 免費額度保護,而且 S3 的流量費比 CloudFront 貴。
解法是使用 Origin Access Control(OAC):bucket 完全封閉,只允許 CloudFront 存取。
BlockPublicAccess: BLOCK_ALL
Origin: S3BucketOrigin.withOriginAccessControl(bucket)
這同時也是安全上的正確做法。
靜態網站的一個常見坑:路徑改寫
用 S3 當來源有一個煩人的行為:它不會自動補 index.html。
/guides/ 這個路徑,瀏覽器要的是 guides/index.html,但 S3 只會去找一個叫 guides/ 的物件,然後回 403。
解法是加一個 CloudFront Function(viewer request 階段):
function handler(event) {
var request = event.request;
var uri = request.uri;
if (uri.endsWith("/")) {
request.uri += "index.html";
} else if (!uri.includes(".")) {
request.uri += "/index.html";
}
return request;
}
CloudFront Function 每百萬次呼叫 0.10 美元,而且有每月 200 萬次的永久免費額度,成本可以忽略。
順帶一提:不要用「404 導向 index.html」來處理這件事。那是 SPA 的做法,會讓所有不存在的路徑都回傳 200,搜尋引擎會判定為 soft 404,對 SEO 有實質傷害。靜態網站應該讓 404 就是 404。
什麼時候不該用這個架構
講了這麼多好處,也要講界線。這個架構不適合:
- 需要伺服器端渲染個人化內容的網站。 每個使用者看到的頁面都不同時,CDN 快取的價值就消失了。
- 內容更新極為頻繁的網站。 每分鐘都要更新的話,快取失效的管理會變成負擔。
- 需要複雜後端邏輯的應用。 那應該是 Lambda 或容器的工作,靜態站只負責前端。
但如果你的網站是產品頁、文件、部落格、作品集——也就是大多數網站——這個架構在成本、速度與維運負擔上都很難被超越。每月三美元、全球 CDN、沒有伺服器要管,這個組合在十年前是不存在的。
其他文章
內容有錯誤或想補充?寄信到 support@throuzlabs.com, 我會更新並在文中註明修訂日期。