一位朋友最近对我说:“我们不能做 DevOps,我们使用 SQL 数据库。” 我差点从椅子上摔下来。这种说法在很多层面上都是错误的。
“但是你不了解我们的情况!” 他反驳道。“DevOps 意味着我们将更频繁地部署我们软件的新版本!我们现在勉强能处理部署,而且我们每年只做几次!”
我询问了他当前的部署流程。
“每隔几个月,我们会得到一个新的软件版本,”他解释说。“将其投入生产需要大量工作。因为我们使用 SQL,所以部署看起来像这样:首先,我们踢出所有用户并关闭应用程序。接下来,DBA(数据库管理员)修改数据库模式。一旦他们的工作完成,新的软件版本就会安装并启用。这个过程需要几个小时,所以我们倾向于在周末做……我讨厌这样。如果失败了,我们必须恢复到备份磁带并从头开始恢复一切。”
他总结道:“仅仅安排这样一个事件就需要数周的协商。我们通常会输掉谈判,这就是为什么我们最终会在周末做这件事。每隔几个月做一次这种事很痛苦,而且是这里最大的压力来源。如果我们要每周发布,我们大多数人都会辞职。我们将没有周末!天哪,我听说有些公司每天多次发布软件。如果我们那样做,我们的应用程序总是会因升级而停机!”
哇。这里有很多东西需要理清。让我先澄清一些误解。然后我们来谈谈一些使这些部署变得更加容易的技术。
首先,DevOps 不是一种技术,而是一种方法论。DevOps 最简洁的定义是,它将敏捷/精益方法从源代码一直应用到生产环境。这样做是为了“更快地交付价值”,这是一种花哨的说法,意思是减少一个功能从想法到生产所需的时间。更频繁的发布意味着新编写的功能闲置等待投入生产的时间更少。
DevOps 不要求或禁止任何特定的数据库技术——或任何技术,就此而言。说你因为使用某种特定技术而可以或不能“做 DevOps”,就像说你不能将敏捷应用于使用特定语言的项目一样。SQL 可能是常见的“本月借口”,但这是一个站不住脚的借口。
我理解 DevOps 和缺少 SQL 数据库如何在某些人的脑海中不可避免地联系起来。在 2000 年代和 2010 年代初期,发明和推广 DevOps 的公司通常是大型网站,巧合的是,这些网站也在推广 NoSQL(键/值存储)数据库。然而,将两者联系起来是将相关性与因果关系混淆。这些公司也在推广为员工免费提供美味午餐。我们都同意这不是 DevOps 的先决条件。
其次,我不确定是否有人可以“做 DevOps”。你可以使用 DevOps 技术、方法等等。也就是说,人们经常使用这个短语,以至于我认为我已经输掉了这场战斗。
我和我的朋友进一步讨论了他的情况,很快他就意识到 DevOps 并非不可能;这将只是一个困难的转变。然而,一旦过渡完成,生活实际上会变得容易得多。
我的朋友还有一个顾虑。“听着,”他坦白道,“这些部署是有风险的。每次我们做一次,我都会冒着公司数据的风险,老实说,还有我的工作。我只是不想做它们。每隔几个月做一次就够有压力了。更频繁地做?不,先生,那太不负责任了。”
正如我在之前的专栏(“小批量原则”,acmqueue 14(2) [https://queue.org.cn/detail.cfm?id=2945077])中讨论的那样,当某件事有风险时,自然会倾向于减少做它的次数。与直觉相反,这实际上增加了风险。下次你做有风险的事情时,你会更加生疏,并且周围环境的累积变化变得越来越大,使得因未知副作用而导致的失败几乎是必然的。相反,DevOps 采取了激进的立场,即有风险的事情应该更频繁地做。更高的频率暴露了那些因为“这只发生一年一次”而被掩盖的小(和大的)问题。它迫使我们自动化流程,自动化流程的测试,并使流程如此顺畅以至于风险降低。它让参与人员有更多的练习机会。熟能生巧。它没有逃避我们所恐惧的事物,而是勇敢地正面应对风险并克服它。就像任何经历过术后康复的人一样,你会重复练习直到不再痛苦。
部署总会有一些固定成本。原则上,你应该始终将部署的固定成本降至零。在不降低固定成本的情况下提高部署频率对业务有害且不负责任。
本文的其余部分描述了两种在使用 SQL 的环境中实现快速发布的做法。实施它们需要开发人员、质量保证和运营部门走出他们的孤岛并进行协作,这在某些组织中是闻所未闻的,但却是 DevOps 的本质。结果将是一种更顺畅、更少痛苦,当然也更少压力的方式来开展业务。
在旧方法中,任何模式更改都需要关闭整个应用程序,同时由专家团队(或一位工作过度的 DBA)手动修改模式。如果你要进行完全自动化的部署,你需要进行完全自动化的模式更新。
为此,应用程序应该管理模式。每个版本的模式都应该编号。应用程序从模式版本 1 开始。该值存储在数据库中(想象一个单行表,其中包含一个字段,用于存储值“1”)。当应用程序启动时,它应该知道它与模式版本 1 兼容,并且如果它在数据库中找不到该版本,它将拒绝运行。
然而,为了自动化模式更新,下一个软件版本知道它需要模式版本 2,并且知道将版本 1 模式升级到版本 2 的 SQL 命令。在启动时,它看到版本是 1,运行适当的模式升级命令,将数据库中存储的版本号更新为 2,然后继续运行应用程序。
执行此操作的软件通常有一个 SQL 模式更新命令表。数组索引 n 中的命令将模式从版本 n-1 升级到 n。因此,无论找到哪个版本,软件都可以将数据库带到所需的模式版本。实际上,如果找到未初始化的数据库(例如,在测试环境中),它可能会循环执行数十个模式更改,直到它达到最新版本。并非每个软件版本都需要模式更改;因此,模式和软件使用单独的版本号。
有开源和商业系统实现了这个过程。其中一些产品比其他产品更复杂,支持各种语言、数据库系统、错误处理复杂性以及它们是否也支持回滚。在网上搜索“sql change automation”会找到很多。我最熟悉 .NET 代码的开源项目 Mayflower (https://github.com/bretcope/Mayflower.NET) 和 Go 的 Goose (https://bitbucket.org/liamstask/goose)。
模式修改过去会锁定数据库几分钟甚至几小时。这会导致应用程序超时并失败。现代 SQL 数据库 благодаря 无锁模式更新和在线重新索引功能,减少或消除了此类问题。这些功能可以在所有最新的 SQL 产品中找到,包括 MariaDB、MySQL 和 PostgreSQL 等开源产品。查看文档以了解在不中断的情况下可以和不可以做什么的详细信息。
一旦你的软件使用了这些技术,采用 CI(持续集成)就会变得容易得多。你的自动化测试环境可以包括测试,这些测试在旧模式中构建数据库,升级它,并运行新的软件版本。你的模式升级过程在投入生产之前可能会经过数百次测试。这应该为该过程带来新的信心,降低模式升级的风险,并将 DBA 的个人参与从升级中分离出来。他们会感谢他们的周末回来了。
我最喜欢这项技术的部分是,你的模式现在被视为代码。控制台上的手动工作已被消除,并且你获得了在开发人员沙箱、测试环境、UAT(用户验收测试)环境和生产环境中一遍又一遍地执行该过程的能力。你可以多次运行该过程,修复和微调它。既然它是代码,你就可以将最佳代码管理和软件工程技术应用于它。
如何在分布式计算环境中升级数据库模式?
想象一下一个典型的基于 Web 的应用程序,它是同一软件的多个实例(副本)在 Web 负载均衡器后面运行。每个实例接收其份额的 HTTP 流量。这些实例访问同一个数据库服务器。
当软件与数据库模式紧密耦合时,就不可能执行需要数据库模式更改的软件升级。如果你首先更改模式,实例将全部崩溃,或者至少会被更改搞糊涂;你可以跑来跑去尽可能快地升级实例,但你已经输掉了比赛,因为你遭受了中断。
啊哈!为什么不先升级实例呢!可悲的是,当你逐个升级实例的软件时,新升级的实例无法启动,因为它们检测到错误的模式。在你将模式更改为与软件匹配之前,你最终会遇到停机时间。
显而易见的解决方案是违抗物理定律,并在升级所有实例上的软件的同时,完全同步地更改数据库模式。如果你能做到这一点,一切都会很好。
可悲的是, 有一项反对违抗物理定律的政策,大多数雇主也是如此。这就是为什么传统方法是关闭整个应用程序,升级所有内容,然后再将其重新联机。在我们 IEEE 的朋友们弄清楚如何暂停时间之前,这是我们能做的最好的事情。
无论你是通过违抗物理定律还是通过安排停机时间来阻止世界运转,你都引入了一个更大的问题:你进行了许多单独的更改,但你不知道其中任何一个是否成功,直到系统再次运行。你也不知道累积的更改中的哪一个导致了问题。
这种“大爆炸”式的更改是有风险的。一次进行并验证一个更改风险较小。如果你一次进行多次更改,并且出现问题,你必须开始二分查找以找出哪个更改导致了问题。如果你一次进行一个更改,并且出现故障,则搜索变得轻而易举。回退一个更改也比回退多个更改更容易。
天哪,即使是谷歌,凭借其高度复杂的测试技术和方法论,也理解到暂存环境和生产环境之间的细微差异可能会导致部署失败。他们“金丝雀”他们的软件发布:升级一个实例,等待查看它是否正确启动,然后在一段时间内缓慢升级剩余的实例。这不是一种测试方法论,而是一种针对不完整测试的保险策略——并不是说他们的测试人员不优秀,而是没有人是完美的。“金丝雀”技术现在已成为行业最佳实践,甚至嵌入在 Kubernetes 系统中。(术语金丝雀源自“煤矿中的金丝雀”。第一个要升级的实例会死掉,作为出现问题的警告信号,正如煤矿工人过去常常携带鸟类,通常是金丝雀,它们对有毒气体比人类更敏感。如果金丝雀死了,那就是撤离的信号。)
由于这些问题是由软件与特定模式紧密耦合引起的,因此解决方案是放松耦合。这些可以通过编写适用于多个模式的软件来解耦。这就是分离推出和激活。
第一阶段是编写不假设表中字段的代码。在 SQL 术语中,这意味着 SELECT
语句应指定所需的确切字段,而不是使用 SELECT *
。如果你确实使用 SELECT *
,请不要假设字段按特定顺序排列。LAST_NAME 今天可能是第三个字段,但明天可能就不是了。
有了这种 дисциплина,从模式中删除字段就很容易了。部署不使用该字段的新版本,一切正常运行。在所有实例都运行更新版本后,可以更改模式。实际上,由于 vestigial 字段被忽略了,你可以拖延并稍后删除它,很久以后,可能要等到下一次(否则不相关的)模式更改。
添加新字段很简单,只需在第一个使用它的软件版本之前在模式中创建它即可。我们使用技术 1(应用程序管理它们自己的模式)并部署一个修改模式但不使用该字段的版本。凭借正确的事务锁定喧嚣,第一个使用新软件重新启动的实例将干净利落地更新模式。如果出现问题,金丝雀将死亡。你可以修复软件并尝试新的金丝雀。回退模式更改是可选的。
由于模式和软件是解耦的,开发人员可以随意开始使用新字段。虽然过去升级需要找到与多个团队兼容的维护窗口,但现在流程是解耦的,所有各方都可以协调工作,但不必步调一致。
更复杂的更改需要更多的计划。当拆分字段、删除一些字段、添加其他字段等等时,真正的乐趣就开始了。
首先,软件必须编写为同时适用于旧模式和新模式,最重要的是还必须处理过渡阶段。假设你正在从将一个人的完整姓名存储在一个字段中迁移到将其拆分为名字、中间名、姓氏、头衔等单独的字段。软件必须检测到哪些字段存在并采取适当的行动。当数据库处于过渡阶段并且两组字段都存在时,它也必须正常工作。一旦两组字段都存在,批处理作业可能会运行,拆分姓名并存储各个部分,将旧字段置为空值。代码必须处理某些行未转换而另一些行已转换的情况。
执行此转换的过程在侧边栏“实时模式更改的五个阶段”中进行了说明。它有许多阶段,包括创建新字段、更新软件、迁移数据和删除旧字段。这在云系统管理实践(我是 Strata R. Chalup 和 Christina J. Hogan 的合著者)中被称为 McHenry 技术;在 Michael T. Nygard 的发布它!:设计和部署生产就绪软件中也称为 Expand/Contract。
1. 正在运行的代码读取和写入旧模式,仅从表或视图中选择它需要的字段。这是原始状态。
2. 扩展:通过添加任何新字段但不删除任何旧字段来修改模式。没有进行代码更改。如果需要回滚,那很简单,因为未使用新字段。
3. 修改代码以使用新模式字段并推送到生产环境。如果需要回滚,它只会恢复到阶段 2。此时,可以在系统运行时完成任何数据转换。
4. 收缩:删除引用旧的、现在未使用的字段的代码,并推送到生产环境。如果需要回滚,它只会恢复到阶段 3。
5. 从模式中删除旧的、现在未使用的字段。万一此时需要回滚,数据库将简单地恢复到阶段 4。
该技术足够复杂,可以处理实时分布式系统上最复杂的模式更改。此外,每个突变都可以单独回滚。
对于特殊情况,可以减少阶段数。如果只添加字段,则跳过阶段 5,因为没有任何内容需要删除。该过程简化为本文前面描述的内容。阶段 4 和 5 可以组合或重叠。或者,来自一个模式更改的阶段 5 可以合并到下一个模式更改的阶段 2 中。
使用这些技术,你可以滚动完成最复杂的模式更改,而无需停机。
使用 SQL 数据库不是进行 DevOps 的障碍。自动化模式管理和一点开发人员 дисциплина 可以实现更有效和可重复的测试、更短的发布周期和降低业务风险。
自动化发布解放了我们。它将令人担忧、压力大、手动升级的过程变成了一个定期发生的事件,而不会发生意外。它降低了业务风险,但更重要的是,创造了一个更可持续的工作场所。
当你能够自信地部署新版本时,你会更频繁地这样做。以前未发布数周或数月的新功能现在更快地到达用户手中。错误修复得更快。安全漏洞更快地被关闭。它使公司能够为客户提供更好的价值。
我要感谢以下人员对本文的帮助:Sam Torno,Stack Overflow Inc. 的 SRE;Mark Henderson,Stack Overflow Inc. 的 SRE;Steve Gunn,独立人士;Harald Wagener,iNNOVO Cloud GmbH;Andrew Clay Shafer,Pivotal;Kristian Köhntopp,Booking.com,前 MySQL AB。
小批量原则 减少浪费、鼓励实验和让每个人都快乐 Thomas A. Limoncelli https://queue.org.cn/detail.cfm?id=2945077
精益软件开发——构建和交付两个版本 在满足团队目标的同时,迎合开发人员的优势 Kate Matsudaira https://queue.org.cn/detail.cfm?id=2841311
在质量保证中采用 DevOps 实践 融合软件开发的艺术和科学 James Roche https://queue.org.cn/detail.cfm?id=2540984
Thomas A. Limoncelli 是纽约市 Stack Overflow Inc. 的 SRE 经理。他的著作包括系统和网络管理实践 (http://the-sysadmin-book.com), 云系统管理实践 (http://the-cloud-book.com), 和系统管理员的时间管理 (http://shop.oreilly.com/product/9780596007836.do)。他的博客在 EverythingSysadmin.com,推特在 @YesThatTom。他拥有德鲁大学计算机科学学士学位。
版权 © 2018 归所有者/作者所有。出版权已许可给 。
最初发表于 Queue vol. 16, no. 5—
在 数字图书馆 中评论本文
Alpha Lam - 使用 Bazel 的远程缓存服务
远程缓存服务是一项新的发展,可以显着节省运行构建和测试的时间。它对于大型代码库和任何规模的开发团队特别有用。Bazel 是一个积极开发的开源构建和测试系统,旨在提高软件开发的生产力。它有越来越多的优化措施来提高日常开发任务的性能。