速来报名!AICon北京站鸿蒙专场~ 了解详情
写点什么

如何将代码部署时间减少 95%?

  • 2019-09-04
  • 本文字数:2664 字

    阅读完需:约 9 分钟

如何将代码部署时间减少95%?

本文作者所在的公司 Plaid 是一家金融科技公司,该公司搭建了一个技术平台,使应用程序能够与用户的银行账户建立联系。随着公司的发展,基础设施规模在不断扩大。目前,这家公司运行着 20 多个内部服务,每天在核心服务上部署 50 多个代码提交。因此,最小化部署时间对于最大化迭代速度至关重要,一个快速的部署过程能够迅速进行 Bug 修复并运行平稳的连续部署系统



几个月前,我们注意到,银行集成服务部署缓慢正在影响团队发布代码的能力。工程师要花至少 30 分钟才能通过多个过渡环境和生产环境构建、部署和监视变更,这将消耗大量宝贵的工程时间。随着团队越来越大,我们每天发布的代码也越来越多,这一点变得越来越不可接受。


虽然我们计划实现长期改进,比如将基于 Amazon ECS 服务的基础设施迁移到 Kubernetes 上,但是,为了在短期内提高迭代速度,有必要快速解决下这个问题。因此,我们决定实践自定义的“快速部署”机制。

Amazon ECS 部署的高延迟

我们的银行集成服务由 4000 个 Node.js 进程组成,这些进程运行在专用的 Docker 容器上,这些容器托管并部署在 Amazon 的容器编排服务ECS上。在分析了我们的部署过程之后,我们将增加的部署延迟归结到三个不同的组件上:


  • 启动任务会导致延迟。除了应用程序启动时间之外,ECS 健康检查也会导致延迟,它决定容器何时准备好开始处理流量。控制这个过程的三个参数是 interval、retry 和 startPeriod。如果没有对健康检查进行仔细调优,容器可能会卡在“启动”状态,即使它们已经准备好为流量服务。

  • 关闭任务会导致延迟。当我们运行ECS服务更新时,一个 SIGTERM 信号被发送到所有正在运行的容器。为了处理这个问题,我们在应用程序代码中使用了一些逻辑,以便在完全关闭服务之前占用现有资源。

  • 我们启动任务的速度限制了部署的并行性。尽管我们将MaximumPercent参数设置为 200%,但是 ECS start-taskAPI 调用的硬限制是每个调用只能执行 10 个任务,而且速度有限。我们需要调用 400 次才能将所有容器投入生产。

方法探索

我们考虑并试验了一些不同的潜在解决方案,以逐步实现总体目标:


  • 减少生产中运行的容器总数。这当然是可行的,但它涉及到对服务架构进行重大修改,以使其能够处理相同的请求吞吐量,在进行这样的修改之前,还需要进行更多研究。

  • 通过修改健康检查参数来调整 ECS 配置。我们尝试通过减少 interval 和 startPeriod 的值来加强健康检查,但是 ECS 在启动时将健康的容器错误地标记为不健康,导致我们的服务永远无法完全稳定在 100%健康状态。由于根本问题(ECS 部署缓慢)依然存在,对这些参数进行迭代是一个缓慢而费力的过程。

  • 在 ECS 集群中启动更多实例,以便可以在部署期间同时启动更多任务。这样做可以减少部署时间,但不会减少太多。从长远来看,这也不划算。

  • 通过重构初始化和关机逻辑优化服务重启时间。只需要做一些小小的修改,我们就能够在每个容器中节省大约 5 秒的时间。


尽管这些更改将总体部署时间减少了几分钟,但是我们仍然需要将时间提高至少一个数量级,才能认为问题已解决。这将需要一个根本不同的解决方案。

初步解决方案:利用 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,但是会给代码增加相当大的复杂性)。更重要的是,还存在运行未同步代码版本的风险,这可能导致应用程序意外失败。


由于我们不愿意在银行集成服务的可靠性上妥协,这种复杂性需要重新考虑“热重载”方法。

最终解决方案:重新加载进程

在过去,为了在所有服务中运行一系列统一的初始化任务,我们编写了自己的进程封装器,它的名称非常贴切,叫做 Bootloader。Bootloader 的核心包含设置日志管道、转发信号和读取 ECS 元数据的逻辑。每个服务都是通过将应用程序可执行文件的路径以及一系列标志传递给 Bootloader 来启动的,这些文件在执行初始化步骤之后会作为子进程执行。


我们没有清除 Node 的 require cache,而是在下载预期的部署构建后,使用特殊的退出代码来调用 process.exit 实现服务更新。我们还在 Bootloader 中实现了自定义逻辑,以触发使用此代码退出的任何子进程的进程重载。与“热重载”方法类似,这使我们能够绕过 ECS 部署的成本并快速引导新代码,同时避免“热重载”的陷阱。此外,Bootloader 层的这种“快速部署”逻辑允许我们将其推广到在 Plaid 运行的任何其他服务。


