担心数据库变更?使用 CI/CD 掌控一切

2025-06-08

担心数据库变更?使用 CI/CD 掌控一切

开发人员常常害怕数据库更改,因为团队中任何人的失误都可能导致严重中断,甚至数据丢失。如果更改不向后兼容、无法回滚或影响系统性能,风险会更高。这会导致团队信心不足,并降低开发速度。因此,数据库更改是敏捷和 DevOps 中常见的故障点。

数据库通常是手动创建的,并且经常会经过手动更改、非正式流程甚至生产测试而不断演变。这会使您的系统更加脆弱。解决方案是将数据库更改纳入源代码控制和 CI/CD 流水线中。这样,您的团队就可以记录每项更改,遵循代码审查流程,在发布前进行全面测试,简化回滚,并与软件发布进行协调。

让我们看一个示例,了解如何将数据库迁移纳入 CI/CD 流程,并成功推送不向后兼容的数据库更改。我们还将介绍更改测试、渐进式部署、回滚处理以及一些实用工具。

什么是 CI/CD?

CI/CD 是现代开发和 DevOps 的基石。

CI(持续集成)是指在一天内将所有开发人员工作代码合并到共享代码库的实践。其目的是通过频繁且尽早的集成来避免集成问题。通常,这种集成会启动自动化构建和测试。

CD(持续交付)是在短周期内构建、测试和发布软件的实践,目的是确保可以随时发布软件的工作版本。

您的数据库准备好使用 CI/CD 了吗?

让数据库做好 CI/CD 准备有几个关键要求。首先,数据库必须能够使用一个或多个 SQL 脚本从头开始复现。这意味着,除了创建数据库初始版本的脚本之外,您还必须维护对数据库进行所有必要架构更新的脚本。

创建这些脚本时,您有两个选择:

  1. 为每个模式对象创建一个脚本,然后在对对象进行更改时更新相应的脚本(基于状态)。
  2. 创建一个用于创建整个数据库架构的原始脚本。然后,针对变更创建一系列单独的变更脚本(基于迁移)。

要了解更多信息,请查看这篇关于基于状态与基于迁移的数据库更新的优秀文章。

CI/CD 的第二个要求是,数据库模式(也就是我们刚才提到的那些脚本)必须像源代码一样,处于源代码控制中。您必须像处理代码一样,将数据库模式的更改视为一个受控的过程。

第三,在执行任何数据库迁移之前,务必进行备份。如果您正在使用实时生产数据库,请考虑使用Postgres 的从属数据库进行迁移或升级。

最后,涉及删除数据库对象的更改(例如如下所示删除一列)由于数据丢失,处理起来可能更加困难。许多组织制定了应对此类更改的策略,例如仅允许添加更改(例如添加一列),或者专门设立一个 DBA 团队来处理此类更改。

您的团队准备好迎接 CI/CD 了吗?

数据库变更和数据库 CI/CD 的最佳流程或许是确保 DevOps 和 DBA 之间能够通力合作。确保 DBA 参与代码审查周期;他们可以帮助识别只有他们才知道的问题。DBA 了解每个特定环境中的数据库,包括数据库特定的依赖关系,例如 ETL 加载作业、数据库维护任务等等。

在为 CI/CD 设置数据库时,以及在任何迁移过程中(如果可能),请务必咨询数据库 SME。此外,请务必遵循合理的 DevOps 流程,例如在测试环境中测试更改、执行备份、降低风险、做好回滚准备等等。

您的 CI 工具如何帮助迁移

当您创建或更新这些脚本并将它们推送到源代码控制时,您的 CI 工具(例如 Jenkins 或 Heroku CI)将提取更改,然后:

  1. 在测试或临时环境中将数据库重建为最新版本的脚本。由于数据库正在重建,请务必导出查找/引用数据,然后将其导入到新的架构中。虽然可以导出和导入事务数据,但事务数据不在本文的讨论范围内。如果您感兴趣,可以在此处阅读更多最佳实践
  2. 运行测试。为了测试数据库更改,一个可能节省时间的方法是进行两组测试。第一组是快速测试,用于验证构建脚本并运行一些基本的功能测试(例如引用完整性、存储过程单元测试、触发器等)。第二组包括事务数据的迁移(可能是经过清理的生产数据),以运行更真实的完整测试。
  3. 将数据库更改部署到生产环境或其他选定环境。(根据您的迁移策略,CI 工具还应同时部署和测试任何依赖于数据库更改的代码更改。)

