如何对DevOps数据库进行源代码控制

探讨了在DevOps环境中数据库源代码控制的重要性,包括模式和数据的源代码控制、个人开发数据库的设置、共享数据库的更新策略以及数据库安全性和容器化的实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

提纲:

  • 包括索引在内的数据库模式需要进行源代码控制
  • 诸如查询表这类用于控制业务逻辑的数据需要进行源代码控制
  • 开发人员需要一种能够便捷地创建本地数据库的方法
  • 共享数据库的更新只能通过构建服务器完成

健壮的DevOps环境需要对系统的每个组件进行持续集成。但是,数据库常常被排除在这之外,这会导致从脆弱的产品发布和低效的开发实践到让新入职的程序员工作更困难等一系列问题。

在本文中,我们将讨论在成功的持续集成环境中,关系型数据库和NoSQL数据库的独特的一面。

模式的源代码控制

首先需要解决的就是源代码控制和模式问题。让开发人员以一种特别的方式进行数据库变更是不合适的。这相当于在生产服务器上通过直接编辑JavaScript文件对其进行更改。

在为数据库规划源代码控制时,需要确保囊括了所有内容。这包括但不限于:

  • 表或者集合
  • 约束
  • 索引
  • 视图
  • 存储过程、函数以及触发器
  • 数据库配置

您可能会想,“我使用的是无模式数据库,所以我不需要源代码控制”。即便如此,您仍需要考虑索引和数据库的整体配置。如果在QA和生产数据库中索引计划不同,那么执行性能测试将毫无意义。

数据库的源代码控制有两种基本类型,我们将其称为“全模式”和“变更脚本”。

全模式源码控制

“全模式”源码控制指的是源代码控制与你所希望的数据库的外观看起来十分相似。在使用此模式时,可以看到所有的表和视图都按照预期的样子排列,这让你无需部署数据库就能够更容易理解它。

SQL Server的SQL Server Data Tools(SSDT)就是全模式源码控制的一个例子。这个工具可以通过CREATE脚本的形式表示所有数据库对象。当想要用SQL创建一个新的对象,只需将最终脚本直接粘贴到处于源代码控制下的数据库项目中即可,这就很方便了。

全模式源码控制的另一个例子是实体框架迁移(Entity Framework Migrations)。在这个案例中,数据库主要通过C#/VB类的方式而非SQL脚本的方式表现。但同样,可以通过浏览源代码获得对数据库的整体认识。

在使用全模式源码控制时,通常不需要直接编写迁移脚本。部署工具通过将数据库的当前状态与处于源代码控制中的理想版本进行比较来确定需要进行哪些更改。这可以让你快速完成数据库变更并看到结果。在使用这种类型的工具时,我很少直接更改数据库,而是使用工具完成大部分工作。

有些情况下,只有工具是不够的,即使包含部署前和部署后脚本也是如此。在这种情况下,生成的迁移脚本必须由数据库开发人员或DBA手工修改,这可能会破坏持续部署计划。这种情况通常发生在对表结构进行重大变更时,因为在这些情况下生成的迁移脚本效率可能较低。

全模式源码控制的另一个优点是它支持代码分析。例如,如果列的名称被更改,但在视图中未做出相应更新,SSDT将返回一个编译错误。就像应用程序设计语言中的静态类型一样,可以捕获大量错误,并且能够对部署有明显错误的脚本提前防范。

变更脚本源码控制方式

另一种源代码控制方式就是变更脚本源码控制。这种方式不存储数据库对象本身,而是存储创建数据库对象所需的步骤列表。我曾经成功使用的是Liquibase数据库,总的来说这类工具的工作机制都差不多。

变更脚本工具的主要优点是可以完全控制最终迁移脚本的样貌。这使得复杂变更的执行更加容易,例如表的拆分或合并。

不幸的是,这种类型的源代码控制也存在一些缺陷。首先是需要编写变更脚本。虽然它的控制力度更强,但同样也更费时。相比于手写ALTER TABLE脚本,为C#类添加一个属性要容易得多。

当使用SchemaBinding之类的功能时,会让问题更加严重。对于不熟悉这个术语的人,可以将其理解为,通过锁定表的模式启用SQL Server中的一些高级特性。如果要变更表或视图的设计,必须首先将SchemaBinding从任何与该表或视图有关的视图中移除。如果这些视图被其他视图模式绑定(schema-bound),那么这些视图同样也需要临时解除绑定。虽然对于全模式源代码控制工具来说,生成所有的样板很容易,但是想要用手工的方式正确地生成它们还是一项相当困难的工作。

