Lyft 微服务研发效能提升实践 | 1. 开发和测试环境的历史

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,118元/月
简介: Lyft 微服务研发效能提升实践 | 1. 开发和测试环境的历史

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


image.png


2018 年底,Lyft 工程团队完成了将最初的 PHP 单体拆分为 Python 和 Go 微服务的工作,在接下来的几年里,微服务在很大程度上成功的帮助团队独立运行和发布服务。微服务所带来的关注点分离使我们能够更快试验和交付特性(可以每天部署数百次),并且提供了足够的灵活性,可以在合适的地方采用不同的编程语言,还可以根据服务的关键程度采用更严格或更宽松的需求,等等。然而,随着工程师、服务、测试数量的增加,开发工具很难跟上微服务的爆炸式增长,拖累了生产率的增长。


本系列分为四部分,将介绍 Lyft 工程团队从 100 名工程师和少量服务发展到 1000 多名工程师以及数百项服务的过程中所使用的开发环境。我们将讨论导致我们放弃这些环境的规模化挑战,以及从主要基于大量集成测试(通常接近端到端)的测试方法,转变为以独立测试组件为中心的本地优先方法。



开发和测试环境的历史


我们在综合开发环境的第一个重大投资始于 2015 年,当时我们有 100 名工程师,几乎所有的开发工作都集中在一个单体 PHP 系统上,在某些用例中开始出现一些微服务(比如司机登录)。


由于预计到需要服务的工程师和服务的数量将会持续增长,因此有必要采用容器化方案。我们计划构建一个基于 docker 的容器编排环境(当时 docker 还处于起步阶段),首先服务于开发人员的测试工作,然后再扩展到生产环境,在生产环境中,多租户工作负载的成本更低、扩展速度更快,我们将因此受益。

利用 Devbox 进行本地开发


Devbox 是 Lyft 的即开即用开发环境,于 2016 年初发布,很快就被大多数工程师所采用。Devbox 的工作方式是代表用户管理一个本地虚拟机,这样工程师就不必安装或更新依赖包、配置 runit[2]启动服务、添加共享文件夹,等等。VM 运行后,只需一个命令和几分钟就可以获取最新版本的镜像、创建/初始化数据库、启动 envoy proxy sidecar[3],以及在开始发送请求前所需的一切依赖。


与之前相比,这次升级非常棒,我们手动为每个开发人员及其负责的服务提供了一个 EC2 实例,这使得设置和保持更新非常繁琐。我们第一次有了一种一致的、可重复的、简单的方法来完成跨多个服务的开发。


利用 Onebox 进行远程开发


很快就出现了新的需求,需要能够与其他工程师或团队(如设计团队)共享的、可长期维持的环境,因此我们打造了 Onebox。Onebox 本质上是一个 EC2 实例上的 Devbox,它有许多吸引用户放弃 Devbox 的优点。我们将其部署在 r3.4xlarge 实例上,拥有 16 个 vCPU 和 122G 内存,比工程师随身携带的 MacBook Pro 要强大得多。Onebox 可以运行更多的服务,下载容器镜像更快(因为基于 AWS),更不用说还可以避免 VirtualBox 让笔记本电脑的风扇声音大的就像喷气发动机。


image.png

我们有两种不同的开发环境,每种环境都能够运行多个服务

集成测试


除了单元测试以外,Onebox 的云基础设施也很适合在 CI 上运行集成测试。服务可以简单的在manifest.yaml文件中定义需要的依赖项,一个临时的 Onebox 会启动这些服务,并对每一个 pull request 执行测试。许多服务,特别是靠近移动客户端的服务组合,会需要构建大型集成测试套件来应对异常的服务失效,并且每次事故分析通常都会以添加新的集成测试结束。有了如此灵活和强大的测试功能,单元测试逐渐退居次要地位。


name: api
type: service
groups:
  - name: integration
    members:
     - driver_onboarding
     - users
tests:
  - name: integration
    group: integration


定义要在 CI 中运行的集成测试的服务示例


预发环境(Staging environment)


Lyft 的预发环境与生产环境几乎相同(除了使用更少的资源,也没有生产数据),所有服务都是和生产环境交付一致的过程部署的。尽管不是开发环境,但因为预发环境在端到端测试中扮演着越来越重要的角色,因此同样值得讨论。


在 2017 年初 Devbox 和 Onebox 发布后不久,我们还解决了另一类不断增长的问题:负载测试。那些会造成拼车需求流量激增的事件(比如新年和万圣节),会暴露我们系统的瓶颈,并且往往会导致宕机。为了解决这些问题,我们构建了一个框架来模拟大规模流量。该框架针对我们的生产环境,协调数以万计具有不同配置的模拟用户(例如,模拟洛杉矶的一名经常取消订单的司机),并将 Lyft 视为黑匣子。


