在本次访谈中,Sam Haskins 分享了他在 Etsy 做 DevOps 的经验。Etsy 使用 Git 做代码管理,平均每天提交的更新数量在 30 次左右,有时甚至达到 70 个。在如此高频率提交代码的情况下,他们如何进行代码审查?如何在每次更新之后监控系统性能?如何处理故障?下面,Sam 将一一解答。
Sam Haskins 目前属于 Etsy 的核心平台团队,该团队一共有 10~15 人,整体负责服务器及系统层与应用层的接口,为上层开发特性的工程师们提供服务。Sam 于 2010 年中加入 Etsy,他现在主要负责数据库接入层,也在 web 框架上做一些东西。核心平台团队的其他成员还负责开发图片存储系统、异步任务管理等功能。Sam 毕业于卡内基梅隆大学的数学系。
代码部署和审计
InfoQ:第一个问题是关于部署系统的。你们使用哪些工具?
Sam:我们使用 Git 做代码版本管理,使用 Deployinator(我们为自己开发的一个工具)从 Git 中拿东西出来,并把它放到所有的服务器上。基本上就是文件复制工作,没有什么特别的。另外,我们使用 Chef 做配置管理。就这些了。实际上我们用 GitHub。我们有一个内部的 GitHub 来管理代码。
关于代码审计流程,实际上我们没有很多正式的检查。只要你的代码能编译,能运行,你就可以放到代码库上,不需要审计。但是,我们鼓励大家做代码复查,而且大家经常这么做。代码复查通常是这样的——你只要把你的补丁发给别人看,他们看过后给你一些点评。大多数人在推送代码到代码库前都会这么做。
InfoQ:有 QA 吗?
Sam: 没有。我们没有传统意义上的 QA。因为我们每次的提交都很小,所以发现哪些代码破坏了系统通常是相当容易的。当然,也有一些人的工作有点儿像 QA,他们的工作就是试着破坏我们的系统,然后让我们知道哪些东西坏了。我们部署做得很多,一天部署 50 次挺平常的,有时候会更多。在部署前后做全面的测试是不可能的,根本没有足够的时间。但是我们有自动化测试,和代码审查等等,而且我们每次的变更都很小,所以,我们认为我们并不需要有一个大规模的正式的 QA 过程。
InfoQ:那么,当你做更新时,你如何做环境控制,保证代码安全?
Sam:对于我们的工具 Deployinator 来说,只要你点一下按钮,它所做的就是从 Git 中拿出东西,并把它分发下去,仅此而已。
InfoQ:你们部署的是源代码,而不是打包后的二进制文件?
Sam:是的,我们不打包。它就是把它们放到 web 服务器的某个目录下之类的,没有什么太复杂的。我们不做太复杂的事儿。
非常简单,只要几分钟就运行完了。 正是因为很容易,所以我们才能部署这么多次。
InfoQ:那么一天 50 次是比较典型的部署频率吗?
Sam:差不多。今年的平均值可能是 30 或 33 次,算上星期六和星期日。但通常星期六和星期日我们什么也不做。我想,最多的时候,我们可能在一天里我们最多部署 60 或者 70 次,少的时候也有二、三十次。的确很多,但这也是我们改变了写代码的方式,让它变成合理的事情了。假如我们每次都做很大的变更,这么做可能就不行啦,一定会变成一种灾难。正是因为我们每次只做很小的改更,所以这种方式才能行。
部署的流程与健康监控
InfoQ:由于你们每天部署这么多次,那么你们通常在一天中的什么时间部署?你是部署后,观察几分钟,然后再部署另一次吗?
Sam:是的,我们有很多度量项,可能成千上万吧。所以 每次部署后,除了要监控你的变更是否起作用了,我们还有一个标准的部署指示仪表盘,你需要关注。那上面有一堆图表,你要看着它,并确保仪表盘上的指标没有异常。
我们还有一个工具,用来读实时日志,你也要看看那个东西。假如有些东西不正常,那你就需要修复它。但只需要几分钟就行,通常是一两分钟。但我要看更多一些的图表数据。 只要没有什么问题出现,并且你认为你的修改正常工作了,那就轮到下一组啦。
InfoQ:当部署时,你是先把它部署到测试环境上吗?
Sam:有一个前提,在你部署之前,你已经自行测试过它了,所以我们是直接部署的。不过,的确也可以说我们已经在测试环境上部署过了,因为我们的试运行环境也是我们的生产环境,是同一个环境,只是版本高一点儿,而且用户不可能访问到这些服务器,只有我们才能访问。
因此,当部署时,首先会放到试运行环境,然后 CI(持续集成服务器)会运行测试,是单元测试。顺便说一下,我们使用的是 Jenkins。同时,你可以访问试运行环境,Jenkins 运行一系列的测试。之后,一旦推送代码的人认为试运行环境上的功能没有问题,他们已经测试过他们的变更,并认为这些变更运行正确的话,那么只是 Jenkins 运行完(通常总共需要 5 分钟),就可以推送到生产环境了。
InfoQ:你在这里提到的生产环境,是指所有的机器吗?
Sam:是的,是所有的机器。
这个工具只有两个按钮,一个指向试运行环境,另一个就指向生产环境。
InfoQ:那是否根据服务的不同,分成了不同的集群呢?比如一种服务,使用一类集群?
Sam:算是吧。所有的部署都是同样的软件栈。 同样的 web 软件栈。但我们的确有搜索专用的软件栈和图片存储专用的软件栈,但那些软件栈也与它相关。我们并没有太多的 Service。我们只有一个代码库,所以它也非常大,大约 2GB 吧。但绝大部分都用同一个软件栈。
因些,当做部署时,先是 web 服务器,然后是 API 服务器。我们有内部的支持系统,异步任务,Gearman,以及类似于运行后台任务 (Cron) 的机器和相关设施,所有这些都是一次完成的。
InfoQ:那么你会根据它们的重要程度做分别推送吗?你要知道,有些部署可能会对用户产生很大的影响。
Sam:我们非常喜欢现在这种工作方式的原因,就是因为这种方式决定了你说的这种情况不会经常发生。如果真有什么事情非常重要的话,那么你可以自己做部署,并确保更顺利。但是,通常来说,我们更喜欢鼓励大家以一种好的方式工作,以避免这种事情的发生。
InfoQ:也就是说,避免一次性做很大的变更,是吗?
Sam:是这样的。
InfoQ:所以,你们总是提交很小的变更?
Sam:当我们发布新功能时,我们会使用配置文件对功能进行控制,比如关闭这个功能,打开那个功能,因为很难一点儿一点儿的发布一个新功能。当需要发布某个功能时,我们再把开关打开。这种方法使得发布并不需要太多的精力,并且比较安全,我们可以做到只在公司内部发布,我们也可以做到只发布给属于 beta 群组中的用户,也能做到每次向百分之多少的用户发布。
所以当做了一点儿改动时,我们常常只发布给 1% 的用户,以验证没有破坏其它功能,然后再逐步发布给所有的用户,而不是一次性发布。如果发布的是一个新特性,我们更多的时候只是把开关打开。但有时我们只会将特性发布给一部分用户,以便观察用户在接下来的几天里是如何使用它的。我们会持续关注。
InfoQ:是不是说,beta 用户会被导向某个特定的集群?
Sam:节点都是一样的。我们只是在运行时检测,比如通过用户的 user ID。
InfoQ:你们是使用自己构建的系统做这件事呢?还是用其它的什么工具?
Sam:我们自己写的代码。它原来的版本是开源的,而这个新版本要比原来那个版本好得太多了,但是还没有发布,不过即将发布了。
这个项目的名字不怎么好,好像是叫做 Feature Flags 之类的。它在我们 Github 的主页里。
这就引出了另一个话题,也是我们开发中使用的方法。我们并不使用 Git 中的分支。大家并不会在一个分支上工作很长时间。通常一个分支的生命周期很短,很快就会把代码部署了。
有关故障
InfoQ:你们是如何处理故障的?
Sam:因为大家都被培训过如何看图表,如何查日志。通常我们发现故障是非常非常快的,因为一直有人在查看我们的那些度量项,这些度量项的确非常多。大家都非常了解这些度量项正常是应该是什么样子的。
InfoQ:那是一分钟查看一次,还是一秒钟?
Sam:我们使用 Nagios 做自动化的检查。每个检查的时间周期不同。有一些非常非常频繁,而有一些则不是。我们不但有很多这种 Nagios 做的自动化检查,还有一些人来看图表,以及对应的模式。这种图表的更新通常是一秒钟。
因为一直在不停地部署代码,所以不得不时刻查看那些图表。既然经常看着那些图表,那么对我们来说,快速地发现问题也就不是什么难事啦,因为一直不停地在做这件事,所以也就做得好啦。
InfoQ:听上去感觉好像很依赖人肉啊?
Sam:是这样的。但我们也有像 Nagios 这样的工具来检查那些可能出错的地方。不能及时捕获故障的情况很少,几乎总是能马上发现问题。那些无法用 Nagios 无法检查的东西在半夜也不会出问题,因为没人会在大家都睡觉时去部署代码。那些出错的地方通常都是在系统级别上,而通常都是由 Nagios 来监控的。在白天我们工作时,因为有新的部署,这些地方才有可能出错。但这时会有人看着,因为他们正在部署代码。所以那些很难用计算机去监控的东西在晚上不会出错。
InfoQ:那么,当出现了一个问题时,你们会采取什么样的措施?
Sam:如果某次部署严重地影响了网站,我们所做的就是要求大家别再推送代码,并让整个生产线停下来,然后无论是谁发现的问题,他都要马上开始分析,尝试找出是哪里出的错。我们都可以访问那些日志和图表数据。当我们确定哪个特性出了问题时,我们会联系那些做这个特性的人或可能知道这个特性的人。通常情况下,如果与部署有关的话,相关的部署人员都已经就位了,因为我们时刻使用 IRC Chat。
大家都在这个聊天室中,所以如果是部署问题,正在做部署和观察监控的人就知道哪里出了错,很可以也已经知道如何修复它了,因为每次部署只做了很小的改动,所以他们可以将代码回滚,部署原来的那个版本。但对于比较小的缺陷,我们更倾向于推送新的修复代码,而不是回滚。就是直接修复好缺陷,再次部署。
InfoQ:那会花的时间较长吧。
Sam:是的。所以如果只是小问题,能很快修复,那就提交并部署新的代码。因为即使回滚的话,你也要再一次推送代码,总共也需要大约十分钟的时间。因为先要上试运行环境,然后才是生产环境。所以无论你是提交修复,还是回滚,花的时间差不多。如果你多花五分钟 能够正确地修复问题,其实并不赖,甚至可能更好。
所以,通常我们会这么做。如果问题很严重,显然我们会先回滚,然后再找问题的根本原因。然而经常遇到的情况是你并不能完全确定回滚就是你想要的结果,所以与盲目回滚相比,我们更多的是试图找到真正的问题所在。
InfoQ:向前修复通常是以什么样的方式进行的呢?
Sam:大多数在部署时遇到的问题并不会影响整个网站,它们只是会影响其中的一小部分。如果是向前修复的话,当你在修复这个问题时,其他人还可以继续推送他们的代码,继续做他们原来做的事情。但是,显然我们做这种决定要基于故障的严重程度。
InfoQ:修复不好,怎么办?
Sam:如果不立即修复的话,就要开始做回滚操作。
InfoQ:比如说,花了十分钟以后,你也不能修复故障怎么办呢?
Sam:我们并没有那种严格的用于快速决策的规则。但是如果大家不得不在那里等待你修复你的问题,那通常说明什么地方出了问题,其他人就不会部署代码。如果你耽误其他人部署代码太长时间的话,那么我们的做法是采取最快的方式,无论哪种方式都行,让大家可以部署代码。所以十分钟过去了你还没有找到问题,但你认为回滚会让工作回到正轨上的话,当然你就要先回滚,然后在你自己的机器上继续找问题。
InfoQ:能讲一下最近的一次故障吗?
Sam:好。比较典型的情况是,我们只会遇到一些很小的问题。比如,有人正在修改一个页面,而此时我们网站上的一个卖家正在修改他有多少东西可以卖。我并不能说这是一个现在正在发生的案例,但是,它的确与我们看到的小故障类似。这个开发人员修改的恰好是这个页面,那么卖家可能就会看到一个错误,但这并不会耽误大家在网站上卖东西。这时我们会认为,问题并不大。这个开发人员去查看一下,然后修复它就行了。通常由于变更很小,所以直接修复,然后推送部署就行了。但要强调的是,如果问题比较严重,那就会回滚。
最近就发生了一个非常严重的问题:我们在数据库中使用完整的 IDs,而这些 IDs 不是自增的。我们没有让数据库来做这种 ID 自增,因为我们所有的数据库都是双主方式 (two masters) 的,即 master-master。所以无法使用自增,因为你不知道哪个 master 是最近一次做自增操作的。所以我们有另外一个服务器,由它来专门提供这些自增数字。这个服务器也有两个。其中一个只产生奇数,而另一个只产生偶数。 这就是用户 ID 帐户的来源。
几个月前,这个服务器的数字超过了 2^31,所以溢出了 32 位的整数列,但这也不是太大的问题,因为你可以把它保存在一个 64 位的列中。然而,在代码中的一些地方并没有这么做。但是,大家并没有意识到为什么 ID 突然出现了 64 位。因此,当 ID 太大时,一些相关的特性就无法工作了。
这些地方的代码无法得到合理的 ID,但仍旧试图把它保存到数据库上,但数据库无法保存它。当这个问题发生时,我们立刻要求大家不要再推送代码了。我们只花了大约一分钟就找到了这个故障的原因,因为在日志里有很多错误信息,这些信息中能看到那些数字。如果之前你看到过这样的数字,你会就发现这个问题。你能想像得到当看到这样的数字时,那种不祥的感觉。
我们看到后,我们就立刻停止推送,是我们的团队(core platform team)发现它的。尽管我们知道这是一个严重的问题,但我们并不知道有多少数据库表使用了它,因为没有地方可以看到这样的信息。我们召集大家,包括一些运维工程师和我们团队的一些人,到会议里来讨论如何处理这个问题。最后决定相看所有的表,看看到底哪些数据库表的列宽不足。
在数据库里,代码中,以及写的 schemas 中都可能有相关的信息。所以我们只能查看所有的代码,来判断有多少 schemas 中存在这个问题。有些只是代码中有问题,有些只是在数据库中有问题。有些地方则是两方面都有问题。同时,其他人来判断哪些特性受到了牵连。我们从错误日志中有可能发现这些受牵连的特性,也可能从图表上发现。只要发现了,就可以找以相应的配置文件,把这些特性关掉。
一旦我们修改好所有的地方,我们就可以在数据库上运行这些变更,将那些字段改成 64 位,再把关掉的特性开关打开,然后马上监控,看哪些数据还会出异常。整个过程只持续了几个小时,而且只有几个比较小的特性受到了影响。但直到把全部问题都修复了,我们才能知道到底有多少特性受到了影响。因为很多影响不是显而易见的。
InfoQ:这是一个相当快的修复了。
Sam:是的,这是最糟糕的故障之一。我想,在过去的几年里我们并没有出现太多的问题。在过去的几年只,可能停机的时间不超过三小时,停机事件也很少发生。如果真发生了,那绝对是很糟糕的事情。
监控
InfoQ:你们如何做服务器监控?我是指网络性能监控和应用程序的监控。
Sam:对于网络性能,我们用 Cacti,以及我们自己写的一个工具,叫做 FITB,在 Github 上开源了。他会监控所有的 switch,并保持那些度量项数据比较少。它有点儿像 Cacti。但我不记得到底区别在哪里了,因为我很少处理网络度量数据。我们用这两个工具监控不同端口上 switch 的性能。我们在这方面很少遇到问题。网络几乎没有给我们带来任何麻烦。我们的容量目前是足够的。
InfoQ:那么,系统常见的瓶颈在哪里呢?
Sam:更多的是数据库和 memcache。最近,我们在 memcache 上遇到了问题,而且与网络相关。有很多特别关键的数据保存在 memcache 中,很多人都需要用。最近,在美国,我们的流量陡增。好象是在 Cyber Monday(译者注:感恩节假期之后的第一个上班日的网购促销活动),流量非常高,有一个关键数据被访问了很多次,其中的一个 memcache 服务器占满了网络连接。而对这个问题的修复方法只是不再把这个数据放在 memcache 中了。因为根本不需要放在那里,所以我们就把它从那里删除了。但这个修复并不合理,因为直到网卡饱和了,我们才发现。
我们用 Cacti,但我并不知道它的监控频度是多少,也不知道其他人隔多长时间来看一下数据。当然,我们用 Nagios 来监控这个数据,判断事情是否出了问题。
对于客户端数据,我们有自己的一套信号器,记录用户在网站上的操作,这些信息会保存到我们的大数据栈上。对于用户行为的监控,我们使用 Hadoop 集群,来处理分析这些信号量,比如我加载了主页,点击了一个物品,并买下了它。我们会观察分析这类的操作模式。我们也会将前端 JavaScript 的错误记录到后台。我们既有服务器端的性能测试,也有 web 页面的测试,并经常运行它们。我们有一个 web 页面测试集群,用它来判断性能是否达到许可水平。
我们也会将应用程序的度量数据与用户的行为做关联。所以我们会度量有多少人在网站上查询买品列表,以便来衡量用户是如何使用我们的网站的。如果这样的操作不再发生了,那么也许我们应该在用户体验方面要改点儿什么东西了。我们也会保存我们帮助论坛的那些图片。这样的话,如果越来越多的人在帮助论坛中请求帮助的话,那么很可能我们破坏了什么功能。当然,还有来自客服的信息,我们常常和他们沟通来尝试判断用户遇到的问题。
查看英文原文: Interview: Sam Haskins from Etsy on Code Deployment, Monitoring and Failure Procedures
感谢黄冬对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。
评论