变更脚本工具的另一个缺点是它们往往难以理解。例如,如果你希望在不部署表的情况下了解某个表中列的信息,则需要查阅所有涉及该表的变更脚本。这就很容易错过某些信息。

该如何选择源代码控制模型?

从头开始一个新的数据库项目时,我会选择全模式源代码控制。这种模式会让开发人员工作更加高效并且只要有一名人员完成设置工作,之后只需要很少的知识就可以正常使用。

对于现存的数据库,特别是那些已经存续多年的生产环境数据库,通常选择变更脚本源代码控制模式更加合适。全模式工具会对数据库设计做出某些特定的假设,而更改脚本工具则是通用的。此外,全模式工具构建难度更大,对于某些特殊的数据库来说,可能根本不可用。

数据管理

根据表中所含数据的性质,表可以被广泛地分类为“管理表”、“用户表”或“混合表”。根据表所属的类别不同,处理这些表的方式也是不同的。

管理表

将数据库置于源代码控制之下的一个常见错误是遗忘数据。总有一些“查询表”保存着用户不打算修改的数据。例如,其中可能包含表驱动的业务规则逻辑、状态机的各种状态码,或者仅仅是与应用程序代码中的枚举类相匹配的键-值对列表。

这类表中的数据应该被视为源代码一样对待。对这类数据的变更需要经过与其他代码变更相同的评审和QA过程。特别重要的是,为确保这些变更不会被遗漏,这些变更的部署应该与其他应用程序和数据库部署一并自动完成。

在SQL Server数据工具中,我使用部署后脚本处理此问题。在这个脚本中,我将期望数据填充到一张临时表中,然后使用MERGE语句更新实际表。

如果源代码控制工具无法很好地处理这个问题,还可以通过构建独立工具来执行数据更新。重要的不是如何做,而是这个过程是否易用并且可靠。

用户表

用户表指的是用户可以添加或修改数据的表。因此,这包括可以直接修改的表(例如名称和地址)和通过操作间接修改的表(例如货单收据、日志)。

真实的用户数据基本不会被直接加入源代码控制中,不过,为开发和测试提供逼真的样本数据也是一种最佳实践。这些数据可以直接存储在数据库项目中,处于源代码控制之下的其他地方,如果特别大,也可以保存在独立的共享文件中。

混合表

混合表指的是即存储管理数据也存储用户数据的表。有两种方法可以对其进行分区。

列分区是指用户可以修改某些列,但不能修改其他列。在这种场景下,可以将该表视为有额外限制的普通管理表,用户控制的列永远不会被更新。

行分区指的是某些记录用户无法修改的情况。我曾经遇到的常见的场景是需要在用户表中对某些值进行硬编码。在较大型的系统中,对于每个可以独立于任何真实用户进行更改的微服务,可能都有一个独立的用户ID。例如,可能是一个被称为“银行数据导入器”的用户。

在我看来,管理行分区混合表的最佳方法是通过保留键的方式。当定义identity/auto-number列时,将初始值设置为1,000,通过源代码控制对编号从1到999的用户ID进行管理。这需要数据库允许手动设置identity列中的值。在SQL Server中,是通过SET_IDENTITY_INSERT命令完成的。

处理此场景的另一个选择是使用名为“SystemControlled”的列或者能够达到类似效果的方法。当设置为1/true时,表示应用程序不可直接修改。如果设置为0/false,则部署脚本会将其忽略。

个人开发数据库

将模式和数据置于源代码控制之下,就可以进行下一步并着手设置个人开发数据库。正如每个开发人员都应该能够运行自己的web服务器实例一样,有可能对数据库设计做出修改的每个开发人员都需要能够运行自己的数据库副本。

这一规则经常会被打破,这对开发团队是相当不利的。在共享环境上做变更的开发人员一定会相互干扰。他们甚至可能会陷入“部署之争”,每个开发人员都试图将更改部署到数据库。当使用全模式工具执行此操作时,开发人员将交替地恢复其他开发人员的变更,甚至都不会意识到这一点。在使用变更脚本工具的情况下,数据库则可能处于不确定状态,迁移脚本可能无法正常使用,需要从备份中重新恢复。

另一个问题是模式漂移。这时,开发数据库与处于源代码控制下的内容不再匹配。随着时间的推移,开发数据库会逐渐积累越来越多的非生产表、测试脚本、临时视图和其他垃圾信息需要清除。当每个开发人员都有自己的数据库时,这样做要容易得多,因为他们可以随时重置数据库。
最后也是最重要的问题是,服务开发人员和UI开发人员需要稳定的平台来编写代码。如果共享数据库不断变化,他们就无法有效地工作。在我曾经工作过的一家公司,很少看到开发人员大喊“服务又宕机了!”,然后玩一个小时视频游戏,等待共享数据库重新组装起来。