作为阶段性测试仿真框架本身的副产品,我们意识到生成的流量对于一般的端到端测试也是有价值的。在预发环境中不断测试公共接口可以为真正的部署提供很好的信号。例如,如果部署破坏了让乘客下车的接口,部署的发起者几乎立即就能看到错误日志和警报。模拟还会持续生成用户、车辆、支付等最新数据,减少了开发过程中必须进行的手动测试的设置时间。随着负载测试的努力,预发环境变得比以往任何时候都更加现实和有用,团队将 PR 分支部署在那里,从而可以获得真实数据的一致的反馈,这已经成为一种普遍现象。


新的问题


快进到 2020 年(在将 Devbox 和 Onebox 作为容器化开发环境引入 4 年后),尽管我们尽了最大的努力,但“Lyft-in-a-box”风格的环境仍然难以跟上。使用这些环境的工程师增加了十倍,现在有数百个微服务为更复杂的业务提供支撑。虽然在依赖关系较小的服务上开发仍然相当高效,但大多数开发都是在已经构建了巨大依赖关系树的服务上进行的,这使得在 CI 上启动环境或运行测试非常缓慢。


虽然这些环境和测试功能非常强大和方便,但却达到了弊大于利的程度。我们构建了一个为测试少量服务而优化的系统,当服务的数量从 5 个增加到 50 个,从 50 个增加到 100 个,甚至更多的时候,我们没有重新评估我们的策略。这不仅需要大量的服务来进行维护和扩展,而且还会因为迫使开发人员不断的从整个系统的角度而不是从一个组件的角度来考虑而降低开发人员的生产力。


让我们更详细的看看这个问题的一些细节:


扩展性问题


由于涉及的资源数量庞大,且与类似于生产环境的环境存在分歧,Onebox 环境的扩展变得很不现实。例如,在数百个环境中运行相同的可观察性工具是不可行的。当出现问题时,很难找出确切的原因(运行的 70 个服务中哪个可能有问题?),人们倾向于在放弃并在预发测试之前按几次“reset”按钮。


另一方面,预发环境既容易缩放,又能更忠实的反映生产环境。它提供了同样的日志记录、跟踪和度量功能来帮助调试。部署到共享的预发环境的主要缺点是:(1)实验更改可能会破坏他人的使用环境,(2)每次只能有一个服务做出一次变更才能够有效进行测试,(3)由于需要同步代码和热加载,需要花费更多的时间(分钟)来构建和部署。


维护困难


由于上述伸缩性的挑战,维护和优化这些环境花费了大量时间,导致技术落后。生产环境和预发环境已经用 Kubernetes 进行容器编排,同时切换到更小的单进程容器镜像。开发使用了捆绑了 sidecars 和其他基础设施组件(指标、日志等)的更重的多进程镜像,使得构建和下载镜像的速度更慢。


每周都有一些变更会造成问题,这些变更不会影响预发或生产环境,但会影响开发环境。由于大多数开发者需要运行大多数服务,一个服务的问题会造成很大的影响。一些团队已经将他们所有的端到端测试转移到预发阶段,使得他们的服务在开发过程中变得越来越弱,进一步加剧了这种问题。


问题所有权不清


在开发环境中,问题的所有权是不清楚的。谁应该负责修复引起问题的特定服务?是启动这个 Onebox 的人、服务的负责人还是开发者基础设施团队?在实践中,这常常落在开发者基础设施团队的头上,但他们无法诊断和解决与应用程序相关的问题(例如,配置变更导致应用程序在启动时崩溃)。


臃肿的测试


笨重的集成测试套件已经成为生产力的一大消耗。长达一小时的测试套件随处可见,运行在复杂的分片基础设施上,通过自动重试来弥补不稳定的环境造成的问题。造成这一问题有两个主要的驱动因素,依赖关系的膨胀和测试本身。由于依赖的传递性,依赖的服务会在服务所有者没有注意到的情况下逐渐增加,从而消耗掉测试时间。测试套件本身也在稳步增长,尽管我们会在出现问题时添加测试,但很少会因为假设现有的测试有作用而被删除。