注意这些常见问题

很多情况下,当你使用双向兼容的代码进行简单的模式添加时,可以同时推送代码和数据库更改。这应该不成问题,因为在我们的案例中,回滚操作简单且可预测。在处理包含简单数据库组件的微服务时,通常都是如此。

然而,在很多情况下,这种简单的方法可能会引发严重的问题:

  • 生产数据可能与测试/阶段数据不同,并导致不可预见的问题。
  • 代码和数据库模式的大量变化可能正在酝酿中,需要同时部署。
  • CI/CD 流程在每个环境中可能并不一致。
  • 您可能受到零停机时间的要求。
  • 即使使用帮助您实现零停机时间的工具(例如 Heroku 预启动),您最终也可能会同时运行两个版本的代码。

解决上述问题有多种策略。一些常用的解决方案包括:

  • 如果您的更改是向后兼容的,请使用“滴答”发布模式。这种方法包括先发布新的数据库列,然后再发布新的代码。通过这种方式,您可以尽早发现问题,并最大程度地减少生产环境的变更。此外,回滚操作规模小且易于管理,可以使用 Heroku 的 Postgres 回滚等工具来完成(如上所述)。
  • 如果您的提供商支持,请使用蓝绿部署。在此模式中,将与现有生产服务器并排创建一组全新的生产服务器。启用数据库同步,并使用 DNS 或代理切换到新的服务器/数据库。您只需将代理更改回原始服务器即可进行回滚。

一个简单的迁移示例

让我们基于上面介绍的迁移脚本选项来运行一个示例。请注意,某些框架(Rails、Django、ORM 工具等)会为您抽象或处理架构创建和迁移。虽然具体细节可能因您使用的框架而异,但以下示例仍然应该有助于您理解这些核心概念。例如,您可能需要在 CI/CD 流程中包含一个架构配置文件。

在本例中,我们将使用 Node.js、Postgres 和 GitHub。此外,我们还将使用 Heroku,因为它提供了便捷的工具,包括Heroku CI和用于 CI/CD 的部署脚本,以及在出现错误时轻松回滚 Postgres 的功能。如果您需要在 Heroku 上部署 Node.js 和 Postgres 方面的帮助,请参阅以下快速指南

以下是我们示例的相关代码。我们将创建一个包含单个表的简单数据库,以及一个在加载时写入该数据库表的 Node.js 文件。

数据库创建 SQL(我们只有一个简单的表):

CREATE TABLE users (
   id           integer PRIMARY KEY,
   firstname    varchar(40) NOT NULL,
   lastname     varchar(40) NOT NULL,
   enrolled     char(1) NOT NULL,
   created_at   date NOT NULL
);

Enter fullscreen mode Exit fullscreen mode

Node.js

