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

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

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让笔记本电脑的风扇声音大的就像喷气发动机。

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

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

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

name: api
type: service
groups:
  - name: integration
    members:
     - driver_onboarding
     - users
tests:
  - name: integration
    group: integration
预发环境(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和集成测试了。

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

  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/

你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。
微信公众号:DeepNoMind

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容