Lyft 微服务研发效能提升实践 | 4. 基于自动验收测试的部署门禁

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
注册配置 MSE Nacos/ZooKeeper,118元/月
云原生网关 MSE Higress,422元/月
简介: Lyft 微服务研发效能提升实践 | 4. 基于自动验收测试的部署门禁

怎样才能提高研发效率?是依赖于各自独立的本地开发测试环境,还是依赖完整的端到端测试?Lyft 的这一系列文章介绍了其开发环境的历史和发展,帮助我们思考如何打造一套适合大规模微服务的高效研发环境。本系列共 4 篇文章,这是第 4 篇。原文:Scaling productivity on microservices at Lyft (Part 4): Gating Deploys with Automated Acceptance Tests[1]


image.png


本文是本系列文章的第四篇,也是最后一篇,主要讲述我们在 Lyft 面对越来越多的开发人员和服务时,如何扩展开发实践。



之前的文章中,我们描述了如何利用上下文传播从而允许多个工程师在共享的预发环境中进行端到端测试。现在我们看看另一部分——自动端到端测试,我们将介绍如何构建一个可伸缩解决方案,让工程师在部署到生产环境之前更有信心。


重新思考端到端测试


本系列的第1部分介绍了在 CI 中运行集成测试时遇到的许多挑战。服务和工程师数量的爆炸式增长导致运行测试的远程开发环境(Onebox)难以扩展,运行测试需要耗费大量时间。每个服务的集成测试也变得非常笨拙,差不多需要花费一个多小时才能完成,并且信噪比极低。工程师们不信任失败的测试,经常将其忽略,否则就会浪费更多的调试时间,而这会让事情更糟糕。


在覆盖 900 多个服务的数千个集成测试中,有一小组真正有价值的端到端场景,我们认为维护这些场景至关重要。比如用户可以登录,请求乘车,支付车费。这些场景中的问题在内部称为 SEV0(严重程度最高的事件)。这些问题将使乘客无法到达需要去的地方,或者使司机无法获得收入,因此必须不惜一切代价解决问题。


验收测试


当我们着眼于想要维护好的具有最高价值的端到端测试时,即使粗略看一下,也会发现它们看起来很像验收测试。这些测试在不需要了解内部实现细节的情况下,描述了用户如何与 Lyft 平台进行的交互。


考虑到这一点,我们决定从分布式模型(每个服务定义自己的集成测试集)转移到小型集中式验收测试集。这样做有两个好处。在技术上,将场景放在一起可以帮助我们消除重复,并在相关服务之间共享测试代码。在组织上,由一个单一的所有者负责协调这些测试的整体健康状况(这些测试仍然由不同的人编写和修改),并设计更好的隔离,以避免失控。


另一个关键决策点是何时运行这些测试。我们希望改变将端到端测试作为“内部”开发循环的一部分运行(在第2部分中描述了)的习惯,之前开发人员习惯于在开发过程中多次执行端到端测试,以取代单元测试或调用特定服务端点等策略。相反,我们想让 CI 快速运行,并鼓励人们更习惯于将端到端测试推迟到过程的后期。由于这些原因,我们选择在部署到预发环境后运行验收测试,作为生产环境部署的门禁策略之一。


构建框架


引擎


首先,需要一个引擎来提供简单的界面,以便像真正的用户一样使用 Lyft 的 API。幸运的是,我们已经在预发和生产环境中构建了类似的东西用于生成流量(参见第1部分中的预发环境)。这个库由几个关键概念组成:


  • Actions(动作): 与 Lyft API 交互,例如,RequestRide Action 会调用 Lyft API,提供所需的出发地和目的地,开始寻找司机。
  • Behaviors(行为): 大脑会根据一定的概率决定下一步该做什么,例如,假设刚刚请求乘车,下一步应该取消还是继续等待司机?
  • Clients(客户端): 代表与平台交互的设备,通常是运行 Lyft 应用程序的手机,用于存储状态和协调 actions/behaviors。


这三个简单理念的结合是我们过去 5 年在预发和生产环境进行自动化测试的策略的基础,为我们提供了很好的服务。然而,要在验收测试中重用这些策略仍然需要考虑一个重要的差别——行为的概率性质(probabilistic nature)。在构建带有负载测试的行为时,考虑到随机性非常有助于消除意外 bug,因此我们将其设计为类似于模糊测试(fuzzer)[2]的东西,而这并不适合于具有确定性的对特定流的验收测试。


image.png


因此我们更新了库,允许客户端按照一系列步骤操作,作为行为的替代方案,从而弥补了这一差距。步骤可以是以下任何一个:


  • Actions(动作): 如上所述,只是执行一个 API 调用。
  • Conditions(条件): 阻塞下一步的执行,直到某个表达式为真,并有可选的超时时间,例如,司机可能会等到到达起点时,通过 PickedUp 动作通知 Lyft 已经接到了乘客。
  • Assertions(断言): 确保客户端状态看起来与预期相符,例如,我们希望确保在请求乘车之前完成报价。


