确认钩子住在 release 外面
目标
亲手确认钩子资源存在于发布之外。添加三个注解,并列比较两种删除策略,计算执行顺序,再观察即使回滚,钩子留下的结果仍会原样保留。
为什么重要
把数据库迁移纳入部署最常见的方法,是使用 pre-upgrade 钩子。但钩子创建的对象不会写入发布 Secret。这会产生两个结果:即使回滚,钩子执行的操作仍然保留;如果不设置删除策略,对象还会不断累积。
由此可以得出实际工作的规则:清理成功的钩子,把失败的钩子保留到下次部署,以便查看日志;把不可逆的架构变更与部署分离,拆成多次执行;为 Job 钩子设置短于 helm 超时的自身上限。最后一条很重要,因为即使 helm 不再等待,Job 仍会继续运行。
环境
这个 Pod 使用 kwok 启动真实的 kube-apiserver,但容器不会实际运行。因此 Job 钩子无法结束,会让发布一直卡住。前六个步骤使用会立即判定为就绪的 ConfigMap 来处理钩子;第 7 步则通过一个值默认关闭 Job,只查看渲染结果。工作目录是 /root/hs-hook,产物放在 /root/hs-hook/out 下。
步骤
- 创建 Chart,并在
hook-migrate.yaml中添加三个钩子注解。 - 安装到
hook-lab,发布名称使用pay,同时查看集群与清单。 - 添加
hook-scratch.yaml,比较两种删除策略。 - 添加
hook-early.yaml,把执行顺序写入/root/hs-hook/out/order.txt。 - 在
tests/smoke.yaml中创建test钩子,确认部署时不会执行。 - 把
--no-hooks渲染结果保存到/root/hs-hook/out/nohooks.yaml。 - 在
hook-migrate-job.yaml中创建带安全措施的 Job 钩子,但默认保持关闭。 - 升级并回滚后,在
/root/hs-hook/out/residue.txt中用四行总结结果。
参考
- 注解值是字符串。如果
hook-weight不加引号,渲染后会成为数字,无法被识别为钩子。 - 第 7 步不要在启用
migrationJob.enabled的状态下升级。Job 无法结束,会使发布卡在pending-upgrade状态。 - 常见错误是在删除策略中加入
hook-failed。对象会在失败瞬间被删除,用于调查原因的日志也随之消失。
添加三个钩子注解
在 /root/hs-hook 中使用 helm create svc 创建 Chart,并删除 svc/templates/tests 目录。然后在 svc/templates/hook-migrate.yaml 中创建 {{ .Release.Name }}-migrate ConfigMap,把 helm.sh/hook 设置为 pre-install,pre-upgrade,把 helm.sh/hook-weight 设置为 -5,把 helm.sh/hook-delete-policy 设置为 before-hook-creation。
钩子不是单独的资源类型,而是添加了注解的普通资源。实际工作中会使用 Job,但这个集群不会实际运行容器,因此 Job 钩子无法结束。所以前六个步骤使用会立即判定为就绪的 ConfigMap 处理钩子。注解值必须是字符串,因此 weight 要用引号括起来。
钩子存在于集群中,却不在清单中
把 Chart 安装到 hook-lab 命名空间,发布名称使用 pay,并分别通过 kubectl 与 helm get manifest 查找钩子 ConfigMap。
命令是 helm install pay ./svc -n hook-lab --create-namespace。安装完成后,kubectl -n hook-lab get cm 会显示钩子 ConfigMap,但 helm get manifest pay -n hook-lab 中没有它,因为发布并不管理钩子资源。这一点就是回滚无法撤销钩子的全部原因。
并列两种删除策略并观察差异
在 svc/templates/hook-scratch.yaml 中创建 {{ .Release.Name }}-scratch ConfigMap。钩子时机与前面相同,weight 为 0,删除策略为 hook-succeeded。然后执行一次升级。
hook-succeeded 会在成功后立即删除对象,而 before-hook-creation 会一直保留到下次部署创建同名钩子之前。升级完成后查看 kubectl -n hook-lab get cm,两者中只会留下一个。失败后能否查看钩子日志,正是在这里产生差异。
使用 weight 决定执行顺序
在 svc/templates/hook-early.yaml 中再添加一个名为 {{ .Release.Name }}-early、weight 为 -10 的 ConfigMap 钩子。然后计算 pre-upgrade 阶段各钩子的执行顺序,把名称逐行写入 /root/hs-hook/out/order.txt。
hook-weight 虽然是字符串,却会按数字排序。值越小越先执行;值相同时,再按资源类型与名称排序。因此实际工作中可把默认值设为 -5,让必须更早执行的钩子使用 -10。不要猜测顺序,应从 helm template 结果中提取注解,排序后确认。
test 钩子不会在部署时执行
在 svc/templates/tests/smoke.yaml 中创建 {{ .Release.Name }}-smoke Pod,把 helm.sh/hook 设置为 test,删除策略设置为 hook-succeeded。然后再执行一次升级。
test 钩子不会介入部署过程,只在调用 helm test 时运行。因此升级完成后,该 Pod 不会存在于集群中,也不会出现在 helm get manifest 中,但渲染结果中会包含它。本步骤就是亲自确认这三者的差异。把测试与部署流程分开,是为了避免测试不稳定时阻塞每次部署。
观察 --no-hooks 会排除什么
在 helm template 后添加 --no-hooks,把渲染结果保存到 /root/hs-hook/out/nohooks.yaml。结果应只排除钩子,主清单保持不变。
--no-hooks 可用于 helm install、upgrade 和 template。事故处理时,如果需要跳过钩子、只部署资源,就会使用它;但这也意味着跳过迁移,因此必须了解后果。保存后,确认不存在任何带钩子的资源,且其余资源数量不变。
为迁移 Job 添加安全措施
在 svc/templates/hook-migrate-job.yaml 中创建 pre-upgrade Job 钩子。通过 values.yaml 的 migrationJob.enabled 开关控制,但默认值必须是 false。Job 应设置 activeDeadlineSeconds(小于 300)、backoffLimit: 0、restartPolicy: Never,删除策略设置为 before-hook-creation,hook-succeeded。
helm 的 --timeout 默认值是 5 分钟,但即使发生超时,Job 仍会在集群中继续运行。 helm 只是停止等待。因此,要用短于该时间的 activeDeadlineSeconds 让 Job 自行先结束。不要在删除策略中加入 hook-failed,否则失败瞬间日志就会消失。这个集群中的 Job 钩子无法结束,所以默认值必须关闭;启用状态下升级,会让发布卡在 pending 状态。
即使回滚,钩子执行的操作仍会保留
再执行一次升级,然后使用 helm rollback 回滚,并确认钩子 ConfigMap 仍然保留。把确认结果写入 /root/hs-hook/out/residue.txt,共四行:HOOK_ROLLED_BACK、HOOK_IN_MANIFEST、MIGRATION_UNDONE、SAFE_PATTERN。
回滚是把发布管理的对象重新应用为旧清单。钩子创建的内容不在该清单中,因此不会被处理,迁移修改的架构也会继续保留。前三行用 yes 或 no 填写;最后一行填写安全发布不可逆架构变更的模式名称,也就是理论部分所称的扩展与收缩。