下面是最终解决方案:


  • Jenkins 部署管道向银行集成服务的所有实例发送 RPC 请求,指示它们“快速部署”特定的提交散列。

  • 应用程序接收 gRPC 请求进行快速部署,并根据接收到的提交散列从Amazon S3下载构建好的压缩包。然后,它替换文件系统上的现有构建,并使用 Bootloader 识别的特殊退出代码退出。

  • Bootloader 看到应用程序使用这个特殊的“Reload”退出代码退出,然后重新启动应用程序。

  • 服务运行新的代码。


下面这张图简单说明了这个过程。


结果

我们能够在 3 周内交付这个“快速部署”项目,并将 90%生产容器的部署时间从 30 多分钟减少到 1.5 分钟。



上图显示了我们为银行集成服务部署的容器数量(按提交表示为不同的颜色)。如果注意下黄线,就可以看到它在 12:15 左右增长趋于平稳,这代表我们的容器长尾仍然在占用资源。


这个项目极大提高了 Plaid 集成工作的速度,允许我们更快地发布特性及进行 Bug 修复,并将浪费在上下文切换和监视仪表板上的工程时间最小化。这也证明了我们的工程文化,即通过黑客马拉松得来的想法实现具有实质性影响的项目。


原文链接:


How We Reduced Deployment Times by 95%


2019-09-04 10:118307
用户头像

发布了 741 篇内容, 共 480.9 次阅读, 收获喜欢 1549 次。

关注

评论

发布
暂无评论
发现更多内容

一文带你看懂分布式软总线在家庭场景的应用

HarmonyOS开发者

HarmonyOS

元宇宙到家,那些「聪明」的设计工具

Zilliz

网络安全好学吗?手把手教你学metasploit信息收集 网络安全工程师学习资料汇总

学神来啦

大数据开发之Spark Shuffle 原理分析

@零度

大数据 spark

尚硅谷Docker与微服务实战教程发布

@零度

大数据 dokcer

在线JSON转XML工具

入门小站

工具

青藤:一招制敌!微隔离,让勒索软件不再横行

青藤云安全

工作中遇到的50个JavaScript的基础知识点

Sunshine_Lin

面试 前端 进阶 基础

据说有人面试栽在了Thread类的stop()方法和interrupt()方法上

华为云开发者联盟

高并发 crud Thread类 stop interrupt

运维工程师必备利器|一招实现运维智能化!

云智慧AIOps社区

运维 AIOPS 运维工程师 基础知识 自动化运维

译文丨伯克利对serverless的看法:简化云编程

华为云开发者联盟

Serverless 云编程 伯克利 无服务器计算 云函数

前端开发代码区域规范分享

@零度

前端开发 代码规范

vscode中Tasks及Emmet的应用

编程江湖

vscode

Linux之文件属性详解

入门小站

Linux

Towhee,开源的 embedding 框架与社区

Zilliz

数据库 开源 向量检索

自动驾驶训练如火如荼,网络带宽跟不上怎么破?

焱融科技

人工智能 自动驾驶 云计算 高性能 文件存储

利用闭包实现自定义等待方法

FunTester

多线程 并发测试 闭包 FunTester 自定义等待

开源走向世界(上):开源构建全球化的舞台丨BDTC 2021

PingCAP

[转]注释驱动的 Spring cache 缓存介绍

kimmking

java开发之内存模型面试分享

@零度

JAVA开发 Java内存模型

java学习中cookie原理

编程江湖

java 编程

ADmobile首席架构师王威:广告业务云上运维最佳实践

阿里云弹性计算

阿里云 弹性计算 年度峰会

Apache Oozie学习笔记(一)

恒生LIGHT云社区

大数据 hadoop 工作流 调度

web技术分享| web的白板工具栏封装

anyRTC开发者

前端 Web 音视频 视频会议 白板

深入解析QUIC协议

拍乐云Pano

WebRTC RTC QUIC QUIC协议

如何使用JDBC API操作数据库

编程江湖

JDBC

Avue中如何对option中属性动态赋值

泉城老铁

前端 avue

Avue复选框动态赋值不能渲染问题解决方式

泉城老铁

前端 avue

阿里云刘强:无影云电脑构建云上安全办公室

阿里云弹性计算

弹性计算 年度峰会 无影云电脑

湖仓一体天花板,大数据一站式SQL分析技术实践

华为云开发者联盟

大数据 HetuEngine 湖仓一体 SQL分析 华为云FusionInsight

CI/CD制作流程

wong

Docker jenkins ansible kubenetes

如何将代码部署时间减少95%?_文化 & 方法_Evan Limanto_InfoQ精选文章