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

本文涉及的产品
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
云原生网关 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

目录
相关文章
|
5天前
|
Kubernetes 负载均衡 Docker
构建高效后端服务:微服务架构的探索与实践
【10月更文挑战第20天】 在数字化时代,后端服务的构建对于任何在线业务的成功至关重要。本文将深入探讨微服务架构的概念、优势以及如何在实际项目中有效实施。我们将从微服务的基本理念出发,逐步解析其在提高系统可维护性、扩展性和敏捷性方面的作用。通过实际案例分析,揭示微服务架构在不同场景下的应用策略和最佳实践。无论你是后端开发新手还是经验丰富的工程师,本文都将为你提供宝贵的见解和实用的指导。
|
4天前
|
监控 Cloud Native Java
云原生架构下微服务治理策略与实践####
【10月更文挑战第20天】 本文深入探讨了云原生环境下微服务架构的治理策略,通过分析当前技术趋势与挑战,提出了一系列高效、可扩展的微服务治理最佳实践方案。不同于传统摘要概述内容要点,本部分直接聚焦于治理核心——如何在动态多变的分布式系统中实现服务的自动发现、配置管理、流量控制及故障恢复,旨在为开发者提供一套系统性的方法论,助力企业在云端构建更加健壮、灵活的应用程序。 ####
44 10
|
3天前
|
缓存 运维 监控
后端开发中的微服务架构实践与挑战#### 一、
【10月更文挑战第22天】 本文探讨了微服务架构在后端开发中的应用实践,深入剖析了其核心优势、常见挑战及应对策略。传统后端架构难以满足快速迭代与高可用性需求,而微服务通过服务拆分与独立部署,显著提升了系统的灵活性和可维护性。文章指出,实施微服务需关注服务划分的合理性、通信机制的选择及数据一致性等问题。以电商系统为例,详细阐述了微服务改造过程,包括用户、订单、商品等服务的拆分与交互。最终强调,微服务虽优势明显,但落地需谨慎规划,持续优化。 #### 二、
|
4天前
|
运维 Cloud Native 持续交付
云原生架构下的微服务设计原则与实践####
【10月更文挑战第20天】 本文深入探讨了云原生环境中微服务设计的几大核心原则,包括服务的细粒度划分、无状态性、独立部署、自动化管理及容错机制。通过分析这些原则背后的技术逻辑与业务价值,结合具体案例,展示了如何在现代云平台上实现高效、灵活且可扩展的微服务架构,以应对快速变化的市场需求和技术挑战。 ####
23 7
|
1天前
|
缓存 自然语言处理 并行计算
基于NVIDIA A30 加速卡推理部署通义千问-72B-Chat测试过程
本文介绍了基于阿里云通义千问72B大模型(Qwen-72B-Chat)的性能基准测试,包括测试环境准备、模型部署、API测试等内容。测试环境配置为32核128G内存的ECS云主机,配备8块NVIDIA A30 GPU加速卡。软件环境包括Ubuntu 22.04、CUDA 12.4.0、PyTorch 2.4.0等。详细介绍了模型下载、部署命令及常见问题解决方法,并展示了API测试结果和性能分析。
25 1
|
6天前
|
消息中间件 Java API
微服务架构设计与实现:从理论到实践
微服务架构设计与实现:从理论到实践
26 7
|
5天前
|
设计模式 API 持续交付
深入理解微服务架构:设计模式与实践
【10月更文挑战第19天】介绍了微服务架构的核心概念、设计模式及最佳实践。文章详细探讨了微服务的独立性、轻量级通信和业务能力,并介绍了聚合器、链式和发布/订阅等设计模式。同时,文章还分享了实施微服务的最佳实践,如定义清晰的服务边界、使用API网关和服务发现机制,以及面临的挑战和职业心得。
|
4天前
|
运维 Cloud Native API
云原生时代下的微服务架构实践
【10月更文挑战第22天】在数字化转型的浪潮中,云原生技术正以前所未有的速度重塑软件开发和运维的模式。微服务架构作为云原生的重要组成部分,其设计哲学、技术栈选择以及与传统单体应用的根本区别成为了现代软件工程讨论的焦点。本文将深入探讨微服务架构的核心概念,通过实际案例分析其在云平台下的应用,并分享在实施过程中的经验教训,旨在为读者提供一套清晰的微服务架构实践指南。
|
6天前
|
运维 监控 API
后端开发中的微服务架构实践与挑战####
【10月更文挑战第19天】 本文将深入浅出地探讨微服务架构在后端开发中的应用,通过实例解析其核心理念、优势所在,以及实施过程中可能遭遇的挑战与应对策略。不同于传统单体应用,微服务以其轻量级、灵活性和可扩展性受到青睐,但同时也带来了服务间的通信复杂性、数据一致性等问题。通过本篇文章,读者将对微服务架构有一个全面而深入的理解,为实际项目中的选型与实施提供参考。 ####
|
6天前
|
缓存 监控 API
微服务架构下RESTful风格api实践中,我为何抛弃了路由参数 - 用简单设计来提速
本文探讨了 RESTful API 设计中的两种路径方案:动态路径和固定路径。动态路径通过路径参数实现资源的 CRUD 操作,而固定路径则通过查询参数和不同的 HTTP 方法实现相同功能。固定路径设计提高了安全性、路由匹配速度和 API 的可维护性,但也可能增加 URL 长度并降低表达灵活性。通过对比测试,固定路径在性能上表现更优,适合微服务架构下的 API 设计。