定义测试


actions、conditions 和 assertions 构建块就位之后,接下来需要决定在新的集中式主系统中定义测试的格式。以前的集成测试是在代码中进行的,但是我们决定切换到使用自定义配置语法定义验收测试。尽管有利有弊,但我们发现以这种方式定义测试能够提供一种强制功能,以保持测试的简单性和一致性,从而让更多人能够读/写测试,以及更好的维护测试。配置中暴露有限的接口,将大多数逻辑实现到前面提到的库中,从而可以更好的与其他验收测试或负载测试运行程序共享。


把所有这些放在一起,看看下面的测试场景示例:


# test_scenarios/standard_ride.yaml
description: A standard Lyft ride between 1 driver and 1 passenger
clients:
  - role: passenger
    steps:
      - type: Action
        action: Login
      - type: Action
        action: SetDestination
      - type: Assertion
        assertions:
          - ["price_quote", "between", 10, 20]
      - type: Action
        action: RequestRide
      - type: Condition
        conditions:
          - ["ride_status", "equals", "completed"]
      - type: Action
        action: TipDriver
  - role: driver
    steps: 
      - type: Action
        action: Login
      - type: Action
        action: EnterDriverMode
      - type: Condition
        conditions:
          - ["ride_request", "exists", true]
      - type: Action
        action: AcceptRideRequest
      - type: Action
        action: PickUpPassenger
      - type: Condition
        conditions:
          - ["location", "equals", "destination"]
      - type: Action
        action: DropOffPassenger


值得注意的是,如果从零开始的话,那么基于现有的测试框架(如 Cucumber/Gherkin[3])可能会更好。而在我们的例子中,扩展现有的流量生成工具比尝试使用这些技术要容易得多。


部署门禁


我们将端到端测试从 PR 合并之前调整到合并后部署之前,很大程度上提高了开发人员的生产力。虽然一个典型的 PR 可能平均会包含 4 个提交(每个提交都会运行测试套件),但通常一次只会部署一到两个 PR,因此开发人员由于测试的不稳定而被阻塞的频率几乎减少了 10 倍。此外我们预期,如果 PR 没有了端到端测试提供的虚假的安全感,开发人员就会把更多资源投入到单元测试中,并且会建立 feature flag 等更安全的发布策略(我们在第3部分中讨论过现在可以基于每个请求进行配置覆盖)。


为了实现这一点,我们扩展了内部部署系统的门禁(deploy gate)概念。部署阶段可以被一个或多个门禁所阻塞,门禁表示允许部署进入下一个阶段之前必须满足的条件。一个典型的门禁例子就是我们在每个预发部署中都会包含的 bake time,这个门禁确保部署的系统运行了特定长度的时间,以便有任何问题的时候,可以给持续的模拟流量一个触发告警的机会。


每个验收测试都会将门禁添加为被测服务的依赖项,一旦预发环境部署完成,相应的门禁就会启动测试运行,并报告成功或失败。为了不至于减缓开发人员的速度,验收测试的目标是在比默认 bake time(10 分钟)更短的时间内完成。


image.png


实践


测试什么?


可以说,转换到验收测试的最困难的部分之一是确定验收测试的构成规则,并在大量集成测试中应用这些规则。在筛选了数百个集成测试并与服务所有者讨论之后,我们确定了以下标准:


  • 验收测试应该只代表关键业务流,应该从用户角度描述与 Lyft 平台的端到端交互。
  • 我们显然不想测试所有场景,因此验收测试对业务必须是关键的。作为套件中最昂贵的测试,我们无法负担测试那些短时间中断不会对业务造成重大损害(即 SEV0)的边缘情况或业务流。


虽然站在测试金字塔[4]的角度来看这些标准似乎很明显,但仍然比预期更难应用。开发人员对于删除集成测试的后果感到不安,无论该测试是否被很好的理解或者是否曾经捕获过 bug。为了简化转换,我们与团队合作,根据上述标准分析每一个测试。大约 95%的测试要么是多余的,要么可以重写为带有 mock 的单元测试。剩下的几个测试在去除冗余后被组合成大约 40 个总的验收测试场景,这些场景将取代所有的集成测试。


结果


