16 KiB
DevOps案例——Etsy
1. 公司介绍
Etsy成立于2005年,是一个电子商务平台(C2C),以手工艺制品买卖为主要特色,其核心理念是社区化的C2C平台经营,基于此理念网站发展出了六大功能:论坛、虚拟实验室、聊天室、小组、微件、博客。
网站功能
- 虚拟实验室:Etsy为用户设置的在线课堂,学习与社交的方式,可以与手工制品培训人员互动学习
- 聊天室:公共聊天室,鼓励交流,增强社区黏性
- 小组:主题群组,组织线下聚会,通过聊天室和虚拟实验室进行线上交流
- 微件:用户插入到社交网站的标志性图标等
- 博客:面向Etsy社区的精美手工艺杂志,艺术家介绍、新手指南、主题活动通知,内容质量非常高,吸引用户
技术栈
PHP
业务规模:2014年数据
- 200多个国家的4200W用户
- 100W活跃卖家
- 13亿美元销售额
系统规模
- 访问量(Visit):60W/月
- 页面浏览量(PV):15亿/月
部署能力
每天超过60次部署
开发策略:
- 反复做许多小的、持续的变更=需要=>每天多次部署
- 开发人员确保:是否有足够的信心来部署这个变更
组织结构
- 600员工
- 核心团队,团队规模、角色配置
- 核心平台团队:10~15人:整体负责服务器及系统层与应用层的接口,为上层开发特性的工程师们提供服务,如数据库接入、图片存储系统、异步任务管理等
问题
- 业务与组织规模的快速扩张带来工程与管理问题,比如构建压力大,发布堵车等
2. 组织与文化
文化
原技术总监,现任CEO Chad Dickerson :
- 代码是工艺品(Code As Craft)
- 开发和运维是目标统一的合作者(交付团队)
组织
每个团队规模:3~7名工程师+1名产品经理+1名设计师
Etsy模式
组织文化
1. 工程师的快乐
-
尊重工程师
-
良好的工作环境
-
丰盛的午餐
-
每个人都可以部署代码(充分授权)
easy deploys == developer happiness本质:快速获得反馈,快速获得成就感
-
发布火车(排队)
2. 委派运维
内容:出席开发会议,参与新特性的发布,掌握开发团队的工作情况,以便提出自己的专业意见,比如第三方接口的稳定性等,但是该运维工程师不是只为该团队服务。
目的:,
-
让开发了解运维如何进行(专业信息的双向流通),培养开发的自运维能力
搜索团队已经能够处理日常的运维问题,当无法处理时,他们会反馈给专业的运维团队处理
-
运维团队会根据掌握的产品发布时间和内容预估基础设施需求,提前计划,寻求解决方案,比如服务器需求、新工具与技术的研究
互相了解对方的工作,理解对方的痛苦和努力,避免因为不了解而造成的误解,避免埋怨,提供正向的协作和沟通环境
-
打破部门墙,增强开放性的协作和交流,实现更频繁的跨组织协作和沟通
3. 其他
- 不要设置DevOps团队或者岗位
- 学习型文化,不惧怕失败
- 团队的目标统一
频繁部署
跟踪每次发布
度量一切
测试用例分组
参考
- https://codeascraft.com/2010/02/10/code-as-craft/
- https://codeascraft.com/2010/02/17/dev-ops-cooperation/
- https://codeascraft.com/2011/06/06/optimizing-for-developer-happiness/
- https://codeascraft.com/2012/02/13/the-etsy-way/
- https://codeascraft.com/2011/02/04/how-does-etsy-manage-development-and-operations/
多篇文章总结而成,分布于2010~2012年
3. 增量构建
原则
主干代码一定是时刻保持可发布到产品环境的状态
需求
在提交前必须验证变更
问题
- 在开发者VM上无法完整通过所有测试套件
- 在集成环境,执行一次完整测试需要3小时以上,使用CI集群可以降低到30分钟
- 在集成环境,由于失败后重新构建和修改后重新构建导致时间远超过30分钟
原因
- 集成环境测试执行通过慢的原因是在集成之前,从没有真正完整执行过一次测试套件,导致集成环境测试失败率高、反复多次
- 在提交前(开发环境VM),开发人员无法在合理(较短)的时间内完整执行一次测试套件
- 多名开发在单台机器上执行测试套件会相互之间干扰,且影响性能
实践内容:
-
基于稳定版本的增量式部署和测试(Etsy使用PHP语言,动态语言)
使用svn diff(Git同理)和稳定版本(trunk latest)对比,并将补丁文件覆盖到持续集成环境中的稳定版本之上。该方法可以实现多人同时部署更新并进行测试,相互之间不影响(并行测试)
-
具体方法 1)Create a new Jenkins Freestyle Project(or copy an existing) and Select Parameterized Build File Parameter: patch.diff String Parameter: username Set up the SCM as usual Add an Execute Shell build step: Apply the patch.diff Use $username in the recipient list of the e-mail publisher 2)Write a short bash script that Creates a patch Sends a cURL request with the patch and $USERNAME to start a build of the Jenkins job 代码示例:
#!/bin/bash HUDSON="ci.example.com" LOCATION="/home/$USER/working_copy" PATCH='patch.diff' cd $LOCATION svn diff > $PATCH file_param="{'name': 'patch.diff', 'file': 'file0'}" user_param="{'name': 'executor', 'value': '$USER'}" args=("$@") for ((i=0;i<${#args[@]};i++)); do curl -F file0=@$LOCATION/$PATCH -F json="{'parameter': [$file_param, $user_param]}" -F Submit=Build http://$HUDSON/job/try-${args[i]}/build done -
文化
在提交之前一定要try一次,是团队自觉遵守的规范
-
工具
TryLib 是简单的PHP库,帮助你生成工作副本之间的差异报告,发送到Jenkins,在最新代码分支上运行测试套件。Try将Jenkins和Deployinator连接起来。
参考
- Mozilla的TryServer:https://wiki.mozilla.org/ReleaseEngineering/TryServer
- https://codeascraft.com/2011/10/11/did-you-try-it-before-you-committed/ 2011年10月
4. 构建集群
原则
1. test in a clone of production environment
2. keep the build fast
背景
- 业务与团队扩充,测试套件的执行压力增加,每天超过1950次构建与测试需要执行,如果在提高执行效率,避免拖慢交付流水线
问题
-
开发者在自己的VM中执行测试,破坏了“在类生产环境进行测试”的持续集成原则,因为VM上可能存在开发自己安装的软件
-
团队大家遵守规范和文化,不会破坏可发布的状态,采用主干开发并使用,使用Try(通过Jenkins将开发者提交的代码增量应用到具有稳定版本代码库的服务器上,并且执行所有的构建与测试)执行提交前验证,导致每天需要执行的测试套件次数增长较快(10个开发者,平均每天515次try,超过13700次测试套件的执行)
-
不同的构建/测试任务对CPU和磁盘I/O的消耗各不相同,同时执行多个同类型的任务会导致资源瓶颈,构建服务器压力较大
-
构建服务器上,多个构建/测试任务同时执行时,容易导致工作区内容冲突
需求
- 测试套件执行时间保持在5分钟内
- 需要根据测试的不同类型,灵活的将测试分配到不同的服务器上执行,以免造成资源的负载过高,形成卡顿
- 工作区内容独立,即文件系统独立
实践
核心策略:尽量在主干分支上开发
Etsy的测试主要可以分为两类:
| 类型 | 测试阶段 | 量级 | 说明 |
|---|---|---|---|
| CPU消耗型 | 单元测试 | 轻量级 | 对CPU的压力较大 |
| I/O消耗型 | 集成测试 | 重量级 | 对磁盘I/O压力较大 |
虚拟化构建环境
-
基于LXC的虚拟化技术,每个容器都是独立的构建/测试环境,均衡工作负载。
-
容器和Executor的比例是1:1,每1个独立的容器都是1个slave,实现了资源隔离,避免了多个Executor访问同一个workspace目录,也更加便于使用Virtual Madness实现构建环境的自动化提供
-
采用Divide and Consur策略和Jenkins的master project插件,实现测试套件的并发执行
Virtual Madness
Virtual Madness是Etsy管理容器化构建环境平台,Etsy采用LXC来实现构建环境容器化(此时还没有Docker)
注:在Etsy,构建服务器采用实体机(刀片式服务器,每个刀片可以视为一台单独的服务器),具体配置如下:
2U的超微服务器,4个刀片,每个刀片挂载3块SSD,共12块盘
Virtual Madness界面
其中会显示容器的运行状态,容器被挂载到哪个Jenkins上,容器所在的物理磁盘等(BOB代表容器)
注:每一列就是一个刀片,共3块盘,每一列划分为不同的用途,如BUILDTEST01.NY4DEV即构建与测试用的服务器,在纽约
一键创建容器
Etsy坚持一键部署原则,构建环境管理也一样
-
选择容器创建位置
如图所示,Host下拉列表会默认选中第一个充足磁盘空间的主机,实践中,每台主机一般为14个容器(逐渐可支持更多容器)。点击Make it即可创建容器

