
作者丨Evan Limanto
译者丨平川
策划丨赵钰莹
本文作者地点的公司 Plaid 是一家金融科技公司,该公司搭建了一个技能渠道,使运用程序可以与用户的银行账户树立联络。跟着公司的开展,基础设施规划在不断扩大。现在,这家公司运转着 20 多个内部服务,每天在中心服务上布置 50 多个代码提交。因而,最小化布置时刻关于最大化迭代速度至关重要,一个快速的布置进程可以敏捷进行 Bug 修正并运转平稳的接连布置体系。
几个月前,咱们注意到,银行集成服务布置缓慢正在影响团队发布代码的才干。工程师要花至少 30 分钟才干经过多个过渡环境和出产环境构建、布置和监督改变,这将耗费很多名贵的工程时刻。跟着团队越来越大,咱们每天发布的代码也越来越多,这一点变得越来越不行承受。
虽然咱们方案完成长时刻改善,比方将依据 Amazon ECS 服务的基础设施迁移到 Kubernetes 上,可是,为了在短期内进步迭代速度,有必要快速处理下这个问题。因而,咱们决议实践自定义的“快速布置”机制。
1
Amazon ECS 布置的高推迟
咱们的银行集成服务由 4000 个 Node.js 进程组成,这些进程运转在专用的 Docker 容器上,这些容器保管并布置在 Amazon 的容器编列服务 ECS 上。在剖析了咱们的布置进程之后,咱们将添加的布置推迟归结到三个不同的组件上:
发动使命会导致推迟。除了运用程序发动时刻之外,ECS 健康检查也会导致推迟,它决议容器何时准备好开端处理流量。操控这个进程的三个参数是 interval、retry 和 startPeriod。假如没有对健康检查进行细心调优,容器或许会卡在“发动”状况,即便它们现已准备好为流量服务。
封闭使命会导致推迟。当咱们运转 ECS 服务更新时,一个 SIGTERM 信号被发送到一切正在运转的容器。为了处理这个问题,咱们在运用程序代码中运用了一些逻辑,以便在彻底封闭服务之前占用现有资源。
咱们发动使命的速度约束了布置的并行性。虽然咱们将 MaximumPercent 参数设置为 200%,可是 ECS start-taskAPI 调用的硬约束是每个调用只能履行 10 个使命,并且速度有限。咱们需求调用 400 次才干将一切容器投入出产。
2
办法探究
咱们考虑并实验了一些不同的潜在处理方案,以逐渐完成整体目标:
削减出产中运转的容器总数。这当然是可行的,但它涉及到对服务架构进行严重修正,以使其可以处理相同的恳求吞吐量,在进行这样的修正之前,还需求进行更多研讨。
经过修正健康检查参数来调整 ECS 装备。咱们测验经过削减 interval 和 startPeriod 的值来加强健康检查,可是 ECS 在发动时将健康的容器过错地标记为不健康,导致咱们的服务永久无法彻底稳定在 100% 健康状况。因为底子问题(ECS 布置缓慢)依然存在,对这些参数进行迭代是一个缓慢而吃力的进程。
在 ECS 集群中发动更多实例,以便可以在布置期间一同发动更多使命。这样做可以削减布置时刻,但不会削减太多。从长远来看,这也不划算。
经过重构初始化和关机逻辑优化服务重启时刻。只需求做一些小小的修正,咱们就可以在每个容器中节约大约 5 秒的时刻。
虽然这些更改将整体布置时刻削减了几分钟,可是咱们依然需求将时刻进步至少一个数量级,才干以为问题已处理。这将需求一个底子不同的处理方案。
3
开始处理方案:运用 Node Require Cache“热重载”运用程序代码
Node require cache 是一个 Javascript 目标,它依据需求缓存模块。这意味着屡次履行 require(‘foo’) 或 import * as foo from 'foo’只会在第一次时恳求 foo 模块。奇特的是,删去 require cache 中的条目(咱们可以运用大局 require.cache 目标拜访)将迫使 Node 在下次导入模块时从磁盘从头读取该模块。
为了绕过 ECS 布置进程,咱们测验运用 Node 的 require cache 在运转时履行运用程序代码的“热重载”。一旦接纳到外部触发(咱们将其完成为银行集成服务上的 gRPC 端点),运用程序将下载新代码来替换现有的构建,铲除 require cache,然后强制从头导入一切相关模块。经过这种办法,咱们可以消除 ECS 布置中存在的大部分推迟,优化整个布置进程。
在 Plaiderdays (咱们的内部黑客马拉松)期间,来自不同团队的一组工程师聚在一同,为咱们所谓的“快速布置”完成了一个端到端的概念验证。当咱们一同设法构建一个原型时,有一件事好像出了问题:假如下载新构建的 Node 代码也企图使失效缓存,那么下载器代码自身将怎么从头加载就不清楚了。(有一种办法可以处理这个问题,便是运用 Node EventEmitter ,可是会给代码添加相当大的复杂性)。更重要的是,还存在运转未同步代码版别的危险,这或许导致运用程序意外失利。
因为咱们不肯意在银行集成服务的可靠性上退让,这种复杂性需求从头考虑“热重载”办法。
4
终究处理方案:从头加载进程
在曩昔,为了在一切服务中运转一系列一致的初始化使命,咱们编写了自己的进程封装器,它的称号十分恰当,叫做 Bootloader。Bootloader 的中心包括设置日志管道、转发信号和读取 ECS 元数据的逻辑。每个服务都是经过将运用程序可履行文件的途径以及一系列标志传递给 Bootloader 来发动的,这些文件在履行初始化进程之后会作为子进程履行。
咱们没有铲除 Node 的 require cache,而是在下载预期的布置构建后,运用特别的退出代码来调用 process.exit 完成服务更新。咱们还在 Bootloader 中完成了自定义逻辑,以触发运用此代码退出的任何子进程的进程重载。与“热重载”办法相似,这使咱们可以绕过 ECS 布置的本钱并快速引导新代码,一同防止“热重载”的圈套。此外,Bootloader 层的这种“快速布置”逻辑答应咱们将其推行到在 Plaid 运转的任何其他服务。
下面是终究处理方案:
Jenkins 布置管道向银行集成服务的一切实例发送 RPC 恳求,指示它们“快速布置”特定的提交散列。
运用程序接纳 gRPC 恳求进行快速布置,并依据接纳到的提交散列从 Amazon S3 下载构建好的压缩包。然后,它替换文件体系上的现有构建,并运用 Bootloader 辨认的特别退出代码退出。
Bootloader 看到运用程序运用这个特别的“Reload”退出代码退出,然后从头发动运用程序。
服务运转新的代码。
下面这张图简略说明晰这个进程。
5
成果
咱们可以在 3 周内交给这个“快速布置”项目,并将 90% 出产容器的布置时刻从 30 多分钟削减到 1.5 分钟。
上图显现了咱们为银行集成服务布置的容器数量(按提交表明为不同的色彩)。假如注意下黄线,就可以看到它在 12:15 左右增加趋于平稳,这代表咱们的容器长尾依然在占用资源。
这个项目极大进步了 Plaid 集成作业的速度,答应咱们更快地发布特性及进行 Bug 修正,并将糟蹋在上下文切换和监督仪表板上的工程时刻最小化。这也证明晰咱们的工程文明,即经过黑客马拉松得来的主意完成具有实质性影响的项目。
https://blog.plaid.com/how-we-reduced-deployment-times-by-95/
点个在看少个 bug
