CloudFront cache 錯頁面 Debug 流程(Rails 篇)

CloudFront cache 錯頁面 Debug 流程(Rails 篇)
阿蘇火山口前 - 煙大的要命 - 跟 CDN 一樣看嚨某餒 - 圖文不符

以前在 bootcamp 學習套用 CDN 的時候,因為懂的少也不懂得懷疑,覺得反正部署之後,圖片有經過 CDN 、顯示得出來就好,其他不管。

但現在有了經驗、也踩了各種雷坑之後,疑心病加重!每個設定都覺得疑點重重,在想幹嘛要有這個,設定這是想做什麼!每個都覺得這一定有問題!!決定用這篇紀錄我的實作心得。

事件緣起:Reddit 真實案例

在 Reddit 上看到這篇討論,作者遇到了一個恐怖的狀況:CloudFront 把 A 使用者的頁面快取起來,卻回傳給 B 使用者看!

想像一下這個場景:

  • 付費用戶登入後看到的專屬內容,竟然被免費用戶看到了
  • 或者相反,免費用戶看到了付費才能看的頁面
  • 更糟的是,用戶 A 的個人資訊被用戶 B 看到了

Production 環境要是真的發生這種事情還得了啊!天啊!!!!緊張如我開始有了一連串的質疑!

問題:我的 CDN 設置可能 cache 錯頁面嗎?

CDN 基礎:Cache Hit / Cache Miss

首先,讓我們複習一下 CDN 是如何工作的:
下面根據剛剛 Reddit 討論畫出流程圖

情況 1:快取命中(Cache Hit)

情況 2:快取未命中(Cache Miss)

當使用者打開一個頁面時,請求會先送到 CDN。如果 CDN 上有快取的資料,就直接回傳(Cache Hit);如果沒有,就會再去找原始伺服器(Cache Miss)。

咦?如果這樣看,那我的 CDN 設置不是會有一樣的問題嗎?!?!我以為只有 assets 有快取誒?

首先看看參考 Rails 的說明:

CDN Request Caching While a CDN is described as being good for caching assets, it actually caches the entire request.

這個說法容易讓人誤解,讓人懷疑不只是 assets,而是任何 request 都會被快取...

💡
問題來了:什麼 request 會經過 CDN? 我原本以為只有像圖片、CSS 這類資源會被快取。但看起來,整個頁面都有可能被 CDN 快取,我開始緊張了。

參考 AWS Cloudfront 說明

A cache hit occurs when a viewer request generates the same cache key as a prior request, and the object for that cache key is in the edge location’s cache and valid.
By default, the cache key for a CloudFront distribution includes the following information:
- The domain name of the CloudFront distribution (for example, d111111abcdef8.cloudfront.net)
- The URL path of the requested object (for example, /content/stories/example-story.html)

💡 AWS Cloudfront 提到的概念,叫做「Cache Key」,用來決定:「這次請求要不要用快取的結果?」

最簡單的情況下,Cache Key 只會包含網址的路徑。例如:

/member/dashboard

只要兩個使用者都請求這個網址,CloudFront 就會認為他們是「要同一個頁面」,直接回傳快取!

問題就在這 ⚠️——
即使路徑一樣,不同使用者的頁面內容可能不同(例如登入身份不同、顯示資料不同),這樣就有可能把 A 的資料給了 B!

CloudFront 當然可以進一步設置 Cache Key,加入更多條件(像是 cookie、查詢參數、HTTP header 等),但如果你沒有特別設定,CloudFront 可能只會看路徑!(這邊先講一些小概念。更多 cache key 細節會之後再補充)

以 rails CDN 設置、CloudFront 設置,我真的需要擔心被 cache 的問題嗎?

參考下面 Rails 官方文件說明:
以這邊來看,非 assets 的 request 是不會進到 CDN 拿取資料的

但這是 Rails 的設置,但為什麼剛剛的上面會遇到使用者頁面被 cache 的問題,照理來說如果除了 assets 之外不會經過 CDN,我根本不用擔心會有問題啊!

兩種常見的 CDN DNS 佈署模式:

(1) 整個網域交給 CDN (2) 僅把靜態子網域交給 CDN

模式 描述 是否經過 CDN 風險
模式一 整個網站都透過 CDN 若設定錯誤,動態頁面也可能被快取
模式二 只有 static.example.com 用 CDN 否(動態頁面直達 origin) 比較安全
💡
「動態頁面」指的是頁面內容會根據使用者的狀態或資料不同而改變,例如登入後才會顯示使用者名稱、訂閱方案、個人資料等等,這些資料通常需要從後端即時撈取並渲染在頁面上,跟「圖片 / CSS / JS」這種靜態資源不一樣。

模式 1:整個網域交給 CDN,DNS 寫法

記錄類型 主機名 內容 (指向)
CNAME www.example.com cdn-provider.net
  • 設定簡單,所有流量都經過 CDN
  • 有風險:如果 Cache Policy 設定錯誤,動態頁面也會被快取
  • Cloudflare 就是用這種模式去做設置

模式 2:僅靜態子網域交給 CDN

記錄類型 主機名 內容 備註
CNAME static.example.com cdn-provider.net 靜態檔案子網域指向 CDN
A / AAAA example.com 原伺服器 IP 動態頁面直達 Origin server。
  • 安全:非 assets 內容不經過 CDN,不會有快取混淆問題
  • 靜態資源(圖片、CSS、JS)享有 CDN 加速
  • 如果沒有把主網域(如 example.com)透過 DNS 設成指向 CloudFront,那整個主網站就不會經過 CDN,只會讓特定子網域(如 static.example.com)走 CDN。(如果是使用 Rails, 記得還要設置 asset_host 要給 CDN 才會生效)

為了確認即使 CDN 設錯,也不會把動態頁面快取錯給其他人,我想確認 Rails 有沒有幫我預設加上正確的 HTTP Header。

Rails 如何保護動態頁面?

如果我沒特別設,Rails 有幫我加防快取 header 嗎?

Rails 預設 Header 設定

🔗參考官方 source code

可以看到預設 header 送 cache 是 private,而且一定要重新驗證,也就是快取內容只能保存在使用者自己的瀏覽器中,CDN、代理伺服器**不得快取。**這個機制也會保護動態內容不會被 cache

如何驗證這些 Header 是否生效?

curl -I <https://example.com/member/dashboard>
# 期待看到類似:
# Cache-Control: private, must-revalidate

三步驟自我健檢

第一步:DNS 指向誰?

# 檢查網域指向哪裡
dig www.udn.com
# 如果回傳的是 CDN 網址,代表使用模式 1
# 如果回傳的是你的伺服器 IP,代表使用模式 2

第二步:頁面是否被快取?

# 檢查 HTTP 回應標頭
curl -I <https://www.example.com/page>
# 注意看這些標頭:
# X-Cache: Hit from cloudfront (代表被快取了)
# 如果你是測試 動態頁面,這行不應該出現 Hit。出現了就表示 CDN 有快取,應該調查 Cache Policy 是否設錯

換用戶測試內容是否正確?

  1. 用無痕模式訪問頁面
  2. 登入後再訪問同一頁面
  3. 登出後再次訪問
  4. 檢查內容是否正確顯示

結語:我怎麼確定自己不會中招?

我目前是採用模式二(僅靜態子網域經過 CDN),所以可以避免 Reddit 上那種動態頁面被錯快取的災情。

Reddit 上那個案例,多半是用了整站走 CDN(模式一),又沒設好 Cache Policy,才導致資料快取錯誤。但說實話,我也不知道對方用什麼框架,所以也不能百分之百說「Rails 就不會有事」。

在我對 CDN 跟 cache 不熟的情境下,看到網路上有人遇到這個問題,我就會完全無法確定自己會不會發生,所以看到那種文章真的會很慌。

經過一連串排查下來才有確定的理由知道不會有問題,所以才想記錄下來也提醒自己:當不確定自己的設定到底在幹嘛的時候,其實就代表,任何一個錯誤都可能發生。(以上紀錄如果有錯,也歡迎來信跟我討論)