Reddit中代码部署方法的演变

图片

我们在Wirex区块链支付服务团队中 ,熟悉不断完善和改进现有技术解决方案的经验。 以下材料的作者讨论了著名的社交新闻平台Reddit的代码部署演变的历史。

“重要的是要遵循您的开发方向,以便能够按时向正确的方向发送。”

Reddit团队一直在部署代码。 开发团队的所有成员定期编写代码,作者自己检查这些代码,并从外部进行测试,以便他可以进行“生产”。 每周我们至少进行200次“部署”,每一次通常少于10分钟。

多年来,提供​​所有这些功能的系统一直在发展。 让我们看看这段时间里发生了什么变化,什么保持不变。

故事的开始:稳定且重复的部署(2007年至2010年)


我们今天拥有的整个系统源于一个种子-一种称为push的Perl脚本。 它是很久以前写的,与Reddit的时代截然不同。 当时我们的整个技术团队非常小,以至于它安静地装入一个小的“会议室”中 。 那时我们没有使用AWS。 该站点只能在有限数量的服务器上工作,并且任何其他容量都必须手动添加。 一切都在一个大型的,整体的Python应用程序r2上运行。

多年来,一件事一直未变。 请求在负载平衡器中分类,并在包含或多或少相同应用程序服务器的“池”中分配。 例如, 主题评论 列表页面由不同的服务器池处理。 实际上,任何r2进程都可以处理任何类型的请求,但是,通过将池划分为多个池,可以保护它们中的每一个免受相邻池中流量突然跳跃的影响。 因此,在流量增加的情况下,故障不会威胁到整个系统,而是会威胁单个池。

图片

目标服务器列表是用推送工具代码手动编写的,并且部署过程适用于整体系统。 该工具遍历服务器列表,通过SSH登录,启动了预定义的命令序列之一,这些命令使用git更新了当前代码副本,并重新启动了所有应用程序进程。 流程的本质(为了使您更容易理解,大大简化了代码):

#            `make -C /home/reddit/reddit static` `rsync /home/reddit/reddit/static public:/var/www/` #    app-        #    ,   foreach $h (@hostlist) { `git push $h:/home/reddit/reddit master` `ssh $h make -C /home/reddit/reddit` `ssh $h /bin/restart-reddit.sh` } 

部署是按顺序进行的,一个服务器又一个服务器。 尽管简单,但该方案具有一个重要的优点:它与“ 金丝雀部署 ”非常相似。 通过将代码部署在多台服务器上并注意到错误,您立即意识到存在错误,可以中断(Ctrl-C)进程并回滚,以免所有请求立即出现问题。 易于部署使检查生产中的东西变得容易并且没有严重后果,如果它们不起作用,则可以回滚。 此外,可以方便地确定是哪个特定的部署导致了错误,具体在哪里以及需要回滚什么。

这样的机制在确保部署期间的稳定性和控制方面做得很好。 该工具运行很快。 一切顺利。

我们的团抵达(2011)


然后我们雇用了更多的人,现在有六个开发人员,而我们新的“会议室”变得更加宽敞 。 我们开始意识到,代码部署过程现在需要更多的协调,尤其是当同事在家工作时。 push实用程序已更新:现在,它宣布了使用IRC聊天机器人的部署开始和结束,该聊天机器人只是坐在IRC中并宣布了事件。 部署期间执行的过程几乎没有发生任何变化,但是现在系统为开发人员完成了所有工作,并将所做的修改告知了其他所有人。

从那时起,在部署工作流中开始使用聊天功能。 关于通过聊天管理部署的讨论当时非常流行,但是,由于我们使用了第三方IRC服务器,因此我们无法在管理生产环境中百分百地信任聊天,因此流程仍处于单向信息流的水平。

随着网站访问量的增长,支持该网站的基础架构也随之增加。 我们不时需要启动一组新的应用程序服务器并将其投入运行。 该过程仍未实现自动化。 特别是,推送中的主机列表仍然需要手动更新。

通常通过一次添加多台服务器来增加池的功能。 结果,在列表中连续运行的推操作设法将更改推到同一池中的整个服务器组,而又不影响其他服务器,也就是说,池没有多样化。

图片