从我们用预发环境验收测试取代 CI 中的集成测试以来,已经过去了大约 6 个月。场景数量保持相对稳定,我们已经将覆盖范围扩大到运输和自行车 &踏板车产品,每周进行几千次测试。我们看到的主要好处是:


  • 大多数 PR 都能在 10 分钟内通过单元测试并准备好合并(之前包含端到端测试时需要 30 分钟)。
  • 从服务中删除了数千个集成测试,无需花费大量时间来维护和调试这些测试。
  • 验收测试迭代起来更快、更可靠,只需要不到一分钟的时间就可以准备一个以预发为目标的本地环境(使用前面提到的本地开发工作流,而 Onebox 的初始设置时间为 1 小时)。
  • 自从将端到端测试从 PR 中移除后,泄漏到生产阶段的 bug 数量并没有明显增加。
  • 验收测试每周在问题泄漏到生产环境之前将其捕获。
  • 我们还没有看到希望的那样,在单元测试方面有额外的投资。这需要进一步的调查来理解为什么,以及我们是否可以/应该做的更多来改变这一点。


将来的工作


到目前为止,我们对从这些变化中看到的生产力提升感到兴奋,但仍然展望未来的许多改进。


预发隔离


目前,在将更改部署到预发环境后立即运行测试,可能会在出现问题时干扰到其他预发环境用户。我们希望在本系列第三篇文章中讨论的预发覆盖工作的基础上,在将新版本的服务公开给其他用户之前对其运行验收测试。这将为部署增加额外的延迟,因此需要评估收益是否大于成本。


测试覆盖率


考虑到测试背后的主要目标是提高可靠性,我们希望做更多更直接的改进,而不仅仅是维护这些测试。我们知道,今天的测试存在差距,这些差距是由之前的集成测试构建的,并与服务所有者讨论了哪些业务流重要,需要被测试覆盖。为了缩小差距并提高可靠性,需要确保真正的 iOS 和 Android 客户端所做的所有最常用的 API 调用都能在这些测试中得到体现。一个想法是对流经我们系统的真实流量和模拟流量之间的增量进行更多的分析,也许可以通过对分布式跟踪工具的进一步投资实现。


测试场景健康度


最初,平台团队手工策划了每个验收测试,并密切关注其稳定性。随着我们继续扩展更多的业务线,希望每个操作(API 调用)具有更细粒度的可观察性,这样就可以自动将故障发送给合适的团队处理。这并不意味着分散所有权(我们认为为更广泛的测试和平台保留中央所有者非常重要),只是更快的提醒产品团队他们的服务出现了故障,并尽量减少手工工作。


总结


在本系列文章中,我们仔细分析了 Lyft 多年来是如何发展开发和测试方法,并追求不断提升开发人员的生产力。我们介绍了 Lyft 开发环境的历史(第1部分),转向本地优先的第一次研发环境转型(第2部分),在预发环境隔离测试服务与 envoy 覆盖(第3部分),用部署期间用一组验收测试取代 PR 触发的较重的集成测试(本文)。


image.png


尽管这种方法可能没办法适用于所有环境,但在缩短开发人员的反馈循环方面取得了很大的成功,并极大简化了支持测试环境的基础设施,从而帮助开发人员持续输出代码。


References:

[1] Scaling productivity on microservices at Lyft (Part 4): Gating Deploys with Automated Acceptance Tests: https://eng.lyft.com/scaling-productivity-on-microservices-at-lyft-part-4-gating-deploys-with-automated-acceptance-4417e0ebc274

[2] Fuzzing: https://en.wikipedia.org/wiki/Fuzzing

[3] Gherkin: https://cucumber.io/docs/gherkin/

[4] The Pratical Test Pyramid: https://martinfowler.com/articles/practical-test-pyramid.html