const result = await client.query('INSERT INTO users 
  (id,firstname,lastname,enrolled,created_at) 
  values ($1,$2,$3,$4,$5) ',[1,'Becky','Smith','y',new Date()]);
Enter fullscreen mode Exit fullscreen mode

一旦这些文件被签入 GitHub 并且我们的存储库附加到 Heroku 应用程序,我们就可以在 Heroku 仪表板上启用Heroku CI 工具:

Heroku 仪表板上的 Heroku CI

真正的工作是由Heroku ProcfileHeroku 发布阶段完成的。通过它们,我们可以告诉 Heroku CI 工具在每次创建新版本(换句话说,成功编译)时运行数据库迁移 SQL 文件。以下是我们需要在 Heroku Procfile 中包含的发布行:

release: bash `./release-tasks.sh`
Enter fullscreen mode Exit fullscreen mode

release-tasks 文件的内容包含要运行的 SQL 脚本列表。该列表会随着每次发布而更新,以包含所需的架构修改。对于这个非常简单的示例,它只指向一个脚本:

psql -h <hostname> -d <database> -U <user> -w -f database/migrate.sql
Enter fullscreen mode Exit fullscreen mode

(数据库密码可以作为 Heroku 环境变量提供。)

通常,由于我们使用的是基于迁移的策略,我们会为每组更改添加额外的迁移脚本。为了获得更强大的解决方案,我们可以使用 Liquibase、AlembicFlyway 等工具。这些工具会为您的数据库添加版本控制功能,既可以在版本之间生成必要的更改脚本,又能让您轻松回滚更改。例如,Flyaway 创建的脚本允许您从数据库的任何版本(包括空数据库)迁移到架构的最新版本。

为了启动 CI 工具,我们做了两点修改:删除一个必需的列,并将 JavaScript 代码改为不再引用该列。首先,我们更新 Node.js 中的 SQL 代码,删除该列:

const result = await client.query('INSERT INTO users 
  (id,firstname,lastname,created_at) 
  values ($1,$2,$3,$4) ',[2,'Becky','Smith',new Date()]);
Enter fullscreen mode Exit fullscreen mode

接下来,我们创建一个migrate.sql文件(在上面的Procfile中引用)来修改表并删除列:

ALTER TABLE users DROP COLUMN enrolled;

Enter fullscreen mode Exit fullscreen mode

现在,我们提交代码更改和 SQL 文件,并观察 CI 的神奇之处。首先,集成测试运行。如果您使用的是通用测试框架,那么 Heroku CI 工具很可能与您的测试套件兼容。

测试运行并通过

现在,CI 工具会创建一个新版本并部署应用程序,这将启动 migration.sql 文件。(参见下图中间部分。)

CI 工具部署成功

我们可以通过 Heroku CLI 工具检查数据库来检查该列是否已被删除:

Heroku CI 工具

成功了!“enrolled”列消失了。我们的 CI 工具运行了我们的脚本,并删除了该列。

一些工具(例如 Liquibase)会保存数据库更改的详细列表。这些工具可让您在上述情况下轻松查看最新的更改。

现在,以后每次提交代码或更新的migrate.sql文件时,CI工具都会启动测试。如果测试通过,就会创建一个新版本并将其推送到暂存区。每当有新版本发布时,migrate.sql文件都会针对暂存区数据库运行。

为了演示,我们在这里采用了一种简单的方法,但本可以让这个过程更加健壮。例如,在将新版本迁移到暂存区时,我们可以清除旧版本的数据库,从头创建一个新数据库,运行原始创建脚本和所有迁移脚本,然后在 Procfile 和发布阶段将所有参考数据填充到数据库中。另请注意,为了简单起见,我们不会在事务进行时运行此迁移。在实际场景中,Heroku 建议使用咨询锁来防止并发迁移。

如何进行回滚

即使有最周全的规划和深思熟虑,有时也需要回滚数据库。回滚失败的部署有很多方法。

  • 创建一个可以快速回滚更改的 SQL 文件。(例如,在准备阶段,使用比较实用程序生成脚本。)此文件应包含在部署包中,以便您在出现错误时可以快速运行回滚。
  • 向前滚动(快速推送修复问题的新版本)。
  • 依靠源代码控制和标签或分支来重新创建和部署以前的版本。
  • 恢复数据库的完整备份。(使用数据库自​​带的工具,例如 Postgres 中的 pg_restore。)
  • 使用平台提供的工具,例如Heroku Postgres RollbackHeroku Release Rollback来处理代码。顾名思义,Heroku Postgres Rollback 允许您轻松地将数据库回滚到先前的时间点,快速而自信地将数据库迁移到正常工作的版本。

请注意,所有这些解决方案都有其自身的挑战,例如可能丢失新数据(恢复备份或重新部署)以及引入新的错误。

概括

数据库的变更和迁移可能令人担忧,并可能导致严重的不信任。但是,如果您将数据库置于 CI/CD 管控之下,您不仅可以自信地迁移变更,还能获得更佳的敏捷和 DevOps 体验。这可以很简单,例如使用源代码控制数据库架构,与 DevOps 和 DBA 团队建立良好的流程,并使用现有的 CI 工具测试和迁移数据库。一旦您建立并培训了团队熟悉新流程,未来的变更将比以前的手动流程更加顺畅和自动化。

鏂囩珷鏉ユ簮锛�https://dev.to/heroku/fear-database-changes-get-them-under-control-with-ci-cd-44n1
PREV
您应该阅读的 10 本 Vue JS 书籍 AWS Security LIVE!
NEXT
使用 Rails 和 Kafka 构建面向服务的架构