博客

发布失败时,正常运行的后端永远不会被删除

9 月 25 日,一次失败的重新发布删除了某个应用的后端。这个后端之前已经稳定运行了大约两个半小时。撤销失败发布的代码,把这次发布涉及的每一个服务器应用都删掉了,包括发布开始前就已经在提供服务的应用。现在,只有发布本身创建的应用才会被删除;原本就存在的应用,会被恢复到它运行前的确切版本。同一天的另外两个修复,确保了当托管平台自己的部署工具失败时,撤销流程也会执行;并且,一个只是处于休眠状态的健康应用,不会因此被判定为失败。

后端是如何发布的

当你的 Mythex 应用包含后端(即 API 或服务器,而不仅仅是静态文件)时,发布过程会用 Dockerfile(把应用打包成镜像的配方)构建后端,并把这个镜像部署到我们托管平台上的机器中。接着,健康检查会等待新版本真正启动并保持稳定运行,之后才会把这次发布标记为已上线。

如果过程中任何一步失败,就会运行回滚来撤销这些改动。回滚必须做对,这比发布中的几乎任何其他环节都重要,因为它恰恰是在出问题的时候运行的。

问题一:回滚删掉了本该恢复的东西

回滚会记录下一次发布部署到了哪些服务器应用,一旦失败,就把它们全部销毁。对于首次发布来说,这样做是对的:应用是刚刚创建的,没有任何东西依赖它,删掉它就等于回到了发布之前的状态。

但对于重新发布来说,这恰恰是错的。应用早已存在,并且正在为真实的访问者提供服务。9 月 25 日,一个已经上线约两个半小时的应用 API,就被一次失败的重新发布删掉了。

同样的代码里还藏着第二种故障模式。如果删除没有成功,那么有问题的新镜像就会留在机器上,而我们却告诉应用所有者,他们当前的网站仍然在线。健康检查失败时,新镜像早已部署到了每一台机器上。不做任何事,并不等于撤销。

修复方案:只销毁自己创建的,其余全部恢复

现在,每次发布都会为它涉及的每个服务器应用记录两项信息:

  • 这次发布是否创建了它,以及
  • 如果不是,它在发布前运行的是哪个镜像。

失败时,每个应用会按以下三种方式之一处理:

  1. 由本次发布创建: 像以前一样销毁。
  2. 已存在,且知道之前的镜像: 用相同的配置重新部署之前的镜像。无需构建,只需把镜像切换回去。
  3. 已存在,但之前的镜像未知: 保持不动。已存在的应用绝不会被删除,即使我们无法恢复它。

这些规则是一个很小的纯函数,并配有自己的测试,因此这个决策不会在不知不觉中退回到“全部销毁”,只要有测试失败就能及时发现。

问题二:部署工具可能在我们记下撤销方法之前就失败了

修复之后不久,我们发现其中还有一个缺口。托管平台的部署工具会运行自己的冒烟检查:如果新机器崩溃,它会以错误退出。但这一步发生在新镜像已经部署到机器上之后。

我们的代码只有在工具成功返回后,才会把应用登记到回滚列表中。当工具自己导致部署失败时,应用还没有进入回滚列表,因此什么也没有恢复。在我们的预发布环境中,一次失败的重新发布就让有问题的镜像继续提供服务。

修复方案:在开始之前先登记撤销方法

现在,应用会在准备完成、尚未部署任何内容之前,就登记到回滚列表中,并同时记录它是否为新建以及它运行的是什么镜像。无论之后哪一步失败,回滚都知道该恢复什么。我们在本地做了验证:一次失败的重新发布在部署工具自己的冒烟检查中被拦下,之前的版本依然正常响应。

问题三:处于休眠的应用被误判为“从未启动”

Mythex 上发布的应用在闲置时会休眠,以节省你的积分。重新发布会让休眠中的机器保持停止状态,因此健康检查必须先把它唤醒。过去,它只会请求平台启动机器一次,而且忽略了平台的回应。

9 月 25 日,那一次启动请求没有起作用。一个健康的 API 就一直处于停止状态,直到五分钟的健康检查时间耗尽,最终被判定为失败,报错信息是“1 台机器从未启动(最后状态:stopped)”。

平台有时确实会拒绝启动请求。我们在复现问题时,它甚至在没有任何诱因的情况下返回了“lease currently held”冲突;而当机器正在更新时,它也会拒绝启动。

修复方案:持续请求,并主动敲门

现在,健康检查会:

  • 在等待期间,每 20 秒再次请求启动一次已停止的机器,
  • 记录平台拒绝启动时的回应内容,
  • 向应用发送一个普通请求,就像访问者那样——平台的代理会在请求到达时唤醒休眠的机器。

我们使用一个首次启动会被拒绝的临时应用,在真实平台上进行了复现。修复之前,发布在 91 秒后失败;修复之后,20 秒内就发布成功。而确实会在启动时崩溃的应用,依然会按预期失败。

我们从中学到的

  • 回滚必须能区分新建与已存在。 对于你创建的东西,“撤销”就是删除;对于本来就存在的东西,“撤销”就是恢复。把两者等同对待,正是安全机制删掉正常运行应用的原因。
  • 在行动之前先写下如何撤销。 如果只在某一步成功之后才记录撤销方法,那么一个中途失败的步骤,就没有可用于撤销的依据。
  • 绝不忽略你所依赖的请求的回应。 一次被忽略的“不行”,就让一个健康的应用变成了发布失败。

同一天的另一项相关修复是:捕获发布后立即崩溃的应用。如果发布在你这边失败,请参阅文档中的发布故障排查。

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