目录
相关文章
|
4天前
|
Java 测试技术 应用服务中间件
Spring Boot 如何测试打包部署
本文介绍了 Spring Boot 项目的开发、调试、打包及投产上线的全流程。主要内容包括: 1. **单元测试**:通过添加 `spring-boot-starter-test` 包,使用 `@RunWith(SpringRunner.class)` 和 `@SpringBootTest` 注解进行测试类开发。 2. **集成测试**:支持热部署,通过添加 `spring-boot-devtools` 实现代码修改后自动重启。 3. **投产上线**:提供两种部署方案,一是打包成 jar 包直接运行,二是打包成 war 包部署到 Tomcat 服务器。
25 10
|
1天前
|
搜索推荐 NoSQL Java
微服务架构设计与实践:用Spring Cloud实现抖音的推荐系统
本文基于Spring Cloud实现了一个简化的抖音推荐系统,涵盖用户行为管理、视频资源管理、个性化推荐和实时数据处理四大核心功能。通过Eureka进行服务注册与发现,使用Feign实现服务间调用,并借助Redis缓存用户画像,Kafka传递用户行为数据。文章详细介绍了项目搭建、服务创建及配置过程,包括用户服务、视频服务、推荐服务和数据处理服务的开发步骤。最后,通过业务测试验证了系统的功能,并引入Resilience4j实现服务降级,确保系统在部分服务故障时仍能正常运行。此示例旨在帮助读者理解微服务架构的设计思路与实践方法。
36 16
|
1月前
|
运维 监控 Java
后端开发中的微服务架构实践与挑战####
在数字化转型加速的今天,微服务架构凭借其高度的灵活性、可扩展性和可维护性,成为众多企业后端系统构建的首选方案。本文深入探讨了微服务架构的核心概念、实施步骤、关键技术考量以及面临的主要挑战,旨在为开发者提供一份实用的实践指南。通过案例分析,揭示微服务在实际项目中的应用效果,并针对常见问题提出解决策略,帮助读者更好地理解和应对微服务架构带来的复杂性与机遇。 ####
|
1月前
|
算法 NoSQL Java
微服务架构下的接口限流策略与实践#### 一、
本文旨在探讨微服务架构下,面对高并发请求时如何有效实施接口限流策略,以保障系统稳定性和服务质量。不同于传统的摘要概述,本文将从实际应用场景出发,深入剖析几种主流的限流算法(如令牌桶、漏桶及固定窗口计数器等),通过对比分析它们的优缺点,并结合具体案例,展示如何在Spring Cloud Gateway中集成自定义限流方案,实现动态限流规则调整,为读者提供一套可落地的实践指南。 #### 二、
65 3
|
1月前
|
消息中间件 运维 安全
后端开发中的微服务架构实践与挑战####
在数字化转型的浪潮中,微服务架构凭借其高度的灵活性和可扩展性,成为众多企业重构后端系统的首选方案。本文将深入探讨微服务的核心概念、设计原则、关键技术选型及在实际项目实施过程中面临的挑战与解决方案,旨在为开发者提供一套实用的微服务架构落地指南。我们将从理论框架出发,逐步深入至技术细节,最终通过案例分析,揭示如何在复杂业务场景下有效应用微服务,提升系统的整体性能与稳定性。 ####
46 1
|
1月前
|
监控 安全 持续交付
构建高效微服务架构:策略与实践####
在数字化转型的浪潮中,微服务架构凭借其高度解耦、灵活扩展和易于维护的特点,成为现代企业应用开发的首选。本文深入探讨了构建高效微服务架构的关键策略与实战经验,从服务拆分的艺术到通信机制的选择,再到容器化部署与持续集成/持续部署(CI/CD)的实践,旨在为开发者提供一套全面的微服务设计与实现指南。通过具体案例分析,揭示如何避免常见陷阱,优化系统性能,确保系统的高可用性与可扩展性,助力企业在复杂多变的市场环境中保持竞争力。 ####
47 2
|
1月前
|
消息中间件 运维 API
后端开发中的微服务架构实践####
本文深入探讨了微服务架构在后端开发中的应用,从其定义、优势到实际案例分析,全面解析了如何有效实施微服务以提升系统的可维护性、扩展性和灵活性。不同于传统摘要的概述性质,本摘要旨在激发读者对微服务架构深度探索的兴趣,通过提出问题而非直接给出答案的方式,引导读者深入
46 1
|
1月前
|
负载均衡 监控 API
后端开发中的微服务架构实践与挑战
本文深入探讨了微服务架构在后端开发中的应用,分析了其优势和面临的挑战,并通过案例分析提出了相应的解决策略。微服务架构以其高度的可扩展性和灵活性,成为现代软件开发的重要趋势。然而,它同时也带来了服务间通信、数据一致性等问题。通过实际案例的剖析,本文旨在为开发者提供有效的微服务实施指导,以优化系统性能和用户体验。
|
1月前
|
弹性计算 Kubernetes API
构建高效后端服务:微服务架构的深度剖析与实践####
本文深入探讨了微服务架构的核心理念、设计原则及实现策略,旨在为开发者提供一套系统化的方法论,助力其构建灵活、可扩展且易于维护的后端服务体系。通过案例分析与实战经验分享,揭示了微服务在提升开发效率、优化资源利用及增强系统稳定性方面的关键作用。文章首先概述了微服务架构的基本概念,随后详细阐述了其在后端开发中的应用优势与面临的挑战,最后结合具体实例,展示了如何从零开始规划并实施一个基于微服务的后端项目。 ####
|
1月前
|
Cloud Native API 持续交付
云原生架构下的微服务治理策略与实践####
本文旨在探讨云原生环境下微服务架构的治理策略,通过分析当前面临的挑战,提出一系列实用的解决方案。我们将深入讨论如何利用容器化、服务网格(Service Mesh)等先进技术手段,提升微服务系统的可管理性、可扩展性和容错能力。此外,还将分享一些来自一线项目的经验教训,帮助读者更好地理解和应用这些理论到实际工作中去。 ####
47 0