部落格

我們如何把部署的 502 錯誤降到零

每次部署 Mythex 後端時,都會有少數請求失敗,回傳 502 Bad Gateway。數量不多,但部署期間的 502 代表有人點擊後什麼都沒發生,或是他們正在看的建置停頓了一下。我們將它追查到兩個獨立的問題:一個與部署互相爭奪的自我修復看門狗,以及伺服器停止監聽與負載平衡器停止把流量送過去之間的時間差。修正之後連續三次正式部署,數量從 10 降到 6,再降到 0。

架構概況

我們的 API 與編排器以容器的形式,運行在一組小型伺服器上,前面是 AWS Application Load Balancer(ALB)。部署會一次更新一台伺服器:排空舊容器、啟動新容器,然後移到下一台。每台伺服器上的看門狗會檢查容器是否正在運行,並重新啟動任何沒有運行的容器——它的存在是為了讓凌晨 3 點崩潰的程序自行復原,不必有人被叫醒。

這兩者單獨來看都合理。合在一起就不合理了。

問題一:看門狗與部署競爭

看門狗每分鐘執行一次,並且在 一次 健康檢查失敗後就會「修復」。而部署會刻意排空服務最長 150 秒——在這段時間內,健康檢查 本來就應該 失敗。因此看門狗在每次部署期間都會觸發。

大多數時候它輸掉這場競爭,沒有造成傷害。但有一次它贏了:它在部署中途改寫了 compose 檔案(描述哪些容器應該運行的檔案),讓一台伺服器完全沒有閘道容器——而部署工作卻回報成功。

修正方式:

  • 同一把鎖。 部署現在會取得與伺服器啟動腳本相同的檔案鎖,因此看門狗與部署永遠不會同時運行。
  • 四次警告,而不是一次。 看門狗需要連續四次健康檢查失敗才會採取行動。正在排空的服務不再看起來像是已經死亡。
  • 單一定義。 compose 檔案現在只有一個來源,發佈到各伺服器,而不再是可能彼此偏離的副本。
  • 驗證,而不是信任。 部署工作不再因為某個命令回傳而通過。它會在任何一台伺服器的 API、編排器、閘道與發佈工作程序都確認正常之後,才能成功。

問題二:伺服器比流量先離開

第二個問題比較隱晦。當部署排空一台伺服器時,該伺服器會開始以 503 回應它的健康端點,好讓負載平衡器將它移出輪替。我們的伺服器這樣做了 5 秒,然後才關閉。

但 ALB 只會定期檢查健康狀態,而且需要連續多次失敗,才會停止把請求路由到某個目標——從頭到尾大約需要 60 秒。因此,大約有 55 秒的時間,負載平衡器仍把真實的請求送往一台已經停止監聽的伺服器。那些就是我們的 502。

修正方式是讓兩條時間線彼此一致:

  • 目標群組現在每 5 秒 檢查一次,連續 2 次失敗後才將目標標記為不健康——大約 10 秒就能察覺。
  • 排空流程在開始讓健康檢查失敗後,仍會繼續為真實流量服務 15 秒,因此總是有重疊的時間。
  • 這些設定存放在佈建基礎設施的腳本中,而不是某人曾在主控台點選過一次的設定。

讓數字彼此相互約束

這類逾時彼此相依。排空時間必須長於負載平衡器察覺所需的時間;排空加上關閉必須在容器被強制終止之前完成。某人在六個月後只改動其中一個數字,就很容易不知不覺地重新引入這個問題。

因此我們把這些關係寫成測試:排空時間必須大於健康檢查間隔乘以失敗門檻;重新被標記為健康所需的時間必須保持短暫;整個排空過程必須在容器的停止寬限期內完成。只要改動一個數字而不同時調整其他數字,CI 就會失敗。我們還對測試本身做了變異測試——把每個數字改成錯誤的值,並確認有測試能抓到它。

測量部署很容易出錯

我們最初的測量顯示,一次乾淨的部署大約有 3% 失敗。其實並沒有。我們把每個非 200 的回應都算進去,但排空期間健康端點回傳的 503 並不是失敗——那是排空在做它該做的事,告訴負載平衡器不要在它仍然為真實請求服務時選擇這台伺服器。

使用者真正感受到的是 502:請求沒有到達任何地方。只計算這些,才能得到真實的情況——三次正式部署分別為 10、6、0。

我們從中學到什麼

  • 自我修復需要知道計畫內的停機。 一個無法分辨部署與當機的看門狗,遲早會與部署爭鬥。
  • 排空是一種交握。 伺服器必須持續服務,直到負載平衡器可證明已停止送出流量為止,而這個時間是在負載平衡器上設定的,不是在伺服器上。
  • 把不變量編寫成測試。 營運數字會逐漸偏離。把它們彼此關聯起來的測試則不會。
  • 在測量之前先決定要測量什麼。 「錯誤」是錯的指標;「沒有到達任何地方的請求」才是正確的指標。

如果你在 Mythex 上發佈 App,這些都不會被你看到——這正是重點。相關文章:你的已發佈 App 如何在重新發佈時保持上線。

Mythex · Pricing · 用 AI 打造任何東西 · 網站與應用程式範本 · Mythex 比較 · AI 應用程式開發詞彙表 · 免費的 AI 應用程式開發工具 · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港)