如果你应用程序中使用的是关系型数据库,随着时间的推移你的数据库结构必然或多或少会有一些变化。在部署你新版本的应用之前,必须确保数据库的结构是最新的,本文不是关于如何生成和管理 schema 迁移的,而是如何将其作为 Kubernetes 上应用部署过程的一部分来完成迁移。
在应用中执行迁移
我们可以将自动迁移程序作为服务启动的一部分而存在,这看上去是可行的,可以保证服务不会在迁移之前就启动,并消除了在过时的 schema 结构上运行应用的风险。
但是当我们将应用程序跑在 Kubernetes 集群上的时候,这会带来一些其他问题。
- 每次创建或重启新的 Pod 时候,它都会尝试再次执行迁移操作,如果你的迁移脚本写得足够优秀,是可以规避这些问题,但是这并不是一个很好的设计。
- 如果迁移需要一段比较长的时间(比如在一个大表上添加一列),你的 Pod 可能会错过就绪状态的检查,在迁移完成之前会杀掉容器重启。我们当然可以增加 Pod 的初始延迟时间,但是这个时间是没办法控制的,而且也会导致正常运行的时候等待大量的时间。
使用 init 容器
Init 容器[1]是指在你的 Pod 中的常规容器启动之前将运行完成的容器。这对于在你的应用程序启动之前执行任何需要的设置都是非常有用的(例如下载一些配置文件)。使用 init 容器来运行数据库迁移似乎是一个更好的方式,但我们将面临与在应用程序中启动的方式相同的问题。
- 如果同时创建多个 Pods,则可能会同时运行多个 init 容器。
- 每次创建新的 Pod 时,init 容器都会运行。
使用 Helm Hooks 执行任务
Kubernetes jobs
首先,我们来看看 Kubernetes 中的 job 资源对象。Jobs 允许我们运行1个或多个 Pod 来完成任务。和 Deployment 中的 Pod 不同,Job 中的 Pod 在退出时不会重新创建(除非它们失败,并且 Job 被配置为在失败时重新启动)。
这对于运行一个只需要运行一次就能完成的任务来说是非常有用的,而运行数据库迁移显然就是一个一次性的任务。
现在要做的是在部署应用程序的新版本之前自动运行一个 Job 来执行迁移任务。
Helm release 生命周期
Helm[2] 允许你将你的应用程序定义的所有 K8S 资源清单打包在一个Chart 中一次性部署,并使用模板来定制每个部署(例如允许在多个环境中用不同的参数部署同一个 Chart)。
Helm 还提供了 Hooks[3] 钩子来决定部署过程中何时创建资源,我们可以利用这一点,在创建或更新任何资源之前执行迁移任务。下面是我们的迁移任务的示例:
代码语言:javascript复制apiVersion: batch/v1
kind: Job
metadata:
name: "{{ .Release.Name }}"
labels:
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "-1"
"helm.sh/hook-delete-policy": hook-succeeded
spec:
template:
metadata:
name: "{{ .Release.Name }}"
spec:
restartPolicy: Never
containers:
- name: db-migrations # 我们需要构建一个专门用于数据库迁移的docker镜像
image: "database-migrations"
Hooks 是通过 annotations 来进行配置的:
"helm.sh/hook":pre-install,pre-upgrade
是告诉 helm 在安装之前和升级应用程序之前执行这个 Job 任务"helm.sh/hook-weight": "-1"
是用于定义 helm 应该以何种顺序创建实现相同钩子的资源helm.sh/hook-delete-policy: hook-succeeded
是告诉 helm 在 Job 执行成功后删除该 Job 资源对象。
其余部分就是一个普通的 Job 资源对象模板。
问题
Helm hooks 的缺点
你的迁移任务很可能需要一些配置和或 Secret 才能运行(至少需要db 服务器地址和凭证),但是这个 Job 资源将在 Chart 中所有其他资源之前创建。这意味着我们的 Job 将无法挂载 Chart 创建的ConfigMap 资源。要解决这个问题我们可以创建一个实现相同钩子的 ConfigMap,如下所示:
代码语言:javascript复制apiVersion: v1
kind: ConfigMap
metadata:
name: db-migrations
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "-10" # 使用一个比迁移任务更小的权重
"helm.sh/hook-delete-policy": hook-succeeded
data:
DB_ADDR: {{ .Values.db.addr }}
DB_NAME: {{ .Values.db.name }}
我们可以配置这个 hook 的权重比迁移任务的权重更小,这样就可以在迁移任务执行之前创建这个 ConfigMap 资源,这样就可以在 Job 中挂载这个 ConfigMap 来获取配置信息了。
部署策略和回滚
默认情况下,Kubernetes Deployment 默认更新策略是滚动更新。这意味着在部署过程中,将有 Pod 同时运行应用程序的上一个和新版本。这将要求所有的迁移至少要向后兼容以前的版本。
如果你需要使用 helm rollback
命令回滚到应用程序的以前版本,你重新部署的版本的迁移任务也会再次运行。在回滚期间试图向下迁移到以前版本的数据库结构,很可能会导致现有的 Pods 运行失败。最后,如果你必须回滚到一个更老的版本,你需要确保当前的数据库结构与你计划回滚到的版本向后兼容。
原文链接:https://itnext.io/database-migrations-on-kubernetes-using-helm-hooks-fb80c0d97805
参考资料
[1]
Init 容器: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/
[2]
Helm: https://helm.sh/
[3]
Hooks: https://helm.sh/docs/topics/charts_hooks/