原文:The AdStage Migration From Heroku To AWS
作者:G Gordon Worley III
翻译:黑色巧克力
2013年的秋天,当我加入AdStage时,我们的产品一直运行在Heroku平台。这是很明智的选择,因为它比其它虚拟服务器更易上手而且低开销,对我们的业务发展也足够灵活。后来我们也确实发展了起来。Heroku让我们唯一专注于建立更有吸引力的产品而不被管理基础设施分散注意力,所以到了2015年,我们运行成千上万的容器的同时也跟上了客户的发展。
我们需要所有容器,因为我们在后台看到了许多相同的分段,这些相同的分段让我们的成本和用户数量成线性关系。一个容器每月25美元,包括其他的技术成本,我们在2016年中期就打破了1百万的年度基础设施增长预算,由此形成庞大的COGS比例,需要我们花费一年才能收益平衡。坦白的说,这种情况是不能接受的。工程师团队开会讨论选择时,一些快速统计显示为了Heroku的便利我们每月将支付超过1万美元,超过直接在AWS上的同等资源消耗。如果我们迁移出Heroku,这相当于一个工程师在基础设施上的全职工作,因此我被任命为第一位运营总监,并作为公司迁移至AWS的先锋。
说来时机也很成熟,因为Heroku已成为我们的最大限制。 我们的工程师团队过去采用看板的方法,所以理论上从概念构想到实施完成,我们有一套常用的模式流。尽管当时我们正在产生大量的在制品,它们经常堵塞我们的管道释放。通过QA移除在制品工作缓慢,并且经常因为bug修复返工。太多“工作在我的机器”的事情,一旦暴露在我们的分段平台环境就会失败。因为AdStage是一个写在不同的技术堆栈中的复杂的相互依存的服务,所以很难保证每个开发人员的工作站同步到生产环境,这也使得部署到分段测试和生产变成一个缓慢且需要大量的人工干预的过程。不过,在这个问题上我们别无选择,因为我们要将每个服务作为独立的Heroku应用程序部署,这限制了我们使用自动化。因此我们迫切需要找到一种替代方案,使我们能够自动化部署,并让开发人员更早地访问可靠的测试环境。
所以为了迁移出Heroku能缩减成本,我们还需要清除QA的限制,此外只要运行的已有服务器代码改动量最小,我还拥有主导规划AWS部署的自由,对此我增加了一些必要条件:
简单的系统管理
过去我使用Chef工具工作,我希望避免频繁从划痕中重建系统易出错的过程,同时通过记录日志和运行命令来更新设备。
无聊的技术
我想采用“无聊的”技术去工作,而不是尝试新的技术解决那些问题。并将精力集中在业务逻辑上而不是我们的基础设施上。
零宕机
部署在Heroku上可能引起用户经历“光点”,因为需要的运行时间超过了Heroku允许的连接引流。我想尽可能的消除这些光点。
回滚
如果部署出现了错误,我希望重新来过并保存服务的最近工作版本。
有限的复杂
为了建立和保持基础设备全时段运行,我需要全面了解项目以便适应。
Netflix能在AWS上运行其价值数十亿美元的业务无非是因为亚马逊机器镜像和自动扩展组,我决定采用他们可靠但绝不是“性感”的方法:建立一个机器映像,使用它在自动扩展组中创建实例,把实例放在弹性负载平衡器之后,再将负载平衡器连接到DNS记录就可以访问我们的客户和任何一方。
以此我开始构建我们的AWS部署策略。
当我成为一名系统工程师时,我喜欢在提交设计之前花大量时间思考一些测试假设。Rich Hickey称之为吊床驱动开发。
我们办公室没有吊床,所以我使用相扑躺椅代替。
2016年春天过去的几个月,对于AWS部署系统的基础我想了又想,最后整理出了它的大体结构:
其核心我们称之为AdStage统一镜像,从开发到分段测试再到生产,这些设备镜像用来为所有环境中的所有服务创建实例。镜像中有我们回购协议的副本和运行它们所需的依赖。根据一些实例标签的价值,它们可以在不同模式中出现以体现其用途。
实例出现在“审查”模式时,例如,所有的服务和它们依赖的数据库在实例中一起运行然后彼此通信。这允许开发工程师和QA访问我们全堆栈运行任意版本代码的独立版本。无论在审查盒做什么都不会影响测试或生产,而且不与其他审查盒关联便能完整的消除旧的QA分段限制。额外的好处是,一旦审查盒通过QA便可以成像,且图像可以部署到生产。
一项任务当实例开始于“分段测试”和“生产”模式时,表示对应的服务应当运行。这也由继承于自动扩展组的实例标签所决定,好让我们引入实例队列运行那些将来自客户的负载分散出去的相同代码。因为自动扩展组服务于web请求,所以它们连接到将请求均匀分布于服务器上的弹性负载平衡器。负载平衡器固定的点下可以顺利交换实例,完成零宕机的部署,也让回滚变得非常简单,就像在备用镜像中保留旧版本以便进行交换一样。
我们使用的AWS资源并不完全协调,因此,我们开发了一个调用AWS API的Tuby Thor应用去协调。它负责开启审查盒,建立镜像,然后部署镜像至分段测试和生产环境。它会自动验证在切换负载均衡器至新的版本之前部署是否正在进行,倘若部署完成后检测到问题将会提示回滚。它还采用一些巧妙的方法来协调多个部署和锁定关键资源,防止多个工程师破坏彼此的部署,任何人都可以开始部署,但它们遇到冲突会停止。
我们的预想是:成像实例可以允许简单的系统管理,设置虽然单调但应用广泛,内在的部署过程保持零宕机,部署自动化支持回滚,而且也不复杂,只有不到1500行代码。正因为它解决了QA的限制,在运行开销上我们预计将节省10000美元,所有这些都取决于能否按计划动态迁移Heroku至AWS。
2016年的7月的旧金山是特别的,大多数雾霭和寒冷的天气我都在室内工作,但从办公室出来的街道上能看到那些穿着不合时宜的短裤在Dragon’s Gate自拍的游客,他们冻的直颤抖。所有着手从HeroKu迁移至AWS的事情也是一样,我们有很多艰难的工作要去做。
客户依靠我们管理他们的广告活动,实现广告预算自动化,并上报广告表现情况。当我们表现疲软的时候,他们又回到了通过网络接口手动创建和更新广告的黑暗时代。当我们切换到AWS时,他们负担不起我们的线下活动,所以我们不得不进行动态迁移。或者至少以合理的方式迁移。
在一个星期六的早上,我们实现了一周代码冻结和1小时代码发现窗口,那时AdStage进入维护模式,而我在运行时不能轻易的切换数据库和其他服务。迁移准备过程中,我们已经完成了分段测试系统的迁移,并且编写了割接生产环境的手册。我采用代码冻结花费一个星期对AWS匹配HeroKu部署进行调整。周六早上一切似乎都很好,我们停机割接数据库,然后重启AdStage。我花了一整天的时间观察显示器,待在键盘前以防万一出错,但什么事也没有。那天晚上我觉得一切状态良好便去睡觉了。
度过一个慵懒的周日早晨之后,我在下午陆续收到一些提醒显示导入器正在备份。当我们着手调查时问题很快就变得明显:对比HeroKu,我们确实一定程度上减少了AWS的CPU消耗,尽管拥有名义上更多的计算资源。然而结果是我们跟不上导入速度,每一个小时都在落后。为了防止队列溢出我们必须减少导入的频率,最终不得不一边让AWS运行跟上工作负载一边切回HeroKu应用。这与存钱是相反的。
HeroKu编造了一个美丽的谎言,我们官方只得到每个容器2 ECUs,但因为在HeroKu上的邻居没有使用其全部份额,实际我们得到的是接近6 ECUs。这意味着AWS实例队列是很小的3次,尽管HeroKu的一切都是廉价的。如果有一种方法可以减少更多的实例的话。
这就是我们偶然发现使用现场实例的时机。我们考虑使用现场实例,是因为它们的价格约为按需价格的十分之一,但如果保留价格低于拍卖价格他们可以选择在任何时候终止,所以也有一些风险。幸运的是,这种情况并不常见,而且自动扩展组也会额外代为管理现场实例的复杂性。加上使用按需的实例很容易备份自动扩展组,如果暂时无法获得足够的现场实例来满足我们的需求,简单一个命令即可扩充。我们最终将大约80%的实例队列转化为现场实例,尽管使用的资源是最初预期的3倍,但成本降低到了预定的目标内。
从Heroku切换到AWS进展如此顺利让我们非常吃惊。不过,不要误解我的意思:因为我们已经达到了一定规模,所以这是一件值得做的事情,并且把一些能产生经济效益的基础设施运营放在公司是合理的。如果在运营成本上没有增加至少一个工程师工资的资金,该资金可以通过转换到AWS的成本节省来弥补,如果基础设施没有成为核心竞争力,我们将被Heroku缠住无法前行,人们会觉得从事的工作比公司业务更有意义。只是因为从Heroku迁移到AWS的所带来的经济变化,以及迁移过程本身变成了我们故事的一部分。