博客

让我们服务器卡死的缺失交换文件

9 月 24 日,我们的一台生产服务器停止了响应。负载均衡器将其标记为不健康,我们的远程命令工具也无法连接到它——但没有任何东西崩溃,也没有任何进程因内存占用过高而被杀掉。服务器陷入了颠簸:没有交换空间,而且容器被允许使用的内存超过了机器实际拥有的内存,Linux 把时间都花在驱逐并重新读取它运行所需的文件上,几乎没有取得任何进展。在每台服务器上添加一个 2 GB 的交换文件解决了问题。更有意思的教训则与健康检查有关。

「卡死」是什么样子

  • 负载均衡器的健康检查在那台服务器上超时。
  • 通过 AWS Systems Manager 发送的命令一直处于「Pending」状态,从未执行。
  • 日志中没有内存溢出被杀的记录。没有任何进程退出。

一周前,我们将生产环境迁移到了更小的 ARM 服务器,每台有 2 GB 内存。我们的预发布服务器——比它还要小——在部署期间解压容器镜像时,也以同样的方式卡死了。

为什么没有内存错误?

Linux 只有在真正耗尽可回收的内存时才会杀死进程。在那之前,它会从页缓存中回收内存——也就是它保存在内存中的文件副本,包括每个正在运行的程序的代码。

在我们的服务器上,各容器的内存限制加起来约为 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 繁體中文(香港) 繁體中文(台灣)