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

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

目录
相关文章
|
13天前
|
API 持续交付 开发者
后端开发中的微服务架构实践与挑战
在数字化时代,后端服务的构建和管理变得日益复杂。本文将深入探讨微服务架构在后端开发中的应用,分析其在提高系统可扩展性、灵活性和可维护性方面的优势,同时讨论实施微服务时面临的挑战,如服务拆分、数据一致性和部署复杂性等。通过实际案例分析,本文旨在为开发者提供微服务架构的实用见解和解决策略。
|
14天前
|
弹性计算 Kubernetes Cloud Native
云原生架构下的微服务设计原则与实践####
本文深入探讨了在云原生环境中,微服务架构的设计原则、关键技术及实践案例。通过剖析传统单体架构面临的挑战,引出微服务作为解决方案的优势,并详细阐述了微服务设计的几大核心原则:单一职责、独立部署、弹性伸缩和服务自治。文章还介绍了容器化技术、Kubernetes等云原生工具如何助力微服务的高效实施,并通过一个实际项目案例,展示了从服务拆分到持续集成/持续部署(CI/CD)流程的完整实现路径,为读者提供了宝贵的实践经验和启发。 ####
|
3天前
|
Cloud Native 安全 API
云原生架构下的微服务治理策略与实践####
—透过云原生的棱镜,探索微服务架构下的挑战与应对之道 本文旨在探讨云原生环境下,微服务架构所面临的关键挑战及有效的治理策略。随着云计算技术的深入发展,越来越多的企业选择采用云原生架构来构建和部署其应用程序,以期获得更高的灵活性、可扩展性和效率。然而,微服务架构的复杂性也带来了服务发现、负载均衡、故障恢复等一系列治理难题。本文将深入分析这些问题,并提出一套基于云原生技术栈的微服务治理框架,包括服务网格的应用、API网关的集成、以及动态配置管理等关键方面,旨在为企业实现高效、稳定的微服务架构提供参考路径。 ####
20 5
|
6天前
|
监控 Go API
Go语言在微服务架构中的应用实践
在微服务架构的浪潮中,Go语言以其简洁、高效和并发处理能力脱颖而出,成为构建微服务的理想选择。本文将探讨Go语言在微服务架构中的应用实践,包括Go语言的特性如何适应微服务架构的需求,以及在实际开发中如何利用Go语言的特性来提高服务的性能和可维护性。我们将通过一个具体的案例分析,展示Go语言在微服务开发中的优势,并讨论在实际应用中可能遇到的挑战和解决方案。
|
4天前
|
负载均衡 监控 Cloud Native
云原生架构下的微服务治理策略与实践####
在数字化转型浪潮中,企业纷纷拥抱云计算,而云原生架构作为其核心技术支撑,正引领着一场深刻的技术变革。本文聚焦于云原生环境下微服务架构的治理策略与实践,探讨如何通过精细化的服务管理、动态的流量调度、高效的故障恢复机制以及持续的监控优化,构建弹性、可靠且易于维护的分布式系统。我们将深入剖析微服务治理的核心要素,结合具体案例,揭示其在提升系统稳定性、扩展性和敏捷性方面的关键作用,为读者提供一套切实可行的云原生微服务治理指南。 ####
|
6天前
|
Kubernetes Cloud Native Docker
云原生技术探索:容器化与微服务的实践之道
【10月更文挑战第36天】在云计算的浪潮中,云原生技术以其高效、灵活和可靠的特性成为企业数字化转型的重要推手。本文将深入探讨云原生的两大核心概念——容器化与微服务架构,并通过实际代码示例,揭示如何通过Docker和Kubernetes实现服务的快速部署和管理。我们将从基础概念入手,逐步引导读者理解并实践云原生技术,最终掌握如何构建和维护一个高效、可扩展的云原生应用。
|
7天前
|
监控 API 持续交付
后端开发中的微服务架构实践与挑战####
本文深入探讨了微服务架构在后端开发中的应用,分析了其优势、面临的挑战以及最佳实践策略。不同于传统的单体应用,微服务通过细粒度的服务划分促进了系统的可维护性、可扩展性和敏捷性。文章首先概述了微服务的核心概念及其与传统架构的区别,随后详细阐述了构建微服务时需考虑的关键技术要素,如服务发现、API网关、容器化部署及持续集成/持续部署(CI/CD)流程。此外,还讨论了微服务实施过程中常见的问题,如服务间通信复杂度增加、数据一致性保障等,并提供了相应的解决方案和优化建议。总之,本文旨在为开发者提供一份关于如何在现代后端系统中有效采用和优化微服务架构的实用指南。 ####
|
7天前
|
Docker 微服务 容器
使用Docker Compose实现微服务架构的快速部署
使用Docker Compose实现微服务架构的快速部署
17 1
|
9天前
|
消息中间件 设计模式 运维
后端开发中的微服务架构实践与挑战####
本文深入探讨了微服务架构在现代后端开发中的应用,通过实际案例分析,揭示了其在提升系统灵活性、可扩展性及促进技术创新方面的显著优势。同时,文章也未回避微服务实施过程中面临的挑战,如服务间通信复杂性、数据一致性保障及部署运维难度增加等问题,并基于实践经验提出了一系列应对策略,为开发者在构建高效、稳定的微服务平台时提供有价值的参考。 ####
|
10天前
|
消息中间件 监控 数据管理
后端开发中的微服务架构实践与挑战####
【10月更文挑战第29天】 在当今快速发展的软件开发领域,微服务架构已成为构建高效、可扩展和易于维护应用程序的首选方案。本文探讨了微服务架构的核心概念、实施策略以及面临的主要挑战,旨在为开发者提供一份实用的指南,帮助他们在项目中成功应用微服务架构。通过具体案例分析,我们将深入了解如何克服服务划分、数据管理、通信机制等关键问题,以实现系统的高可用性和高性能。 --- ###
33 2