借助请求级别的隔离,不同团队可在共享集群上开展实验。
译自Environment Replication Doesn’t Work for Microservices,作者 Nočnica Mellifera(她/她的)在转向开发人员关系之前是一个开发人员 7 年。她专门从事容器化工作负载、无服务器和公共云工程。Nočnica 一直倡导开放标准,并就此进行了演讲和研讨会......
什么是验证代码是否能够工作的最佳方式?当我与能力强大的平台工程师和运维架构师交谈时,有一个迷人的趋势是,没有人似乎能就测试应该在哪里或如何进行达成一致。
您是在什么时候第一次意识到您的代码与其他服务不正确地协作的?分阶段失败应该经常发生,因为开发人员在测试重大更改,还是分阶段应该总是获得工作代码提交?应该在合同测试上花巨大努力,使用复杂的模拟来模拟延迟峰值等情况,还是应该在生产环境上设置金丝雀测试并观察会发生什么?这些都是在企业平台工程团队上没有一致答案的问题。
让我们来看看试图在本地复制复杂的微服务环境的问题。虽然更小的团队绝对可以为每位工程师提供一个运行在他们的笔记本电脑上的生产集群的副本,但这种方法的可扩展性非常糟糕,并且在本地复制上花费的时间更好地用于创建可以由整个团队共享并从开发的第一天开始安全用于测试的预发布环境。
二十个微服务的困境
关于最佳开发平台的所有问题都始于规模。对于我们的案例,想象一个拥有 50 多名工程师和 25 多种微服务的团队。关于这个规模的团队,有一些事情使其成为一个拐点,从熟悉单体应用的流程转变为更分布式、共享、高速发展的团队。
关于50名工程师和25个微服务的团队,有什么是真实的?让我们列出一些观察结果:
- 团队太大而无法保持同步和共享知识:C团队可能在没有任何A团队知情的情况下更新数据库接口。
- 所有微服务所做的计算工作足以考验一台普通笔记本电脑。
- 使用了不止一个数据库。
- 代码分布在多个仓库中。
当团队和产品规模减半时,开发人员可以获取必要的仓库,从其他团队获得帮助以使事情正常工作,并在其副本过时时,他们可能已经从其他团队的更新中得知。然而,在这个规模下,这些业务之间的人为交流不再扩展,A团队中的某人会发现他们的本地复制环境在他们没有意识到的情况下不同步。
沉没成本:过度承诺本地副本
在这种情况下,许多团队实际上会做出决定来购买本地复制,也就是说,他们会开始向该项目投入真正的DevOps资源。突然,我们有责任维护用于本地复制的Dockerfile,开发人员必须更新它以了解其更改是否与其他服务一起使用。
坚持这项工作的原因似乎令人信服:通过一致的本地副本,开发人员可以在更新进入预发布环境之前发现错误,并且不会阻塞其他团队的工作,这些团队需要预发布环境大部分时间可用。(我在这里使用了“预发布”,但只需将其视为正式上线之前的部署,无论其称为预发布、QA、测试还是其他名称。)但是,在这个阶段对本地复制投入大量时间有三个主要问题:
- 如果您目前没有运行整个集群的本地副本,那么架构本身可能需要重新设计,增加标准的服务启动和运行方式、单一仓库架构以及明确的服务所有权。
- 许多组件无法在本地很好地复制,包括第三方服务和包含复杂数据结构的数据存储。结果将是这些组件的模拟或其他高度简化的副本,这引发了对测试准确性和持续维护成本的担忧。
- 这种方法长期不可扩展。一旦团队规模和架构大小都加倍,开发人员的笔记本电脑就无法运行整个系统。一旦笔记本电脑无法运行集群,那么为每个开发者运行相同集群的副本的云基础设施成本将无法承受。
这并不意味着本地复制对所有团队都有效,这意味着一旦您意识到您的规模需要全职维护本地副本镜像,您应该把那个时间花在别的东西上。
为什么您的所有微服务都捆绑在一起?
整个讨论又提出了另一个问题:如果您需要测试每次代码更改,那么您真的拥有微服务吗?即使您的产品的25个组件作为独立服务运行,但如果它们耦合得那么紧,以至于无法隔离测试,那么您就只有微服务的名称吗?(顺便说一句,我真切地希望紧耦合的微服务体系结构的首字母缩写 MINO 能流行起来。)
关于测试微服务之间集成的每一次讨论都会回到这样一个问题:微服务应该被很好地隔离,这样您就可以进行合同测试。问题再次归结为规模。
在小规模下,每个服务都应该可靠地完全满足与其他服务的合同。即使在大规模下,您集群内的事务也不应产生意外的副作用。然而,在更大的规模下,合同测试的要求会变得越来越复杂。合同测试不测试延迟、多变量请求和数据存储中的意外数据,这些都是我们希望通过测试覆盖的情况,然后才准备进入生产环境。
是否有可能覆盖这些情况?当然可以,但问题是我们是否应该花大量时间来模拟集群中的所有其他服务,或者那时间是否最好花在为预发布服务器建立单一的、高精度的生产环境克隆上。
对我们的产品进行重新架构以更清晰地分离微服务和实现这些服务之间的广泛合同测试的总体投资回报率可能不是我们希望的大规模技术跨度。
更好的解决方案:作为事实来源的共享集群
如果我们不想投入时间将我们的集群适应工作站或一套深入的合同测试,那么解决方案是一个非常接近生产环境的共享集群。这个预发布环境可以提供关于更改是否能够与其他服务很好地协作的真实答案,并且当其他服务发生变化时,它是一个需要更新的单一集群。最后,与本地环境不同,它应该 24/7 可用,开发人员不需要更新他们的复制环境。
同样,我们必须讨论规模问题。在 50 名开发人员和 25 个微服务的情况下,多个团队可能会同时想要在预发布环境上进行测试。这里的规模再次成为拐点:稍小一些,团队可以在 Slack 上发布他们将在接下来的几个小时内使用预发布环境。但是随着我们的发展,保持同步变得更加困难,我们最终会有开发人员等待数小时或数天的预发布环境可用。
使用 Kubernetes namespace 作为团队的开发环境为复制预发布或生产环境的条件提供了一个强大的解决方案。通过创建一个预发布设置的克隆命名空间,开发人员可以在一个高度模拟生产环境的环境中工作。这种方法可以确保所有服务、配置和依赖项都是对齐的,从而更容易在开发周期的早期捕获问题。
克隆的命名空间还有助于团队成员之间的更好协作。由于命名空间是隔离的,多个开发人员可以在不同的功能或错误修复上工作,而不会相互干扰。这种隔离对于需要管理复杂 CI/CD 管道的 DevOps 工程师特别有益,因为它允许他们在与生产环境几乎相同的环境中测试部署脚本和编排过程。该命名空间可以充当最后一个检查点,在该检查点上,所有代码和功能都进行了集成和测试,然后再移至预发布或生产环境。Prezi 等团队正在使用这种方法,每个开发团队都有一个命名空间来部署和测试更改。
命名空间复制的问题
谨慎管理这些克隆的命名空间以避免配置漂移至关重要。需要自动化工具和脚本来确保命名空间保持对预发布或生产环境的真实复制。任何对预发布或生产设置的更改都需要尽快在开发命名空间中镜像。
如果没有维护这种紧密的集成和同步,那么结果将是已经过时并且开发人员不再信任的命名空间环境。
随着您的团队规模的扩大,您将需要更多包含生产环境相关部分副本的命名空间。随着您需要为每个命名空间复制数据库、云资源和第三方集成,这可能开始觉得令人生畏。
最后一个考虑因素是运行所有这些复制命名空间的成本,无论是基础设施成本还是时间成本。或者您一直在运行许多命名空间,这是昂贵的,或者每次团队想运行集成测试时都会启动命名空间服务,从而增加测试和实验的阻力。平台工程团队的开销使我们回到了这样一个普遍观点,即环境复制在大规模的微服务团队中不可扩展。
Uber 和 Lyft 的工程团队由于同步和测试保真度问题,发现命名空间方法不足,并转向请求隔离模型,在该模型中,多个团队可以在单个共享集群上安全实验。
为什么环境复制不可扩展
本地复制的诱人之处,尽管最初很有前途,但随着团队和体系结构的扩展,其局限性就显露出来了。这不仅仅是关于尽早发现错误的问题;而是关于这些测试的准确性和测试环境的可持续性。合同测试虽然有价值,但随着服务之间交互的复杂性增加,它也显示出局限性。
在考虑这些微服务规模化集成测试和开发环境的障碍时,我建议您重新考虑我们对“微服务”的理解。如果服务之间相互依赖,以致无法隔离测试,那么这个术语就更像是一个标签,而不是对体系结构的描述。
共享的预发布环境成为一个务实的中间立场。使用 Kubernetes 命名空间针对特定团队的环境可以在隔离性和准确性之间实现平衡。然而,即使这种方法也不是没有其缺点,例如配置漂移的风险和所涉及的运营开销。
随着我们的扩展,我们的测试方法也必须与我们一起扩展,始终以那种难以捉摸的准确性、效率和可维护性的组合为目标。近年来,一种新的方法已经突显出来,它使用共享环境而不需要多个副本,并通过请求隔解来隔离实验。
请求隔离:开发人员实验和测试的新模型
在大型企业团队中,并且在中型开发团队中也越来越多地使用一种新的模型,它承诺在开发周期的早期进行更快、更好的测试。
请求级别隔离是一种利用上下文传播和请求路由的微服务环境测试方法。当开发人员想要测试微服务的新版本时,依赖项由运行最新稳定版本(称为基线)的共享服务池满足。这种方法可以确保一个开发人员做出的更改与其他开发人员隔离,减轻了跨依赖性和不可预测的预发布环境的问题。
使用共享的预发布环境,我们可以像上述命名空间策略中提到的那样产生一个高精度的复制空间。但是,与其将组件复制到命名空间中,我们可以使用请求隔离同时部署多个开发人员版本的服务。