如何解决 OpenShift 集群中几个 pods 中的 liquibase waiting for changelog lock 问题?

How to solve liquibase waiting for changelog lock problem in several pods in OpenShift cluster?

我们支持使用 Spring Boot 以 Java 编写并部署在 OpenShift 中的多个微服务。一些微服务与数据库通信。我们经常在单个部署中 运行 多个 pods 中的单个微服务。当每个微服务启动时,它会启动 liquibase,它会尝试更新数据库。问题是有时一个 pod 在等待更改日志锁时会失败。 当这种情况发生在我们的生产 OpenShift 集群中时,我们预计其他 pods 在重新启动时会失败,因为更改日志锁定问题存在同样的问题。所以,在最坏的情况下,所有 pods 将等待解除锁定。

我们希望 liquidbase 在每个 pod 启动时自动准备我们的数据库模式。

把这个逻辑放在每个微服务里好不好?当出现liquidbase changelog lock问题时,我们如何自动解决问题?我们是否需要将数据库准备逻辑放在单独的部署中?

所以也许我应该解释一下我的问题。就微服务架构而言,运行 数据库迁移的最佳方式是什么?也许我们不应该在每个 pod 中使用数据库迁移?也许最好通过单独部署来完成,或者通过一些根本不在 OpenShift 中的额外 Jenkins 工作来完成?

当 Liquibase 在 spring-boot 应用程序部署期间启动时,它执行(在非常高的级别上)以下步骤:

  1. 锁定数据库(在 databasechangeloglock 中创建一条记录)
  2. 执行更改日志;
  3. 解除数据库锁;

因此,如果您在 Liquibase 处于第 1 步和第 3 步之间时中断应用程序部署,那么您的数据库将保持锁定状态。因此,当您尝试重新部署您的应用程序时,Liquibase 将失败,因为它会将您的数据库视为已锁定。

因此您必须在再次部署应用程序之前解锁数据库。

我知道有两个选项:

  1. 清除 databasechangeloglock table 或将 locked 设置为 false。即 DELETE FROM databasechangeloglockUPDATE databasechangeloglock SET locked=0
  2. 执行liquibase releaseLocks命令。您可以找到有关它的文档 here and here.

最后我们在另一个项目中通过去除微服务启动时的 liquibase 迁移解决了这个问题。现在,单独的 Jenkins 作业应用迁移,并在迁移应用后单独 Jenkins 作业部署和启动微服务。所以现在微服务本身不应用数据库更新

我们运行 liquibase 迁移作为 Kubernetes 中的初始容器。 运行 Liquibase 在微服务中的问题是,如果就绪探测在配置的超时之前不成功,Kubernetes 将终止 pod。在我们的例子中,这有时会在大型数据库迁移期间发生,这可能需要几分钟才能完成。 Kubernetes 将终止 pod,使 DATABASECHANGELOGLOCK 处于锁定状态。使用 init-containers 你不会有这个问题。有关详细说明,请参阅 https://www.liquibase.org/blog/using-liquibase-in-kubernetes

更新 请看一下这个 Liquibase 扩展,它通过使用数据库锁替换了 StandardLockService:https://github.com/blagerweij/liquibase-sessionlock

此扩展使用 MySQL 或 Postgres 用户锁定语句,当数据库连接关闭时(例如,当容器意外停止时),这些语句会自动释放。使用扩展所需的唯一事情是向库添加依赖项。 Liquibase 会自动检测改进后的 LockService

我不是图书馆的作者,但我在寻找解决方案时偶然发现了图书馆。我通过将库发布到 Maven Central 来帮助作者。目前支持 MySQL 和 PostgreSQL,但支持其他 RDBMS 应该相当容易。

我们在我的公司也采用相同的方法 Liquibase suggests with Init Containers 设法解决了这个问题,但我们没有使用新容器和 运行 通过 Liquibase CLI 进行 Liquibase 迁移,而是重用现有的Spring 启动服务设置但只是执行 Liquibase 逻辑。我们已经创建了一个替代 main class,可以在入口点中使用它来使用 Liquibase 填充数据库。

InitContainerApplication class 带来启动应用程序和设置 Liquibase 所需的最低配置。

典型用法:

entrypoint: "java -cp /app/extras/*:/app/WEB-INF/classes:/app/WEB-INF/lib/* com.backbase.buildingblocks.auxiliaryconfig.InitContainerApplication"

这里是class

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.context.ApplicationContext;

@SpringBootConfiguration
@ImportAutoConfiguration(InitContainerAutoConfigurationSelector.class)
public class InitContainerApplication implements ApplicationRunner {

    @Autowired
    private ApplicationContext appContext;

    public static void main(String[] args) {
        SpringApplication.run(InitContainerApplication.class, args);
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        SpringApplication.exit(appContext, () -> 0);
    }

}

这里是Init Container的用法:

spec:
  initContainers:
    - name: init-liquibase
      command: ['java']
      args: ['-cp', '/app/extras/*:/app/WEB-INF/classes:/app/WEB-INF/lib/*',
                 'com.backbase.buildingblocks.auxiliaryconfig.InitContainerApplication']