UWSGI用于控制工作进程,因此当我们向应用程序发出重新启动命令时,它会立即杀死所有现有进程,并用新进程替换它们。 新流程需要一些时间来准备处理请求。 在意外重启位于同一池中的一组服务器的情况下,这两种情况的结合严重影响了该池处理请求的能力。 因此,我们将代码安全部署到所有服务器的速度受到限制。 随着服务器数量的增加,整个过程的持续时间也随之增加。

回收仪器部署(2012)


我们彻底重新设计了部署工具。 尽管进行了彻底的更改,尽管它的名称保持不变(推送),但这一次它是用Python编写的。 新版本进行了一些重大改进。

首先,他从DNS中获取了主机列表,而不是从代码中硬编码的序列中获取了主机列表。 这仅允许更新列表,而无需更新推送代码。 服务发现系统的起源已经出现。

为了解决连续重启的问题,我们在部署之前对主机列表进行了重新排序。 改组降低了风险,并加快了流程。

图片

原始版本每次都随机地对列表进行混洗,但是,这使得很难快速回滚,因为每次第一组服务器的列表都不相同。 因此,我们纠正了混合:现在它生成了一定的顺序,可以在回滚后的重复部署期间使用该顺序。

另一个小而重要的变化是不断部署某些固定版本的代码。 该工具的先前版本始终会更新目标主机上的master分支,但是如果master在部署期间由于某人错误地启动了代码而进行了更改,会发生什么情况? 部署某些给定的git版本而不是按分支名称进行调用,可以确保在每个生产服务器上使用相同版本的代码。

最后,新工具区分了其代码(它主要与主机列表配合使用,并通过SSH访问它们)和在服务器上执行的命令。 它仍然非常依赖于r2的需求,但是具有类似API原型的东西。 这使r2可以遵循其自己的部署步骤,这使得滚动更改变得更加容易并释放了流程。 以下是在单独的服务器上执行的命令的示例。 同样,该代码也不是确切的代码,但是总体而言,此序列很好地描述了r2工作流程:

 sudo /opt/reddit/deploy.py fetch reddit sudo /opt/reddit/deploy.py deploy reddit f3bbbd66a6 sudo /opt/reddit/deploy.py fetch-names sudo /opt/reddit/deploy.py restart all 

特别值得一提的是fetch-names:该指令是r2独有的。

自动缩放(2013)


然后,我们最终决定切换到具有自动缩放功能的云(整个单独帖子的主题)。 这样一来,在网站未满载流量的那一刻,我们可以节省一大笔钱,并自动增加容量以应对任何急剧增加的请求。

以前的增强功能(自动从DNS加载主机列表)使这种过渡成为理所当然的事情。 主机列表更改的频率比以前更多,但是从部署工具的角度来看,这没有任何作用。 最初作为一项质量改进而引入的更改已成为运行自动缩放所需的关键组件之一。

但是,自动缩放导致一些有趣的临界情况。 需要控制发射。 如果服务器在部署期间正确启动,会发生什么情况? 我们需要确保每台正在运行的新服务器都检查新代码的可用性,并采用新代码(如果有)。 我们不能忘记部署时服务器会脱机。 该工具需要变得更聪明,并学会确定服务器已脱机,这是该过程的一部分,而不是由于部署期间发生的错误而导致。 在后一种情况下,他必须大声警告所有与该问题有关的同事。

同时,由于各种原因,我们从uWSGI切换到Gunicorn。 但是,从该职位的主题来看,这种转变并没有导致任何重大变化。

所以它工作了一段时间。

服务器过多(2014)


随着时间的流逝,服务高峰流量所需的服务器数量不断增加。 这导致部署需要越来越多的时间。 在最坏的情况下,一次正常部署需要大约一个小时的时间-结果很差。

我们重写了该工具,以便它可以支持与主机并行工作。 新版本称为Rollingpin 。 旧版本需要大量时间来初始化ssh连接并等待所有命令完成,因此在合理的限制内进行并行化可以加快部署速度。 部署时间再次减少到五分钟。

图片

为了减少同时重新启动多台服务器的影响,该工具的混合组件变得更加智能。 他没有对列表进行盲目整理,而是对服务器池进行了排序,以使一个池中的主机尽可能远

新工具最重要的变化是, 部署工具和每台服务器上的工具之间API定义得更加清晰,并且与r2的需求分开了。 最初,这样做是出于使代码更加面向开放源代码的愿望,但是不久之后,这种方法在另一种方法中非常有用。 以下是带有远程启动的API命令选择的示例部署:

