網誌

令伺服器凍結的缺失交換檔案

九月二十四日,我們的一台正式伺服器停止回應。負載平衡器把它標示為不健康,而我們的遠端命令工具也無法連接到它——但沒有任何東西崩潰,也沒有因為用量過多的記憶體而被終止。伺服器是在抖動:由於沒有交換空間,而容器又獲准使用超出機器所擁有的記憶體,Linux 把時間都花在逐出並重新讀取運行所需的檔案上,幾乎沒有任何進展。在每台伺服器上加入一個 2 GB 的交換檔案,便解決了問題。更有意思的教訓則與健康檢查有關。

「卡住」是什麼樣子

  • 負載平衡器對該伺服器的健康檢查逾時。
  • 透過 AWS Systems Manager 發送的命令停留在「Pending」,從未運行。
  • 日誌中沒有記憶體不足的終止紀錄,沒有任何程序死亡。

一星期前,我們已把正式環境遷移到較小的 ARM 伺服器,每台有 2 GB 記憶體。我們的預發佈伺服器——比它更小——在部署期間解壓縮容器映像檔時,也以同樣的方式凍結了。

為什麼沒有記憶體錯誤?

Linux 只有在真的耗盡可回收的記憶體時,才會終止程序。在那之前,它會從頁面快取中回收記憶體——即它保留在 RAM 中的檔案副本,包括每個運行中程序的程式碼。

在我們的伺服器上,各容器的記憶體上限加起來約為 3.3 GB,而機器實際可用的約為 1.8 GB。平時它們不會同時用到上限。但當它們同時使用時,核心會不斷從快取中逐出程序程式碼以騰出空間,然後程式碼一運行,又要從磁碟重新讀回。這種循環——抖動——可以令機器幾乎完全無回應,而技術上從未耗盡記憶體。所以什麼都沒有被終止,也沒有任何東西能夠恢復。

沒有交換空間,核心就沒有地方放置已分配但很少使用的記憶體,因此只能從快取中把它拿走。

修復方法

伺服器的啟動腳本現在會在每次伺服器開機或修復時,建立一個 2 GB 交換檔案,並把 swappiness 設為較低的 10。低 swappiness 意味著核心仍然傾向於把工作記憶體保留在 RAM 中,但很少被觸及的頁面可以移到磁碟上,而不是迫使程序程式碼被逐出快取。我們已在正式環境的兩台伺服器和預發佈環境上即時套用,而且部署流程在每次正式部署時都會重新發佈啟動腳本,因此被替換的伺服器也會取得它。

更令我們擔心的:不健康的伺服器仍然留著

我們的伺服器群組使用的是 EC2 健康檢查——「機器是否在運行?」——而不是負載平衡器健康檢查——「它是否在回應請求?」。因此,在群組看來,一台負載平衡器已經放棄的伺服器仍然是健康的。它永遠不會被替換。正式環境會悄悄地只靠一台伺服器運行,直到有人注意到並重新啟動另一台為止。

把群組改為使用負載平衡器健康檢查聽起來是顯而易見的修復,但對我們來說並不簡單:在部署期間,伺服器會刻意以錯誤回應其健康端點,好讓負載平衡器把它們排空(見我們如何把部署降至零 502)。如果群組依據這個信號來替換伺服器,它會在每次部署的中途替換它們。

因此,目前我們在部署檢查清單中加入了一個步驟:每次部署後,檢查負載平衡器上每個目標的健康狀態,而不只是確認部署工作是否通過。如果發給某台伺服器的命令停留在「Pending」,那就是它在抖動——重新啟動它;它本來就已經脫離輪替。

我們的心得

  • 「沒有崩潰」不等於「健康」。 抖動不會產生錯誤,只會造成慢到機器停止回應的程度。
  • 小型伺服器需要交換空間。 即使只有一點點,搭配低 swappiness,也能讓核心有地方存放冷門的記憶體。
  • 記憶體上限加起來超過機器本身,就是一場賭博。 有時你會輸。知道自己何時下了這個注。
  • 知道你的自動擴展信任哪一種健康檢查。 一個只問「虛擬機是否開著?」的健康檢查,不會替換一台已停止運作的伺服器。

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 简体中文 繁體中文(台灣)