平台团队为了让测试和发布代码“正常工作”而采取的快速修复措施,以及这些措施在扩展时如何反噬。
译自 Testing Shortcuts to Avoid in Microservice Environments,作者 Nočnica Mellifera。
快速修复的风险是什么?我似乎可以发表一篇以“鉴于最近发生的重大技术故障”为开头的常青文章,但我的思绪回到了 2023 年西南航空公司的停机事件。
在这种情况下,多年来,重大 IT 改造的成本阻止了西南航空公司 升级其系统,直到其整个网络(仍然基于自动电话路由系统)崩溃。飞机和机组人员不得不空载飞回家,这是最糟糕的效率低下,只是为了让其系统有一个重新开始的地方。一个字面上的“你试过关机重启吗”?
西南航空公司的例子是一个真正古老的架构,但优先考虑简单解决方案而不是质量的问题也影响着现代微服务架构。在微服务架构的世界里,我们看到工程师重视 测试和 QA 的速度,而不是从这些测试中获得的信息质量。
总的来说,这看起来像是为了以最快的速度测试新的代码更改而进行优化,而没有关注从这些测试中获得的信息的可靠性。
当我们看到一个系统显然不适合一个组织时,解释几乎总是相同的:这在不同的规模下是一个很好的解决方案。当一个 Web 服务器可以很好地安装在 PC 上时,单体架构非常有意义。当我们扩展到单个实例和单个机器之外时,测试问题解决方案 和一致性通常可以通过“快速修复”来解决,这些修复对于给定的规模来说效果很好。
以下是平台团队为了让测试和发布代码“正常工作”而采取的捷径,以及这些捷径如何反过来咬你一口。
我想回顾一下我们在现代架构团队中看到的一些故障模式。
与多个平台工程师交谈后,最近的一个主题是重新强调单元测试。单元测试是一个有吸引力的选择,因为它通常在开发人员的笔记本电脑上运行,运行速度快且效率高。
在理想情况下,每个开发人员正在处理的服务将很好地与其他服务隔离,并且具有明确的服务性能规范,单元测试应该涵盖所有测试用例。但遗憾的是,我们在现实世界中开发,服务之间的相互依赖很常见。在请求在相关服务之间来回传递的情况下,单元测试难以以现实的方式进行测试。不断更新的服务集意味着即使是记录需求的努力也无法保持最新。
结果是,通过单元 测试的代码不能保证在预发布环境(或您在生产环境之前部署的任何其他环境)中正常工作。当开发人员在不确定代码是否能正常工作的情况下推送他们的拉取请求时,他们的测试速度更快,但获得真实反馈的时间更慢。结果,开发人员的反馈循环更慢。开发人员需要更长时间才能发现他们的代码是否通过集成测试,这意味着功能的实现需要更长时间。更慢的 开发速度是一项成本,任何团队都负担不起。
等待在预发布环境中发现问题的问题可能表明解决方案是克隆预发布环境。当多个团队试图将他们的更改推送到单个预发布环境时,如果我们克隆该环境,我们肯定会提高速度。这种解决方案的成本分为两部分:基础设施成本和可靠性损失。
为开发人员维护数十个或数百个环境运行会带来真正的基础设施成本。我曾经听说过一个企业团队在这些复制集群上花费了如此多的钱,以至于他们计算出一个月的基础设施成本中几乎有四分之一花在了开发环境上,仅次于生产环境!
设置多个低级环境(即比预发布环境更小、更容易管理的环境)有一些缺点,最大的缺点是测试质量。当测试使用模拟和虚拟数据运行时,通过测试的可靠性可能会变得非常低。我们冒着维护(并支付)实际上不可用于测试的环境的风险。
另一个问题是同步;当许多环境运行服务的克隆时,很难保持所有这些服务更新。
在最近与金融科技公司 Brex 的案例研究中,平台开发人员谈论了一个命名空间复制系统,该系统具有让每个开发人员都有空间进行集成测试的优势,但许多环境很难保持更新。
最终,当平台团队加班加点地保持整个集群稳定和可用时,开发人员注意到他们克隆的命名空间中太多服务没有更新。结果要么是开发人员完全跳过此阶段,要么依赖于稍后的推送到暂存环境作为“真正的测试阶段”。
我们不能严格控制创建这些复制环境的策略吗?这是一个难以平衡的问题。如果你锁定创建新环境以要求高度合格的使用,你就会阻止某些团队在某些情况下进行测试,并损害测试可靠性。如果你允许任何人在任何地方启动一个新环境,那么环境被启动一次就再也不使用的风险就会增加。
好的,这似乎是一个好主意:我们投入时间创建一个接近完美的暂存环境副本,甚至生产环境副本,并将其作为完美的、神圣的副本运行,专门用于测试发布。如果任何人对任何组件服务进行更改,他们都需要与我们的团队联系,以便我们可以将更改跟踪回我们的防弹环境。
这确实解决了测试质量问题。当这些测试运行时,我们真正确信它们是准确的。但我们现在发现,我们在追求质量的过程中走得太远,以至于放弃了速度。我们正在等待每个合并和调整完成,然后才运行一套庞大的测试。更糟糕的是,我们又回到了开发人员等待数小时或数天才能知道他们的代码是否有效的状态。
强调快速运行的测试以及希望让 开发人员拥有自己的空间来试验 代码更改都是值得称赞的目标,它们不需要降低开发人员的速度或在基础设施成本上破产。解决方案在于一个随着大型开发团队而不断发展的模型:对单个服务或服务子集进行沙盒化。
沙盒是在暂存环境中运行实验性服务的独立空间。沙盒可以依赖于环境中所有其他服务的基线版本。在 Uber,这个系统被称为 SLATE,它对使用它的原因以及为什么其他解决方案更昂贵且更慢的探索 值得一读。
让我们回顾一下沙盒的要求。
如果我们控制服务之间的通信方式,我们可以对服务之间的请求进行智能路由。标记为“测试”的请求将被传递到我们的沙盒,它们可以像往常一样向其他服务发出请求。当另一个团队在暂存环境中运行测试时,他们不会用特殊标头标记他们的请求,因此他们可以依赖于环境的基线版本。
那么不太简单的、单请求测试呢?消息队列或涉及持久数据存储的测试呢?这些需要他们自己的工程,但所有这些都可以与沙盒一起使用。事实上,由于这些组件可以被多个测试同时使用和共享,因此结果是更逼真的测试体验,测试在看起来更像生产环境的空间中运行。
请记住,即使在不错的轻量级沙盒上,我们仍然希望有一个生命周期配置,以便在一定时间后关闭它们。由于运行这些分支服务需要计算资源,尤其是多服务沙盒可能只对单个分支有意义,因此我们需要确保我们的沙盒在几小时或几天后关闭。
为了速度而 在微服务测试中 偷工减料,往往会导致日后代价高昂的后果。虽然复制环境似乎是确保一致性的快速解决方案,但维护这些设置的财务负担可能会迅速升级。
在压力下,人们急于进行测试、跳过全面检查或依赖不完整的暂存环境设置的诱惑是可以理解的。然而,这种方法会导致未发现的问题、不稳定的发布,最终会导致更多的时间和资源花在修复生产环境中的问题上。优先考虑速度而不是彻底测试的隐藏成本体现在项目延迟、团队沮丧和客户信任丧失上。
在 Signadot,我们认识到有效的测试并不一定需要高昂的成本或减缓开发周期。通过使用动态配置和请求隔离等策略,我们提供了一种简化测试流程并控制基础设施成本的方法。我们的共享测试环境解决方案允许团队执行安全的金丝雀式测试,而无需复制环境,从而节省大量成本并提高暂存环境的可靠性。