Kubernetes 环境中如何进行数据库迁移?

52 阅读4分钟

原文链接:packagemain.tech/p/database-…

原文作者:Julien Singler

译者:菜小鸟魔王

01 引言

随着微服务和 Kubernetes 的兴起,数据库迁移的管理难度达到了前所未有的高度。以往应用程序启动时执行迁移的传统做法,现已不足以应对。

本文将深入探讨在 Kubernetes 环境下如何有效进行数据库迁移,特别关注基于 Golang 的解决策略。

02 Kubernetes 环境下的迁移难题

Kubernetes 环境下,数据库迁移面临以下挑战:

  • 多个副本可能同时启动。

  • 需要协调操作,防止迁移过程并发执行。

  • 应用程序逻辑与迁移逻辑需要明确划分。

03 Golang 常用的迁移工具

正如其他文章中所提及的,以下是一些可用于管理数据库迁移的 Golang 工具:

golang-migrate

  • 使用广泛,支持多种数据库。

  • 提供简洁的命令行界面和 API。

  • 支持多种迁移源,包括本地文件、S3、Google Storage 等。

goose

  • 支持主流的 SQL 数据库。

  • 在处理复杂场景时,允许使用 Go 语言编写迁移脚本。

  • 灵活的版本控制模式。

atlas

  • 一款强大的数据库架构管理工具。

  • 支持声明式和版本控制的迁移方式。

  • 提供完整性检查和迁移脚本的风格检查。

  • 还提供了 GitHub Actions 和 Terraform 的支持。

04 在 Kubernetes 中执行数据库迁移

使用迁移代码直接执行

最简单的方法是在启动服务器之前,直接在主函数中嵌入迁移代码的逻辑。

以下是使用 golang-migrate 的示例:

package main

import (
    "database/sql"
    "fmt"
    "log"
    "net/http"

    "github.com/golang-migrate/migrate/v4"
    "github.com/golang-migrate/migrate/v4/database/postgres"
    _ "github.com/golang-migrate/migrate/v4/source/file"
    _ "github.com/lib/pq"
)

func main() {
    // Database connection parameters
    url := "postgres://user:pass@localhost:5432/dbname"

    // Connect to the database
    db, err := sql.Open("postgres", url)
    if err != nil {
        log.Fatalf("could not connect to database: %v", err)
    }
    defer db.Close()

    // Run migrations
    if err := runMigrations(db); err != nil {
        log.Fatalf("could not run migrations: %v", err)
    }

    // Run the application, for example start the server
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatalf("server failed to start: %v", err)
    }
}

func runMigrations(db *sql.DB) error {
    driver, err := postgres.WithInstance(db, &postgres.Config{})
    if err != nil {
        return fmt.Errorf("could not create database driver: %w", err)
    }

    m, err := migrate.NewWithDatabaseInstance(
        "file://migrations", // Path to your migration files
        "postgres",          // Database type
        driver,
    )
    if err != nil {
        return fmt.Errorf("could not create migrate instance: %w", err)
    }

    if err := m.Up(); err != nil && err != migrate.ErrNoChange {
        return fmt.Errorf("could not run migrations: %w", err)
    }

    log.Println("migrations completed successfully")
    return nil
}

但这种做法可能会引发多种问题,例如迁移过程可能耗时较长,导致 Kubernetes 认为容器启动未成功,进而终止该 Pod。如果尝试在 Go 协程中运行迁移,那么遇到失败又该如何应对?

如果同时启动多个 Pod ,还可能遇到并发执行的问题。

同时,这也意味着迁移脚本需要被打包进 Docker 镜像中。

利用 InitContainer 容器

在 Kubernetes 部署中,通过使用 InitContainer,可以在主应用容器启动之前执行迁移操作。这对于尚未面临扩缩容问题的场景来说,是一个不错的初步解决方案。

如果 InitContainer 执行失败,Kubernetes 的蓝/绿部署(blue/green deployment)将不会继续,原有的 Pod 将保持运行状态。这样做可以避免在没有完成计划迁移的情况下,就部署了新版本的代码。

Example:

initContainers:
  - name: migrations
    image: migrate/migrate:latest
    command: ['/migrate']
    args: ['-source', 'file:///migrations', '-database','postgres://user:pass@db:5432/dbname', 'up']

独立任务执行

可以创建一个 Kubernetes 任务(Job),用于运行数据库迁移,并在部署流程中,在推出应用之前触发该任务。

Example:

apiVersion: batch/v1
kind: Job
metadata:
  name: db-migrate
spec:
  template:
    spec:
      containers:
      - name: migrate
        image: your-migration-image:latest
        command: ['/app/migrate']

还可以将其与 initContainers 结合使用,确保只有在作业成功执行后 pod 才会启动。

initContainers:
  - name: migrations-wait
    image: ghcr.io/groundnuty/k8s-wait-for:v2.0
    args:
      - "job"
      - "my-migration-job"

Helm hook

如果使用 Helm,可以利用其提供的hook 功能在 chart 安装或升级时执行迁移操作。您可以在 Helm chart 中定义 pre-install(预安装)或 pre-upgrade(预升级)hook。

pre-install-hook.yaml:

apiVersion: batch/v1
kind: Job
metadata:
  name: {{ include "mychart.fullname" . }}-migrations
  annotations:
    "helm.sh/hook": pre-install,pre-upgrade
    "helm.sh/hook-weight": "-5"
    "helm.sh/hook-delete-policy": hook-succeeded
spec:
  template:
    spec:
      containers:
        - name: migrations
          image: your-migrations-image:tag
          command: ["./run-migrations.sh"]

Helm 提供了预安装和后安装等多种 hook。

05 Kubernetes 迁移的实践指导原则

  1. 将迁移与应用程序代码分离
  • 为迁移单独构建 Docker 镜像。

  • 利用 Atlas 等工具独立进行迁移管理。

  1. 迁移的版本控制管理
  • 将迁移脚本存放在 Git 版本库中。

  • 采用顺序编号或时间戳的方式进行版本控制。

  1. 惰性迁移
  • 确保迁移可重复执行而无副作用
  1. 回滚策略
  • 为每一次迁移制定并测试回滚方案。
  1. 监控与日志记录
  • 使用 Atlas Cloud 等工具来追踪迁移历史记录。

06 Conclusion

在 Kubernetes 环境下处理数据库迁移,必须精心策划并精准执行。

借助 golang-migrate、goose、atlas 等工具,并依照业界标准操作,我们可以打造出稳健、可扩展和可维护的迁移方案。

务必将迁移逻辑与应用程序代码分离,坚持版本管理,并且部署有效的监控体系,确保在 Kubernetes 架构下数据库的持续进化不会出现波折。