Dark是如何在50毫秒内完成代码部署的?


决定技术公司成长速度的一个最重要因素就是开发人员迭代的速度。但实际的应用更新却与这个速度相冲突。要求应能够实时更新,没有停机或维护窗口,而实时部署更新却非常困难,即使是小型应用团队,也需要复杂的CD(连续交付)管道。

CD管道的速度决定了开发迭代的速度,但CD管道往往都是独特、脆弱和缓慢的,需要消耗很多时间来构建和管理,许多公司都愿意聘请专业DevOps团队来维护CD管道。但即便如此,管理最佳CD的部署时间也需要5-10分钟左右。很多情况下,单个部署都可能会花费几小时。

但在Dark中,代码部署只需要50毫秒。没错,就是50毫秒!这是因为Dark是为CD而生,它包括了编程语言、编辑器和基础设施等全方位套件。包括开发语音本身在内的每个组件都是围绕安全、即时部署而设计构建。

为何CD管道会如此之长?

举个例子,我们有一个Python写的Web应用程序,并且已经构建了一个不错的CD管道。对于熟悉这个项目的开发人员来说,部署一个小的变更可能的过程是:

生成变更

  • 在Git创建一个新的分支
  • 在对应的功能标志下生成变更
  • 运行单元测试,校验变更


推送变更申请

  • 提交变更
  • 将变更代码推送到远端代码库(GitHub)
  • 生成一个推送请求
  • 后端CI自动化代码封装
  • 代码审核
  • 在此过程中,可能存在多次代码审核和代码封装
  • 将变更分支合并到主代码包


主节点的CI运行过程

  • 通过npm完成前端依赖包安装
  • 构建/优化HTML+CSS+JS环境资产
  • 运行前端单元/功能测试
  • 通过PyPI安装Python的依赖包
  • 运行后端单元/功能测试
  • 分别对前后端环境运行集成测试
  • 将前端更新后内容推送到CDN
  • 为Python程序构建容器
  • 注册新构建的容器
  • 更新Kubernetes清单


更新代码包

  • Kubernetes进行新旧容器的实例切换
  • Kubernetes等待新容器实例变成健康可用状态
  • Kubernetes将可对外服务的容器实例加入到HTTP负载均衡器
  • Kubernetes等待旧容器实例退出服务
  • Kubernetes关闭被替换旧容器实例
  • Kubernetes不断重复以上步骤,直到全部容器实例替换完毕


开放新功能开发

  • 开放开发或测试人员的流量,观察确认正常
  • 开放10%的正式用户流量,观察性能和业务指标
  • 开放50%的正式用户流量,观察性能和业务指标
  • 开放100%的正式用户流量,观察性能和业务指标
  • 最终确认后,移除旧的代码和功能标志


根据所使用的工具、语言和面向服务的体系结构,会对应执行不同的配置,但以上的步骤非常具有代表性。其中还没有列出DB迁移的部署步骤,因为那个通常需要详细的计划,我将在本文后续讨论的Dark解决方案将会谈到这部分。

而且,这个CD管道中还包含很多额外的步骤,这些步骤中,大多数要么运行时间长,失败后再引入临时环境,要么可能会导致正在运行生产环境的崩溃。这都是由于CD管道具有临时性和脆弱性特征。可能会由于Dockerfile中的某些配置问题导致无法部署代码,或相关联的十几项服务中有一个服务出现了异常,甚至是懂构建失败的核心人员正在休假等原因,导致我们大多数人不得用几天的时间来处理这些问题。

更为糟糕的是,管道中有许多步骤并没有任何价值,仅仅是通过代码为用户打开功能而已,而现在的方式是使用独立的功能标志。因此,新旧代码替换部署步骤中,这些步骤只会增加风险概率。

当然,上述过程已经是一个非常先进的CD管道。构建这个项目的团队在这个管道上投入了大量的时间精力和金钱,来获得快速部署的能力。大多数其他的CD管道会更慢和更加脆弱。

