Kubernetes 上的 Liquibase

@高效码农  November 29, 2022

背景、动机和理论

我们有一个带有Kubernetes(k8s)的微服务环境,我们在其中开发了一个需要数据库的服务。我们决定使用Liquibase 来做数据库的迁移管理。

最终,我们意识到某些部署使数据库处于锁定状态。经过一番研究,我们找到了关键。k8s 本身,如果部署过程花费的时间超过了某个时间,如果failureThreshold超过initialDelaySecondsstartupProbe,在我们的示例中,在应用迁移时,它假定 Pod 已处于损坏和/或不一致的状态并终止过程。

Liquibase有一个迁移系统,其中有一个名为 changelog 的配置文件,其中指示了迁移文件以及它们已应用或必须应用的顺序。
为了避免在迁移过程中发生冲突,第一个 pod 锁定了 DB 中表的LOCKED字段。DATABASECHANGELOGLOCK这用于确保一次只有一个 Liquibase 进程在运行。完成后,它将从数据库中释放该字段,下一个将其锁定,依此类推。
如果 k8s 在部署时杀死了这个进程,要么是因为它花费的时间比它自己部署时配置的要长,要么是因为节点 CPU 在那个时候非常饱和并且花费的时间比预期的要长,那么 DB 就会留下那个字段LOCKED到 1,并且没有其他 Pod 可以接管迁移过程。

通过一些研究,我们发现官方 Liquibase 文档本身就谈到了使用 Init Containers 的可能性。 在文档链接中,您可以更详细地了解 Init 容器到底是什么,但简而言之,它们是在 Pod 中产生的容器,具有两个主要特性:

  • 这些容器总是运行完成。也就是说,Kubernetes 不会在进程中间杀死它们。
  • 每个初始化容器必须在下一个启动之前成功完成。在我们的例子中,这将防止迁移过程试图并行并发运行。

现在我们清楚了问题的背景和可能的解决方案,让我们逐步了解我们如何实施该解决方案并将其应用于生产。

案例分析

正如文档本身所说,Liquibase 在 Kubernetes 中有自己的图像,因此似乎使用该图像并指定要运行的文件就足够了。

对于大多数人来说,这个案例研究到这里就结束了,但我们的案例有点特殊。与我们的数据库的通信是通过秘密进行的,因此,我们需要能够安装 liquibase 基础镜像没有的一系列依赖项,例如AWS 命令​​行界面(AWS CLI)。这就是问题的开始。Liquibase 图像给您的权限非常有限,因此我们无法安装 awscli。

下一步是决定使用 Ubuntu 20.04 基础映像并安装我们需要的一切。好吧,有了这个我们真的可以实现我们的目的,但我们意识到 Liquibase 需要 Java 才能运行,这意味着必须管理比预期更多的依赖项。
因此,我们决定使用 JRE 映像,特别是 jdk:11。这样我们就已经拥有了必要版本的 java,并且我们能够毫不费力地安装 liquibase 和 awscli。

同时安装 liquibase 和 awscli

一旦我们dockerfile为 init 容器创建了我们的独立文件,我们需要创建文件,在我们的例子中是 bash,它将在我们的 Init 容器中从头到尾执行。
在我们的示例中,为了运行更新 bd 的 liquibase 命令,我们需要使用秘密 arn 提取所有必要的信息。

运行 liquibase 命令

这是一个非常重要的观点。如果我们已经有我们的服务在生产运行,而我们要做的只是简单的改变这个方法要使用的迁移系统,我们必须考虑以下几点:changeLogFile参数的值必须完全相同出现在表的FILENAME字段中DATABASECHANGELOG

否则,它会将更改日志识别为不同的更改日志,并从头开始应用它,这会带来所有问题(从创建现有表时的简单错误,到在生产中覆盖我们的数据)。

现在,我们必须配置 k8s YAML 文件以使用我们的新dockerfile初始容器。假设我们的服务配置了名称“app”。我们决定为我们的 init 容器命名为“migrations”,配置如下所示:

眼镜

有了这个,我们应该能够使用 Init Containers 进行部署。在我们的例子中,我们必须在 Spring 中启动应用程序时禁用迁移,以避免“应用程序”容器在 init 容器之后尝试再次启动迁移。在这个例子中,就像在 application.yml 中指出 liquibase 没有被激活一样简单:

液碱

有了这个,我们应该让我们的 init 容器运行以启动迁移,然后再使用我们的应用程序提升容器。
作为一个注释,我想评论一下,要执行 e2e 测试,我们可以配置我们的application.yml,以便在测试配置文件中,当启动我们在此环境中使用的数据库的应用程序时,它会执行 liquibase 迁移,在我们的例子中是 H2。

结论

在写结论之前,我在写这篇文章后留下了一些时间来验证我们的假设。这样工作一段时间后,我们发现有时 init 容器也会因各种原因而失败,这与部署过程本身无关。但确实,此解决方案在以非常重要的方式应用迁移时最大限度地减少了数据库锁定问题。
因此,尽管这不是一种万无一失的方法,但它比我们之前在 Kubernetes 中的微服务状态有了巨大的改进。因此,如果您的微服务使用 Liquibase 管理迁移,我建议使用 Init Containers 来执行它们。
对于运行迁移时发生数据库崩溃的异常情况,我们仍在努力寻找解决方案。

在 init 容器中运行的 bash 文件中进行抽象迁移管理的好处是我们可以控制 Liquibase。我们发现了一些允许我们list-locksrelease-locks. 所以目前,我们已经创建了一个读取锁的规则,如果自上次活动锁以来超过 15 分钟,我们假设最后一个 init 容器已损坏,我们继续使用 release-locks 释放锁命令。

目前,这是我们的方法,比最初的情况效果更好。我希望您发现本文对您有所帮助,并随时在本文的评论中留下针对同一问题的反馈和/或替代解决方案。



评论已关闭