**物理磁盘的选择规则:**每个刀片的第1块最多起4个容器,第2\3块磁盘各自最多起5个容器(4+5+5=14),如此,第一块磁盘有足够的空间以支持操作系统的使用并提供基础LVM卷(包含容器的初始化配置内容,会分发到每个容器中)
-
使用lvcreate和mkfs.ext3创建容器时创建并挂载一个空的LVM卷
-
将基础LVM卷的内容复制到该卷中
-
使用Chef完成容器的配置
规则:每块磁盘同时只能允许执行一个重量级任务
容器分类:CI HEAVY \ CI ANY \ TRY HEAVY \ TRY ANY
-
环境维度:
-
CI环境是后台自动执行,用于构建和更完善的测试
-
TRY环境是开发者使用,用于自行构建和进行测试
-
-
任务维度:
-
HEAVY任务是指重量级任务,比如集成测试,对IO要求较高
-
ANY任务是指轻量级的任务,比如单元测试,对CPU要求较高
-
Virtual Madness其他功能
参考资料
https://codeascraft.com/2013/09/23/lxc-running-14000-tests-per-day-and-beyond-part-1/ https://codeascraft.com/2013/09/23/lxc-automating-containers-aka-virtual-madness-part-2/
2013年9月
5. 测试分组
原则
Fail fast
Fast, Reliable Tests
问题
每天超过25次部署,每次部署都需要执行测试
需求
部署耗时需要保持在20分钟内,长时间的部署过程会导致部署队列拥堵,降低效率,也会打击部署积极性
实践
主干测试(trunk tests):部署之前必须执行的测试,用于测试Etsy的产品功能,Etsy拥有7000个主干测试(持续增长中,并非都是单元测试,并非所有测试都要在部署前执行)
将重量级测试套件根据相似性拆分为多个轻量级的测试套件
5分钟、11分钟、20分钟
如果主干测试失败,部署将会暂停,工程师一般将会在5分钟内解决该问题,然后重新执行测试,通过之后本次部署成功结束,考虑失败的情况,自动化主干测试必须在11分钟内完成,才有足够的剩余时间重新测试,以免部署超过20分钟太多
测试执行策略
从前到后顺序执行完所有的主干测试(大于7000)需要耗时超过30分钟,如何实现11分钟:
1. 对测试进行分组,选择性执行
2. 将测试分发到10台Jenkins构建服务器上并行执行
测试的执行场景
- 每次提交时执行
- 点击测试按钮时执行
- 点击发布按钮时执行
测试分类
单元测试(Unit Tests)
单元测试只针对一个类,无需与数据库、文件或其他基础设施的交互。Etsy拥有超过4500个单元测试,且必须在部署前执行,耗时1分钟
集成测试(Integration Tests)
集成测试需要与基础设施交互,如数据库、消息队列、缓存服务等,Etsy使用PHPUnit连接DBUnit(模拟数据库服务),Etsy的PHPUnit扩展
在Etsy的测试套件中,集成测试最慢,如果顺序执行需要耗时20分钟,并行执行则只需耗时8分钟
网络测试(Network Tests)
部分集成测试也需要访问网络资源,如使用第三方服务发送邮件等,Etsy建议尽量避免需要依赖网络访问的测试
冒烟测试(Smoke Tests)
冒烟测试是系统级别的测试,测试目标是部署完成系统,通过PHPUnit执行Curl命令,并且使用断言比对返回结果的header和其他数据
功能测试(Functional Tests)
类似冒烟测试,基于GUI驱动的端到端功能测试的测试目标也是部署在测试环境的系统,Etsy使用Cucumber和Selenium进行功能测试,基于Xvfb的虚拟桌面环境驱动Firefox进行测试
功能测试脚本需要在开发和维护上投入较大工作量,在Etsy,端到端的功能测试只用于最重要的关键功能。在单元测试、集成测试、冒烟测试之后,功能测试只作为最终的验证
编者注:功能测试对重要功能的验证是很有意义的,从用户操作的角度进行验证。比如Dropbox有一次部署将用户验证功能关闭了,导致极大的安全问题,类似这样的关键功能应该增加最后一道门槛。
不稳定测试(Intermittent Tests)
不稳定测试:偶尔会失败的测试,但是对于开发很有帮助,此类测试不能纳入主干测试,因此Etsy建立了PHPUnit中的flaky组,当开发者认为测试不够稳定时,可以将其划分到flaky组中,并在其他场景下使用
零容忍对待不稳定测试
慢测试(Slow Tests)
符合80/20原则,少数测试占用了主要的测试执行时间,工程师会将耗时长的测试划分到slow组,失败和取消最慢的20个慢测试,测试套件执行时间会有明显提升,此类测试依然会在某些场景下执行,但是主干测试中不进行
睡眠测试和时间测试(Sleep Tests and Time Tests)
好的测试不会使用sleep(),也不会依赖于系统时间。 追求测试覆盖率会导致出现低质量的测试用例,尤其是维护历史系统时,Etsy允许这类测试存在,但是不在部署前执行这些测试
工具
-
PHPUnit,使用其他@group的注释功能,从逻辑上将测试划分为多个子集。
-
Jenkins,使用XUnit插件,选择性执行测试
<groups> <include> <group>dbunit</group> </include> <exclude> <group>database</group> <group>network</group> <group>flaky</group> <group>sleep</group> <group>slow</group> </exclude> </groups>
*解释:PHPUnit任务只会执行dbunit,不会执行database,network, flaky, sleep,slow;所有单元测试必须被执行。*
### 总结
1. 识别并分组执行测试
2. 并行执行测试
3. 只保留对部署对重要的测试
##### 文化
Etsy的部署由工程师管理,而非运维或者其他发布管理团队
理由:你写的代码,你自己发布到线上。$$Even $$ $$dogs $$ $$deploy $$ $$code$$
授权工程师对测试用例进行分类
### 参考
https://codeascraft.com/2011/04/20/divide-and-concur/
*2011年4月*