Dark的CD持续交付设计

对于Dark来说,CD持续交付是不可或缺的,甚至在最初它就被设计成亚秒级部署。我们深入地研究CD管道,删除每一个可能简化的步骤,并优化余下的步骤,使它们能够全球级通用。下面将阐述我们如何删除和优化传统CD管道的步骤。

Deployless

第一个也是最重要的决定就是让Dark成为“Deployless”(致谢Jessie Frazelle,他在Reykjavik未来软件开发会议上创造了这个术语)。Deployless意味着您导入的任何内容都可以立即部署并立即在运行环境中生效。但这并不等于允许发布错误的或不完整的代码(本文后续的安全设计中讨论)。

在演示Dark时,我们经常被问到如何让部署速度如此之快?这在我看来,是一个奇怪的问题。我怀疑提问的人,可能认为我们解锁了某些令人难以置信的先进技术,从而可以在50毫秒内进行差异编码、编译、容器化构建、VM打包和冷启动容器等等。其实这也是有可能的。不过我走了相反的思路,我们构建了一个无需做任何类似事情的定制化部署引擎。

Dark在云中运行着解释器,所以当您在函数或HTTP/事件处理中编写新代码时,我们就向服务器发送一个抽象语法树的差异数据(新代码在Dark底层编辑器和服务器上的表示形式),然后在收到提交请求时运行该代码。所以部署只是一个非常小的数据库写操作,同时还具有即时性和原子性。我们的部署速度如此之快,就是因为我们将部署缩减在尽可能小的范围内。

未来,Dark的目标是成为一个基础架构编译器,为用户应用创建并运行一个性能和可靠性都很棒的基础架构。当我们朝这个方向努力时,也将一直保持着即时性部署的特性。

确保部署安全

结构编辑器

Dark中的代码是在Dark编辑器中编写的,结构化编辑器会禁止语法错误。事实上,Dark根本没有解析器,当您输入代码时,编辑器直接操作AST,类似于Paredit、Sketch-n-Sketch、Tofu、Prune和MPS。

在Dark中,不完整代码的每个状态都有一个有效的执行语义,类似于Hazel中的类型化漏洞。例如,当您更改一个函数调用时,我们将一直保持内存中的旧函数,直到新函数生效为止。由于Dark中的每个状态都有意义,因此,不完整的代码并不妨碍完整代码的正常运行。

编辑模式

在Dark中编写代码会有两种情况。首先,您是编写新代码时的唯一用户,或许代码处于REPL中,没有用户会接触它,或者它只是一个新HTTP路由,还没有配置任何链接。在这种环境下,即便没有任何保护,编辑也是安全的,非常类似于在开发环境中编写代码。

第二情况是当前正在运行的代码。无论是函数、事件处理程序、数据库等跑流量的代码,编辑它们必须是安全的。我们通过“锁定”所有正在使用的代码来保证安全,并要求您使用更加结构化的工具来编辑它。

下面将讨论结构化工具:HTTP/事件处理程序的功能标志、强大的数据库迁移框架以及用于函数和类型的版本控制技术。

功能标志

在Dark中减少意外复杂性的一种方法是用一个解决方案处理多个不同的问题。功能标志是我们的关键设计,可以用于替换本地开发环境、Git分支、代码部署,以及传统案例中的慢速、受控的代码更新等。

创建和部署功能标志是编辑器中的一个简单操作。它创建了一个独立的空间来添加新代码,并提供了开关,可以控制谁可看到旧代码和新代码,和代码自动切换或放弃的按钮/命令。

功能标志构建在Dark语言中,不完整的功能标志也有意义;Dark将执行旧的已锁定代码。

开发环境