共享的开发和集成数据库

对于共享的开发人员数据库或集成数据库来说,首要原则就是不能对数据库直接进行修改。更新共享数据库的唯一方法就是通过构建服务器以及持续集成/部署流程完成。这不仅可以防止模式漂移,还可以通过有计划的更新减少中断的次数。

根据经验,我会在相关分支进行代码检入时同步更新共享开发人员数据库。这可能会造成中断,但通常是处于可控范围的。在进行集成之前,您确实需要先验证部署脚本的正确性。

对于集成数据库而言,我更倾向于每日安排一次部署,与服务的部署次数相同。这样能够为UI开发人员提供一个相对稳定的工作平台。对于UI开发人员来说,没有什么比不知道突然开始失败的代码是他们的错误还是服务/数据库中的问题更令人沮丧的了。

数据库安全和源代码控制

在数据库管理中,安全性是一个经常被忽略的方面。具体来说,就是哪些用户和角色可以访问哪些表、视图和存储过程。在最糟糕的情况下,应用程序可以获得对数据库的完全访问权限。它可以对每个表的每一列进行读写操作,甚至是那些与其没有业务关联的列。数据泄漏经常是由于实用小程序的访问权限远远超过其所需的访问权限而造成的。

锁定数据库的主要反对意见是,“我们无法预知什么会崩溃”。因为以前从未被锁定过,所以开发人员根本不知道应用程序实际需要的权限是什么。

解决这一问题的办法是从第一天起就将权限控制放入源代码控制中。这样,当开发人员测试应用程序时,如果权限不正确,一开始就会失败。这反过来又意味着,当到达QA阶段时,所有权限问题都已解决,不存在权限缺失的猜测或风险。

容器化

根据项目的性质不同,数据库的容器化是一个可选步骤。我将通过两个案例说明个中原因。

对于第一个案例,这个项目有一个非常简单的分支结构:有一个“dev”分支,它会导入QA分支,QA分支又会导入阶段化分支和最终的生产分支。这可以通过四个共享数据库实现,管道中的每个阶段,各使用一个数据库。

在第二个案例中,我们有一组重要的特性分支。每个重要的特性分支被进一步细分为开发和QA分支。每个特性必须通过QA检验,才能够成为合并到主分支的候选特性,因此每个重要特性都需要有各自的测试环境。

在第一个案例中,即使web服务确实需要容器,容器化也可能是浪费时间。对于第二个用例,容器化某种程度上则是至关重要的。如果没有真正的容器(例如Docker),那么在创建新的重要特性分支时,至少可以根据需要生成新环境的部署脚本(例如AWS或Azure)。

关于作者

Jonathan Allen在90年代后期开始为一家医疗诊所开发MIS项目,逐步将该项目从Access和Excel升级成企业级的解决方案。在花了五年时间为金融业编写自动化交易系统之后,他成为了多个项目的顾问,其中包括自动化仓库的UI、癌症研究软件的中间层,以及一家大型房地产保险公司的大数据需求。在空闲时间,他喜欢研究和记录源于16世纪的武术。

查看英文原文: How to Source Control Your Databases for DevOps