图片

太多人(2015)


突然之间,事实证明,已经有很多人在研究r2。 这很酷,同时也意味着会有更多的部署。 一次遵守一个部署的规则变得越来越困难。 开发人员必须就发行代码的程序达成共识。 为了优化这种情况,我们在聊天机器人中添加了另一个元素来协调部署队列。 工程师要求部署保留并收到它,或者他们的代码“排队”。 这有助于简化部署,那些想要完成部署的人可以冷静地等待轮到他们。

随着团队的成长,另一个重要的补充是在一个地方跟踪部署。 我们更改了部署工具,以将指标发送到Graphite。 这使跟踪部署和度量标准更改之间的相关性变得容易。

许多(两项)服务(也是2015年)


突然之间,在线发布第二项服务的时刻到了。 它是网站的移动版本,具有完全不同的堆栈,自己的服务器和构建过程。 这是拆分部署工具API的首次真实测试。 此外,它还具有为每个项目在不同“位置”进行组装的所有阶段的能力,从而使他能够承受负载并应对同一系统中两项服务的维护。

25服务(2016)


在接下来的一年中,我们见证了团队的快速扩张。 而不是两个服务,而是出现了十二打,而不是两个开发团队,而是十五个。 大部分服务都建立在我们的后端框架Baseplate或类似于移动Web的客户端应用程序上。 部署背后的基础架构对于每个人都是相同的。 很快,许多其他新服务将在线发布,这在很大程度上归因于pin面杖的多功能性。 它使您可以使用人们熟悉的工具简化新服务的启动。

安全气囊(2017)


随着整体中服务器数量的增加,部署时间也增加了。 我们希望大幅增加并行部署的数量,但这将导致应用程序服务器同时重启太多。 这样的事情当然会导致吞吐量下降,并且由于剩余服务器的过载而导致服务传入请求的能力下降。

Gunicorn的主要流程使用与uWSGI相同的模型,同时重新加载所有工作程序。 新的辅助进程在完全加载之前无法处理请求。 我们的整体发射时间为10到30秒。 这意味着在这段时间内我们将完全无法处理请求。 为了解决这种情况,我们用Stripe的Einhorn工作经理替换了主要的gunicorn流程, 同时保留了Gunicorn HTTP堆栈和WSGI容器 。 在重新引导期间,Einhorn会创建一个新工作线程,等待直到准备就绪,摆脱一个旧工作线程,然后重复该过程,直到更新完成。 这将产生一个安全气囊,并使我们能够在部署期间将带宽保持在一定水平。

新模型产生了另一个问题。 如前所述,用新的完全完成的工人替换一名工人最多需要30秒钟。 这意味着,如果代码中存在错误,则不会立即弹出该错误并设法在检测到该错误之前将其部署到许多服务器。 为防止这种情况,我们引入了一种机制,用于阻止将部署过程过渡到新服务器,该机制在重新启动所有工作进程之前一直有效。 它的实现很简单-通过轮询einhorn的状态并等待所有新员工的准备就绪。 为了保持相同的速度,我们扩展了并行处理服务器的数量,在新条件下这是完全安全的。

这种机制使我们可以同时部署到大量计算机上,并且考虑到检查错误的额外暂停时间,可以将大约800台服务器的部署时间减少到7分钟。

回望过去


此处描述的部署基础结构是经过多年不断改进而诞生的产品,而不是一次性的集中精力。 在折衷方案的早期阶段做出的一次决策的回响仍然在当前的系统中让人感觉到,而且在所有阶段都是如此。 这种进化方法各有利弊:在任何阶段都需要付出最小的努力,但是,迟早会有陷入停顿的风险。 重要的是要遵循您的开发方向,以便能够按时向正确的方向发送。

未来


Reddit基础架构应随时准备为团队的成长和发布新事物提供持续的支持。 公司的增长速度比以往任何时候都要快,而且我们正在做比以前更大的有趣的大型项目。 我们今天面临的问题具有双重性质:一方面,需要提高开发人员的自主性,另一方面,需要保持生产基础架构的安全性并改善安全气囊,这使开发人员可以快速而自信地执行部署。

图片

Source: https://habr.com/ru/post/zh-CN404577/


All Articles