功能标志取代了本地开发环境。现在,团队努力让每个人都使用相同版本的工具和库,例如核心代码、包管理器、编译器和预处理器、测试工具等等。使用Dark,不需要在本地安装依赖项、管理本地Docker配置,以及任何维护类似开发/生产环境的步骤。实际上,开发/生产环境也是无法检验和完全模拟的。

相对于传统做法的克隆一个本地环境,Dark的功能标志是在生产环境中创建一个全新的沙箱,用来取代了本地开发环境。在未来,我们还计划为应用程序的其他部分也创建沙箱,例如实时克隆数据库,不过我们认为这个还不是目前最紧迫的事项。

分支和部署

现在有几种不同的方法可以将新代码导入系统,包括Git分支、部署步骤和功能标志。它们都在工作流的不同部分解决了相同的问题。Git是在部署前阶段,部署步骤是在新旧代码包过渡阶段,功能标志是在新代码的控制推出阶段。其中,功能标志是其中最有力的方法,也可以说是最容易理解和使用的方法,它使我们能够完全忽视其他的两个概念。与用户自己写代码实现新功能的切换相比,功能标志的部署风险要小很多。

Git比较难用,对初学者也不友好,被称为是“真正的拦路虎技术”。不过,通过分支来优化是一个不错的方法,它使得Git的问题减少了许多,同时,由于Dark是实时编辑的,并使用Google-Docs-style的实时协作,所以去除了push的概念,减少了重新构建和代码合并的需要。

特性标志是我们安全部署的基石。通过将它们与即时部署相结合,开发人员可以在细粒度的、低风险的代码块中快速地测试,而不会发生导致系统崩溃的大型变更。

版本控制

我们使用版本控制来实现对函数和类型的变更。如果需要编辑某个函数,Dark将为该函数创建一个新版本,并允许您在HTTP或事件处理程序中通过功能标志调用该版本。(如果函数位于很深的位置,则会为所在路径的每个组合创建新版本。照这样下来,显然会产生大量的函数版本,但除非您真正使用到该函数,否则不会被影响和被注意的)。

出于类似的原因,我们还提供版本类型。通过版本控制功能和类型,允许用户慢慢改变应用,可以逐个验证使用新版本的处理器,而不需要批量地更改应用程序,对此,我们还提供了工具,帮助用户加快速度。这比目前的0/1部署方式要安全很多。

包和标准库的更新

用户在Dark中更新包时,并不会立即替换代码库中正在使用的函数或类型(存在安全风险)。而与之相反,所有运行的代码都继续保持现状,且全部的函数版本都是一致的。对于升级后的函数,可以通过功能标志定位到,成为可用的选择项,并可基于所使用的功能标志指定为新的版本。

如下案例为Dark自动更新的部份截屏,显示Dict::get函数有2个版本。第1个版本的类型是any,升级后的v1版本的类型是option。
Untitled_3.jpg

除此之外,我们会经常性地推出新的标准函数库,并摈弃旧版本的函数。对于那些在自己代码中使用了特定旧版本函数并需要持续访问的用户,当我们隐藏旧版函数时,用户就会无法正常访问。为此,我们计划提供相关的工具,帮助用户能一键升级到新版函数,这同样是通过功能标志来实现的。

Dark还有一个独特的功能。因为正式代码是运作在Dark中,所以我们可以为用户执行新版本代码的测试,比较新旧版本请求的输出,从而判断是否存在变更。这显著改变了软件更新的模式。由大范围的盲目包更新(或依赖于大量测试确保安全)转变为风险非常低,且可以自动升级的精确函数更新。

Dark的更新

我们看到,从Python 2到Python 3的升级转换已经进行了差不多10年,但直到今天仍然还是个问题。Dark是为持续交付而生的,我们同样需要考虑开发语言变化更新的问题。

在Dark中,处理小的语言改变时,我们通过新的Dark版本应对。旧代码维持在旧版的Dark上,新代码开始使用新的Dark版本。您可以使用功能标志或函数版本在新旧版本的Dark之间切换。

