失敗的重新發佈絕不會刪除運作中的後端
9 月 25 日,一次失敗的重新發佈刪除了一個應用程式的後端。該後端已經運作了大約兩個半小時。撤銷失敗發佈的程式碼,移除了該次發佈所觸及的每一個伺服器應用程式,包括那些在發佈開始前已經在提供服務的應用程式。現在,只有由發佈新建的應用程式才會被刪除;原本已存在的應用程式,會被還原到它在發佈前運行的確切版本。當日另外兩項後續修正,確保了當託管平台自己的部署工具失敗時,撤銷程序同樣會執行,並且健康的應用程式不會因為正在休眠而被判定為失敗。
後端如何發佈
當你的 Mythex 應用程式有後端(即 API 或伺服器,而不只是靜態檔案)時,發佈會先用 Dockerfile(一份將應用程式打包成映像檔的指令清單)建構它,然後把該映像檔部署到我們託管平台的機器上。之後,健康檢查會等待新版本真正啟動並穩定運行,發佈才會被標示為上線。
如果過程中任何一步失敗,就會執行回滾來撤銷變更。正確處理回滾,比發佈過程中幾乎所有其他環節都更重要,因為它正正是在有事情已經出錯的時候才會運行。
問題一:回滾刪除了本應還原的東西
回滾程序會記錄一份清單,列出該次發佈部署到的伺服器應用程式,失敗時就把清單上的每一個都摧毀。對於首次發佈來說,這樣做是正確的:應用程式是剛剛才建立的,沒有任何東西依賴它,刪除之後一切就回到原來的樣子。
但對於重新發佈來說,這就完全錯誤。應用程式本來已經存在,並且正在為真實的訪客提供服務。9 月 25 日,一個已經上線約兩個半小時的應用程式 API,就被一次失敗的重新發佈刪除了。
同一段程式碼裡還藏著另一個失敗模式。如果刪除沒有成功,壞掉的新映像檔就會留在機器上,而我們卻告訴擁有者他們目前的網站仍然在線。健康檢查失敗的時候,新映像檔其實已經部署到每一部機器上了。什麼都不做,並不等於撤銷。
修正方法:只刪除自己建立的,其餘的還原
現在,每次發佈都會為它觸及的每個伺服器應用程式記錄:
- 這次發佈是否建立了它,以及
- 如果不是,它在發佈前運行的是哪個映像檔。
失敗時,每個應用程式會按以下三種方式之一處理:
- 由這次發佈建立: 照舊將其刪除。
- 原本已存在,且已知先前的映像檔: 以相同設定重新部署先前的映像檔。不需要重新建構,只是把映像檔換回去。
- 原本已存在,但先前的映像檔未知: 不予理會。已存在的應用程式絕不會被刪除,即使我們無法還原它。
這些規則是一個小型的純函式,並附有自己的測試,所以這項決定不會在沒有測試失敗的情況下倒退回「全部刪除」。
問題二:部署工具可能在我們記下撤銷方法之前就已失敗
那次修正之後不久,我們發現它仍有一個缺口。託管平台的部署工具會執行它自己的冒煙測試:如果新機器崩潰,它就會以錯誤結束。但它是在新映像檔已經部署到機器上之後才這樣做的。
我們的程式碼只有在工具成功返回後,才會把應用程式登記到回滾清單上。當工具本身令部署失敗時,應用程式還沒有登記在回滾清單上,因此什麼都沒有被還原。在我們的預發佈(staging)環境中,一次壞掉的重新發佈就讓壞掉的映像檔繼續提供服務。
修正方法:在開始之前先登記撤銷方法
現在,應用程式會在準備就緒、部署任何東西之前,就登記到回滾清單上,同時記下它是否是新建的以及它原本運行的映像檔。無論之後哪一步失敗,回滾都知道要還原什麼。我們在本地驗證過:一次壞掉的重新發佈在部署工具自己的冒煙測試中失敗,而先前的版本仍然繼續回應請求。
問題三:休眠中的應用程式被判定為「從未啟動」
Mythex 上的已發佈應用程式在閒置時會休眠,以節省你的額度。重新發佈後,休眠中的機器會保持停止狀態,所以健康檢查必須把它喚醒。以往它只會要求平台啟動機器一次,並且忽略回應。
9 月 25 日,那一次啟動請求沒有得到回應。一個健康的 API 在整個五分鐘的健康檢查期間都保持停止狀態,最後因為「1 部機器從未啟動(最後狀態:stopped)」而被判定為失敗。
平台有時確實會拒絕啟動請求。我們在重現問題時,平台在我們沒有刻意觸發的情況下,返回了「lease currently held」衝突;而且平台在機器正在更新時,也會拒絕啟動。
修正方法:持續請求,並敲門
健康檢查現在會:
- 在等待期間,每 20 秒再次要求啟動已停止的機器,
- 記錄平台拒絕時給出的回應,以及
- 向應用程式發送一個普通請求,就像訪客那樣——平台的代理會在請求到達時喚醒休眠的機器。
我們用一個第一次啟動會被拒絕的臨時應用程式,在真實的平台上重現了這個問題。修正前,發佈在 91 秒後失敗;修正後,20 秒內就完成發佈。真正在開機時崩潰的應用程式仍然會失敗,這正是應有的結果。
我們從中學到的
- 回滾必須分清楚新建與已存在的分別。 對於你建立的東西,「撤銷」就是刪除;對於原本就存在的東西,「撤銷」就是還原。把兩者等同處理,就是保護機制刪除了運作中應用程式的原因。
- 在行動之前先寫下如何撤銷。 如果撤銷方法只在某一步成功之後才記錄,那麼中途失敗的步驟就沒有東西可以用來撤銷。
- 絕不要忽略你所依賴的請求的回應。 一個被忽略的「不」,就讓一個健康的應用程式變成了發佈失敗。
同一天的另一項相關修正:剛發佈後就崩潰的應用程式如何捕捉。如果發佈在你這一端失敗,請參閱文件中的發佈疑難排解。