那么,为什么我们要为合并一个 PR 而花费几小时的等待时间呢?当然是因为可以在 bug 进入生产环境之前捕获它们!但通过在实践中进一步检验,这一理论并不成立。对我们开发得最活跃的一些服务的集成测试进行分析发现,80%或更多的测试要么是不必要的(例如,过时的或现有单元测试的副本),要么可以重写,从而可以在不依赖外部的情况下以较短的时间运行。当测试失败时,大多数都是误报,而这将耗费数小时的调试时间,其余测试通常会在通过预发或金丝雀环境并造成生产问题之前被捕获。


# 2013 (monolith), duration: 1 minute
def test_driver_approval():
    """
    Requires: 
        - api
    """
    user = get_user()
    approve_driver(user)
    assert user.is_approved
# ------------------------------------------------------------ #
# 2015 (mostly monolithic, a few services), duration: 3 minutes
def test_driver_approval():
    """
    Requires:
        - api (monolith)
        - users
            - mongodb
        - driver_onboarding
            - mongodb
            - redis
    """
    user = user_service.create_user()
    user = driver_onboarding_service.approve_driver(user)
    assert user.is_approved
# ------------------------------------------------------------ #
# 2018 (post-decomp, microservices), duration: 20 minutes
def test_driver_approval__california():
    """
    Requires:
        - users
            - redis
            - experimentation
            - fraud
                - dynamodb
            - messaging
                - mongodb
        - driver_onboarding
            - messaging
                - email
                - experimentation
            - dmv_checks
                - vehicles
                    - payments
    """
    user = user_service.create_user()
    user = driver_onboarding_service.approve_driver(user)
    assert user.is_approved
def test_driver_approval__newyork():
    # ...
def test_driver_approval__montreal():
    # ...

随着我们继续分离出新的微服务,集成测试变得越来越笨拙。

改变过程


在大约一年前开始将我们的开发环境迁移到 Kubernetes 之后,工程资源的变化成为了我们缩小并重新审视发展方向的催化剂。维护基础设施以支持随需应变的环境变得过于昂贵,而且随着时间的推移会变得越来越糟。要解决这种情况,我们需要对开发和测试微服务的方式进行更彻底的改变,是时候用对由数百个微服务组成的系统具有可持续性的替代方案来取代在 CI 上的 Devbox、Onebox 和集成测试了。


仔细观察开发人员是如何使用现有环境的,我们确定了三个关键的工作流(在下图中用紫色表示),这三个工作流对维护非常重要,并且需要进行投资:


image.png


  1. 本地开发: 对于任一给定服务,运行单元测试或启动 web 服务器并发送请求都应该非常简单快捷。
  2. 手动端到端测试: 测试特定变更在更大的系统中如何执行是许多工程师依赖的关键工作流程。我们希望扩展预发测试,使开发人员可以更容易、更安全的独立进行测试。
  3. 自动端到端测试: 尽管我们过度依赖于这种测试,但如果没有自动化的端到端测试提供的信心,我们无法继续每天交付数百次变更。我们将保留一小部分有价值的测试作为验收测试,在部署到生产环境时运行。


本系列后续文章将深入研究这三个领域,我们将讨论相关问题、如何处理以及学到了什么。


References:

[1] Scaling productivity on microservices at Lyft (Part 1): https://eng.lyft.com/scaling-productivity-on-microservices-at-lyft-part-1-a2f5d9a77813

[2] runit - a UNIX init scheme with service supervision: http://smarden.org/runit/

[3] Envoy Proxy: https://www.envoyproxy.io/