因为Dark还非常年轻,这种方式非常有效,但是,如果语言和库都存在非常多的变化时,这种方式是否合适就无法确定了。对语言的小增量更新进行版本控制允许我们进行小的更新,这意味着我们可以推迟许多开发语言方面的决策,不用现在就立刻下决定,而可等到我们拥有更多的用户,并获得更多的信息时,再做更准确的决策。

DB 迁移

现在已经有标准的模型,安全地进行DB数据迁移,大致的过程如下:
  1. 重写应用代码,以同时支持新旧2种数据模式
  2. 将数据全部转换成新数据模式。
  3. 去除旧的数据访问路径,完成切换


上述过程将DB迁移变成了耗时、费成本的项目。而所得的一个结果就是让所从事的大多数人拿到的都是过时的数据模式,即便简单地修复表名和列名都需要很高的成本。

Dark拥有一个强大的DB迁移框架,我们相信它使DB迁移变得非常简单,可以经常进行迁移。Dark中的所有数据存储(键值对存储/持久化哈希表)都有一个类型。要迁移一个数据存储,只需给它新存储的类型,以及一个回滚和前滚函数,以便让数据存储在这两种类型之间来回转变。

Dark中的数据存储是通过版本控制的变量名访问的。例如,为Users-v0的访问而初始化了一个用户数据存储。当创建一个具有不同类型的新版本时,它可以作为Users-v1访问。通过Users-v0保存并通过Users-v1访问的数据,包含有前滚函数,从而实现数据类型的转化,反之亦然。DB迁移的界面如下:
Untitled_4.jpg

使用功能标志将DB调用从Users-v0切换到Users-v1。您可以每次使用一个HTTP处理程序来降低操作风险,并且功能标志允许您为单个用户启用它们,以确保它们按照计划执行。一旦Users-v0调用不在使用,Dark将把后台所有剩余的数据从旧格式转换为新格式。这部分转换对用户是不可见的。

测试

Dark是一种静态类型的函数语言,值多数采用常量。与动态类型的面向对象语言相比,显著减少了测试的可能面,但仍然需要测试。

在Dark中,编辑器会自动在后台对正在编辑的代码执行单元测试,并且默认跨所有功能标志运行。同时,我们还尝试运用静态类型自动模拟代码,帮助用户发现bug。

Dark中运行着用户的正式环境框架,自然就带出了新的功能,我们自动将HTTP请求保存到Dark的基础框架中(目前我们保存了所有的请求,但将来会改成抽样模式)。我们就可以根据这些请求,结合单元测试来测试新代码,并可以将这些请求轻易转换为有价值的测试请求,用于单元测试本身。

结果对比

无部署和功能标志的组合意味着可以省去大约60%的部署管道。我们不再需要Git分支、pull请求、构建后端资产和容器、将这些资产和容器推入注册中心,以及Kubernetes部署步骤的任何部分等等。

以下是2种CD管道的流程对比:左边标准的CD管道有35个步骤和3个循环,而右边的Dark CD管道只包含6个步骤和1个循环。
Untitled_5.jpg

在Dark中,测试在后台自动运行;自动安装依赖关系;完全不需要涉及Git或GitHub;不需要构建、测试和推送容器;也不需要调用Kubernetes的部署步骤。

即便如此,在Dark中,保留下来的步骤也更为精简。由于功能标志可以通过单个原子操作完成或取消,因此不需要第二次遍历整个部署管道来删除旧代码。

这非常接近我们所能想象出的代码交付的最基本复杂性,显著地减少了CD持续交付的时间和风险。我们还显著地简化了升级包、DB迁移、测试、版本控制、安装依赖项、等价开发/生产环境,以及在不同语言版本之间快速、安全地升级等。如需了解更多关于Dark的设计和信息,请点击“Dark是什么?”。

原文链接: How Dark deploys code in 50ms(翻译:易理林)

0 个评论

要回复文章请先登录注册