攔下發布後立即當機的應用程式
9 月 25 日,有三次發布被標示為成功,但該應用程式的 API 每次開機幾秒鐘後就會當機。因為發布「成功」了,所以沒有執行復原;幾分鐘後,背景檢查將這些部署判定為失敗,整個網站則回傳 404。健康檢查原本是在託管平台的事件清單中計算當機次數,而這份清單每台機器只保留最近五筆事件,因此一旦發生當機迴圈,清單就會被填滿,計數便不再增加。現在檢查改為比對最新一次當機的時間;發生當機迴圈的發布會失敗,並還原到先前的版本。
發布需要等待什麼
當你在 Mythex 上發布帶有後端的應用程式時,新版本會被部署到我們託管平台的機器上,由健康檢查決定這次發布是已經上線還是失敗。如果失敗,復原會把先前的版本放回去。
困難之處在於,「已啟動」不等於「正在提供服務」。平台一旦機器啟動,就會將其標示為已啟動。一個在開機後一秒就當機的應用程式,會被立即重新啟動,過了一會兒又被標示為已啟動。因此,健康檢查隨著時間推移增加了幾個層次:
- **看兩次。**機器被標示為已啟動之後,稍等片刻再看一次。開機時的當機,通常會在第二次查看時被發現。
- **留意當機迴圈。**如果平台回報某台機器一再當機,而且它已經放棄重新啟動,就判定失敗。
- **計算自發布開始以來的當機次數。**兩次「已啟動」的查看可能跨越了一次重新啟動。因此,只要自本次發布開始以來發生過任何當機,就再看第三次;如果第三次看到的當機次數比前一次多,就表示應用程式無法穩定運行。
第三項檢查,正是這次出錯的地方。
為什麼計數一直沒有增加
當機資訊來自每台機器的事件清單:啟動、結束、重新啟動。平台每台機器只保留最近五筆事件。
想像一個應用程式每次開機後 2.5 秒就當機,其中一個就是這樣。每個循環都會新增一次當機和一次啟動。過了幾個循環之後,清單就滿了。從那時起,每新增一筆事件,就會擠掉一筆舊的。清單會向前滑動,但它的內容總是大致相同的組合:幾筆當機,幾筆啟動。
因此,「自本次發布開始以來的當機次數」每次查看都會得到相同的數字。第二次查看看到的是兩次當機;第三次查看也是兩次。雖然是不同的當機,但數量相同。檢查便判定當機已經停止,於是讓這次發布通過。
這種情況在 9 月 25 日發生了三次,涉及兩個應用程式。每一次都是:
- 發布被標示為上線,
- 沒有執行復原,因為沒有任何東西失敗,
- 我們的對帳程式(一個事後檢查部署的背景工作)在幾分鐘後將該部署判定為失敗,
- 整個網站回傳 404。
一個在時限內失敗的發布,是可以恢復的:復原會還原先前的版本。而一個在幾分鐘後才失敗、且已先被宣告為成功的發布,就已經跳過了它的復原。
我們如何發現它
我們在本機重現了這個問題:拿一個正常運作的 API,讓它進入休眠,然後用會在開機時當機的修改重新發布。健康檢查將它標示為上線。
這與正式環境的情況相符:在這三次發布中,每次應用程式都在開機後幾秒鐘死亡,但健康檢查所比較的當機次數,在兩次查看之間始終沒有增加。
修正方式:比較最新的當機,而非次數
健康檢查不再計算當機次數,而是查看自本次發布開始以來,最近一次當機的時間。
如果第二次查看顯示自發布開始以來發生過任何當機,檢查就會等待並進行第三次查看。如果第三次查看的最新當機時間晚於第二次的最新當機時間,表示機器在兩次查看之間又當機了。它無法穩定運行,發布便會失敗,並顯示訊息說明機器在發布後反覆當機,仍在不斷重新啟動。
無論事件清單有多短,這種做法都有效。一個只保留五筆事件的滑動視窗,可能會隱藏當機的總次數,但它無法隱藏剛剛又發生了一次新的當機:最新的事件永遠會被保留下來。
有了這項修改,本機的重現便能正確運作:發布失敗,並還原先前的映像檔。我們還新增了一個測試,模擬平台的行為(每次查看都會新增一次當機和一次啟動,並把清單修剪為五筆),然後驗證結果是失敗,而不是通過。
我們的收穫
- **了解你所計數的資料形狀。**對一個有上限的清單進行計數,一旦清單被填滿,計數就不再有意義。這個上限並非隱藏的;只是我們沒有針對它來設計檢查。
- **比起「發生了多少次」,更應該問「有沒有發生新的事情?」**最新的時間戳記在被截斷後依然可靠;總數則不然。
- **晚來的失敗比早來的失敗更糟。**同一個有問題的建置,如果讓發布失敗並復原,危害很小;但如果先被宣告為成功,就可能讓網站停擺。
- **用真實的條件重現。**這個錯誤需要一台機器已經當機到足以填滿事件清單的程度。在測試中只當機一次,是不會暴露這個問題的。
這項修正與同一天的另一項修正相輔相成,後者確保復原會把舊版本放回去,而不是刪除它:失敗的重新發布絕不會刪除運作中的後端。如果你自己的應用程式因為啟動時當機而無法發布,可以從發布疑難排解文件,以及我們的除錯 AI 建置應用程式指南開始。