目录
相关文章
|
6天前
|
敏捷开发 人工智能 Devops
探索自动化测试的高效策略与实践###
当今软件开发生命周期中,自动化测试已成为提升效率、保障质量的关键工具。本文深入剖析了自动化测试的核心价值,探讨了一系列高效策略,包括选择合适的自动化框架、设计可维护的测试脚本、集成持续集成/持续部署(CI/CD)流程,以及有效管理和维护测试用例库。通过具体案例分析,揭示了这些策略在实际应用中的成效,为软件测试人员提供了宝贵的经验分享和实践指导。 ###
|
5天前
|
机器学习/深度学习 人工智能 jenkins
软件测试中的自动化与持续集成实践
在快速迭代的软件开发过程中,自动化测试和持续集成(CI)是确保代码质量和加速产品上市的关键。本文探讨了自动化测试的重要性、常见的自动化测试工具以及如何将自动化测试整合到持续集成流程中,以提高软件测试的效率和可靠性。通过案例分析,展示了自动化测试和持续集成在实际项目中的应用效果,并提供了实施建议。
|
6天前
|
Java 测试技术 持续交付
探索自动化测试在软件开发中的关键作用与实践
在现代软件开发流程中,自动化测试已成为提升产品质量、加速交付速度的不可或缺的一环。本文深入探讨了自动化测试的重要性,分析了其在不同阶段的应用价值,并结合实际案例阐述了如何有效实施自动化测试策略,以期为读者提供一套可操作的实践指南。
|
5天前
|
Cloud Native 安全 API
云原生架构下的微服务治理策略与实践####
—透过云原生的棱镜,探索微服务架构下的挑战与应对之道 本文旨在探讨云原生环境下,微服务架构所面临的关键挑战及有效的治理策略。随着云计算技术的深入发展,越来越多的企业选择采用云原生架构来构建和部署其应用程序,以期获得更高的灵活性、可扩展性和效率。然而,微服务架构的复杂性也带来了服务发现、负载均衡、故障恢复等一系列治理难题。本文将深入分析这些问题,并提出一套基于云原生技术栈的微服务治理框架,包括服务网格的应用、API网关的集成、以及动态配置管理等关键方面,旨在为企业实现高效、稳定的微服务架构提供参考路径。 ####
26 5
|
9天前
|
监控 Go API
Go语言在微服务架构中的应用实践
在微服务架构的浪潮中,Go语言以其简洁、高效和并发处理能力脱颖而出,成为构建微服务的理想选择。本文将探讨Go语言在微服务架构中的应用实践,包括Go语言的特性如何适应微服务架构的需求,以及在实际开发中如何利用Go语言的特性来提高服务的性能和可维护性。我们将通过一个具体的案例分析,展示Go语言在微服务开发中的优势,并讨论在实际应用中可能遇到的挑战和解决方案。
|
7天前
|
负载均衡 监控 Cloud Native
云原生架构下的微服务治理策略与实践####
在数字化转型浪潮中,企业纷纷拥抱云计算,而云原生架构作为其核心技术支撑,正引领着一场深刻的技术变革。本文聚焦于云原生环境下微服务架构的治理策略与实践,探讨如何通过精细化的服务管理、动态的流量调度、高效的故障恢复机制以及持续的监控优化,构建弹性、可靠且易于维护的分布式系统。我们将深入剖析微服务治理的核心要素,结合具体案例,揭示其在提升系统稳定性、扩展性和敏捷性方面的关键作用,为读者提供一套切实可行的云原生微服务治理指南。 ####
|
6天前
|
Web App开发 敏捷开发 测试技术
探索自动化测试的奥秘:从理论到实践
【10月更文挑战第39天】在软件质量保障的战场上,自动化测试是提升效率和准确性的利器。本文将深入浅出地介绍自动化测试的基本概念、必要性以及如何实施自动化测试。我们将通过一个实际案例,展示如何利用流行的自动化测试工具Selenium进行网页测试,并分享一些实用的技巧和最佳实践。无论你是新手还是有经验的测试工程师,这篇文章都将为你提供宝贵的知识,帮助你在自动化测试的道路上更进一步。
|
6天前
|
敏捷开发 Java 测试技术
探索自动化测试:从理论到实践
【10月更文挑战第39天】在软件开发的海洋中,自动化测试是一艘能够带领团队高效航行的船只。本文将作为你的航海图,指引你理解自动化测试的核心概念,并分享一段实际的代码旅程,让你领略自动化测试的魅力和力量。准备好了吗?让我们启航!
|
8天前
|
Kubernetes Cloud Native Docker
云原生技术探索:容器化与微服务的实践之道
【10月更文挑战第36天】在云计算的浪潮中,云原生技术以其高效、灵活和可靠的特性成为企业数字化转型的重要推手。本文将深入探讨云原生的两大核心概念——容器化与微服务架构,并通过实际代码示例,揭示如何通过Docker和Kubernetes实现服务的快速部署和管理。我们将从基础概念入手,逐步引导读者理解并实践云原生技术,最终掌握如何构建和维护一个高效、可扩展的云原生应用。
|
10天前
|
监控 API 持续交付
后端开发中的微服务架构实践与挑战####
本文深入探讨了微服务架构在后端开发中的应用,分析了其优势、面临的挑战以及最佳实践策略。不同于传统的单体应用,微服务通过细粒度的服务划分促进了系统的可维护性、可扩展性和敏捷性。文章首先概述了微服务的核心概念及其与传统架构的区别,随后详细阐述了构建微服务时需考虑的关键技术要素,如服务发现、API网关、容器化部署及持续集成/持续部署(CI/CD)流程。此外,还讨论了微服务实施过程中常见的问题,如服务间通信复杂度增加、数据一致性保障等,并提供了相应的解决方案和优化建议。总之,本文旨在为开发者提供一份关于如何在现代后端系统中有效采用和优化微服务架构的实用指南。 ####