钩子住在 release 外面
一句话总结
Hook 资源不受 release 管理。 因此,回滚时它不会消失;如果没有设置删除策略,还会不断堆积在集群中。
为什么需要这些知识
把数据库迁移纳入部署,最常见的方式是使用 pre-upgrade Hook Job。但这个 Job 并不是 release 的一部分。Helm 会应用 Hook、等待它完成,然后再应用主要 manifest。Hook 创建的 Job 对象不会进入 release Secret。
这会产生两个结果。
- 即使回滚,Hook 已经执行的操作仍会保留。 迁移修改过的 schema 不会恢复。
- Job 对象会持续堆积。 如果不设置
helm.sh/hook-delete-policy,每次部署都会多出一个。
它是如何运作的
metadata:
annotations:
"helm.sh/hook": pre-upgrade,pre-install
"helm.sh/hook-weight": "-5" # 작을수록 먼저
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
hook-delete-policy 的三个值含义不同。
| 值 | 何时删除 |
|---|---|
before-hook-creation |
下一次部署创建同一 Hook 之前(保留失败的 Job,便于调查) |
hook-succeeded |
成功后立即删除 |
hook-failed |
失败后立即删除(日志会消失——通常不是好选择) |
生产环境常用的默认形式是 before-hook-creation,hook-succeeded。成功项会被清理,失败项则保留到下一次部署,以便查看日志。
常见误解
误以为 Hook 失败就会回滚。 pre-upgrade Hook 失败时,upgrade 会中断,release 会保持 failed 状态。使用 --atomic 时会触发回滚,但已经执行的迁移不会恢复。
不应放进 Hook 的工作
Hook 很方便,因此人们容易把各种工作都放进去。其中有三类不应该这样做。
耗时很长的数据迁移。 移动数百万行数据会让部署停留数十分钟。在此期间,release 会被锁定为 pending-upgrade,阻止其他部署。此类工作应与部署分离,作为独立 Job 运行,并让应用程序能够同时读取两种 schema。
通知外部系统。 使用 post-install Hook 向 Slack 发送通知很常见,但只要 Hook 失败,整个部署就会显示失败。没有必要仅因通知未发送,就把部署视为失败。这类工作应放在 CI 侧。
测试。 helm test 是独立命令,使用 helm.sh/hook: test。如果把它混进部署过程,测试稍有波动就会阻止部署。
Hook 卡住时,部署也会停止
如果 Hook Job 一直不结束,helm upgrade 就会持续等待。--timeout 默认值为 5 分钟,超过后将判定失败。但即使已经超时,Job 仍会继续在集群中运行。 Helm 只是停止等待,并不会终止 Job。
这会产生危险情况。判定失败后重新部署时,前一个迁移可能仍在运行,第二个迁移又会开始。如果同一张表上同时执行两个 ALTER,锁等待可能导致整个服务停摆。
可以通过三项措施阻止这种情况。
- 为 Job 设置
activeDeadlineSeconds。 将它设得比 Helm 超时时间更短,Job 就会先自行结束。 - 设置
backoffLimit: 0。 迁移操作通常无法安全重试。 - 使用迁移工具的锁。 Flyway、Liquibase 和 Alembic 都有锁表,让第二次执行等待。自行编写脚本时,可使用
pg_advisory_lock。
spec:
activeDeadlineSeconds: 240 # helm --timeout 5m 보다 짧게
backoffLimit: 0
template:
spec:
restartPolicy: Never
Hook 与普通 manifest 的顺序
同一个 Chart 混合使用 Hook 与普通资源时,很容易混淆顺序。实际顺序如下。
pre-install 훅 → 일반 매니페스트 적용 → post-install 훅
(weight 순) (Helm 의 종류별 순서) (weight 순)
这里有一个常见陷阱:pre-install Hook 会在 Secret 和 ConfigMap 尚未存在时运行。 如果 Hook Job 引用了 release 的 Secret,就会因“没有这个 Secret”而失败。Hook 所需的值必须由 Hook 自己创建,例如在同一个 Hook 组中通过更低的 weight 创建 Secret,或者预先在 release 之外创建。
hook-weight 虽然是字符串,却按数值排序。 设置 "10" 和 "9" 时,9 会先执行。使用负数的惯例由此而来:把 -5 作为默认值,需要更早执行的项目则设为 -10。
生产环境中真正重要的事
不可逆的迁移必须与部署分离。删除字段的变更应分三次部署:①添加新字段,同时保持旧代码可运行;②部署新代码;③删除旧字段。这样,无论在哪一步回滚,前后版本都能正常承受。这称为扩展—收缩(expand-contract)模式。