刚刚发布的ThoughtWorks技术雷达 建议技术团队“暂缓或谨慎”使用反模式“CI theatre(伪CI,可以理解为不完整的持续集成)”。 “伪CI”描述的是实践持续集成(CI)过程中的一些错觉,然而这些并不是真正的CI实践。 基于持续集成,我和同事 Emily Luke做了一些研究, 我将分享伪CI是什么样的,为什么我们建议你“暂缓或谨慎使用”,以及预防伪CI的方法。持续集成我最喜欢的CI定义来自于continuousdelivery.comCI开发人员定期(至少每天)将他们所有的工作集成到主干(也称为主线或主干分支)这个引用中暗含了CI实践的两个基本原则。第一个是“把他们所有的工作集成到主干”;第二个是“至少每天”。对于CI还有一系列其他原则和实践,例如:将所有内容都检入您的代码库,构建每个提交,自动化构建,保持快速构建,并有可以自我验证的代码, 还有Martin Fowler 关于持续集成的评论中的可视化故障并立即修复故障等。我个人认为 每天至少检入代码到主干分支一次 是CI的基础。没有达到这一点就只是伪CI而不是真正意义上的CI。伪CI是什么样的?这是我们调研到的一个故事,一位经验丰富的开发人员(让我们称他为David)来自湾区的一个中型创业公司,每周有两次产品交付。 David说他的组织正在践行CI,他说:“是的,我们用Circle CI”,他描述了一个具有挑战性的场景,曾经在一个分支上工作了一段时间,大约修改了100个文件和7000行代码,然后在代码审查阶段就开始招架不住了,因为他要向他的同事解释所有的代码变更的原因。“最具挑战性的事情是你最终要将一大堆功能集中到一个提交里,因为它们都是这个组件的一部分”,他解释说:“我希望有一个更好的方式来分解这些提交,因为很难把所有事情(变更历史)记在脑子里。”如果这个情况你听起来很熟悉,那么你也在做伪CI。 如果有下面的这些场景,那么你们就是在做伪CI:当有人问起你们在实践CI吗? 如果你说我们有一个CI服务器并且我们使用X工具在我们的调查中,只有10%的参与者承认有CI服务器与CI践行不一样。 相反,90%的个人表示他们正在践行CI,无论他们是否有专门从事CI的基础知识。 所以简单的认为只要有一个CI服务器就是“在做CI”,这就清楚地表明是在做“伪CI”。使用长期开发分支,但不会定期检入master主干在David的故事中,他们并没有实践每天检入master主干,这就是“伪CI”的标志。 这是我们在调研中常看到的一种模式,其中团队在master主干上运行CI,但不频繁构建,也不是每天都在提交。 或者他们在分支上运行CI,但不会频繁的集成到master主干。 只对特性分支运行CI,其实应该称之为持续隔离(continuous isolation)才对.合并分支时感到焦虑和疲惫真正的持续集成要把代码所有者的责任意识扩展到整个团队。 这改变了团队内部人员的观点以及他们对失败构建的态度。 不再是“我的宝贵的分支”,或是“我的错误导致构建被破坏”,而是“我们的代码”和“我们的失败”。David遇到焦虑和疲惫的事实清楚地表明,他忽略了CI的一个重要的优势:持续反馈和代码集体所有权。伪CI还有更多的一些现象,虽然我们发现有一些并不那么常见,但它们仍然存在一些问题,构建的时候,仅有极少的测试覆盖允许构建长时间处于失败状态虽然David的团队引入了一个备受尊崇的CI工具和常见的流程,如特征分支和代码审查,但他们并没有实施全套CI实践,因此错过了许多好处。 我们遗憾的发现,在我们的研究的组织中90%发生了这种情况。一些组织实施伪CI中反而错失了CI的主要优势 - 快速反馈,代码集体所有权,并准备达成持续交付如何避免,预防和解决伪CI的问题?如果您确定可能正在遭受伪CI之苦,则可以通过三种方式来解决问题并进行持续改变。1. 提交更频繁回到根本,尽量频繁地提交,每天至少提交一次应该是最低目标。 如果你还没有开始做CI,这就是你可以开始的地方,即使你在做CI,依然会有改进的空间。我喜欢“频率降低难度”的说法。 往往我们在做一些事情时,任务变得越小,则其更容易被实现。 持续集成就是是一个典型例子。 我的建议是要更加频繁地检入你的代码到代码库并且将开发分支集成到主干分支,至少每天集成一次”。2. 基于主干分支开发有很多论坛在讨论基于主干还是基于开发分支进行开发,我不想讨论那些血淋淋的细节。 然而,在我们的调研中,当我们与一些曾经在实践CI过程中感到痛苦的人交谈时,没有引入主干开发的团队对此有更深刻的感受。 DevOps现状调查报告-2016 还发现,基于主干开发和持续集成是达成持续交付的关键因素,同时也能建设更高效能的团队。基于主干的开发肯定会有一定的挑战,但它可以把问题提前暴露出来,而不是要等到代码合并、代码审查甚至到延迟发布的时候。如果你想达到更好效果的CI和CD,我建议在主干上做开发,或者如果你更愿意使用短周期的临时分支(合并到主干之前不到一天)进行开发,那么最好同时不要超过三个以上的活跃分支。3. 自动化自动化是做好持续集成的基石,所以如果你还没有自动化一切,现在是开始的好时机。 如果你认为已经实现了所有自动化的功能,那么每次有人手动地做任何事情超过一次,便要问自己“这个为什么不能自动化”? 我已经观察到自动化不仅可以帮助您在CI中变得更好,还可以帮助您开始持续交付。总结现在你知道什么是伪CI了,如果你的团队正在这么实践伪CI,那么你可以阻止这种情况继续发生了。 如果您仍然感到困惑,我建议你在Martin Fowler的博客“CI Certification test”做一个测试, 以确认你的组织是否正在做可靠的CI。 如果你通过了CI测试,那太好了,现在是考虑您是否准备好实施持续交付的时候了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值