依赖与钩子 — 拼装与顺序
一句话总结
依赖项是组装 Chart 的语法,Hook 则是在组装完成的发布中决定“先做什么”的语法。
为什么需要了解这些
假设某个服务需要缓存。直接把缓存清单放进同一个 Chart,眼下很方便。但当第二个服务也需要相同缓存时,复制就开始了;以后需要修改缓存配置时,也没人知道究竟要改多少处。反过来,如果把缓存完全拆成独立发布,就需要有人记住“部署应用之前必须先准备好缓存”这一顺序。
依赖项是两者之间的折中方案。缓存保持为独立 Chart,由父 Chart 以声明方式引入使用。父 Chart 可以通过自己的 values 覆盖子 Chart 的值,也能通过一个条件整体关闭子 Chart。同时,子 Chart 仍然保持可独立安装。
工作原理
依赖项写在父 Chart.yaml 的 dependencies 列表中。每个条目包含 name、version、repository,还可选择添加 condition 与 tags。repository 可以是远程 URL,也可以是以 file:// 开头的本地路径。本地路径以父 Chart 目录为基准解析为相对路径,在无法访问互联网的环境,或多个 Chart 存放在同一仓库的结构中尤其有用。
执行 helm dependency update 会发生两件事:依赖 Chart 以软件包形式放入 charts/,同时生成 Chart.lock。锁文件中记录最终确定的版本与 digest,这个摘要相当于“已声明依赖项列表”的指纹。因此,CI 使用的不是 update,而是 helm dependency build。build 会严格按锁文件重现依赖;如果声明发生变化而锁文件未更新,就会当场失败。这一区分与应用程序世界中的锁文件完全是同一个概念。
向子 Chart 传值的规则很简单:父 values 中,与子 Chart 同名的键下方所写内容,会成为子 Chart 的 .Values 顶层。 如果在 cache: 下写入 replicaCount: 3,子 Chart 会将其视为自己的 .Values.replicaCount。关键是由父 Chart 覆盖值,而不修改子 Chart 的 values.yaml。反过来,需要同时传递给父、子 Chart 的值则放在 global 下。镜像仓库地址、环境名称等整个系统共享的值,就适合放在这里。
启用和关闭有两种方式。condition 只在指定值为真时启用子 Chart,tags 则能一次控制带有同一标签的多个子 Chart。如果二者发生冲突,以 condition 为准。
Hook 的性质不同。Hook 是插入发布生命周期特定时间点的资源,通过注解声明。
| 注解 | 含义 |
|---|---|
helm.sh/hook |
何时执行(pre-install、post-install、pre-upgrade、pre-rollback 等) |
helm.sh/hook-weight |
同一时间点多个 Hook 的顺序,值越小越先执行 |
helm.sh/hook-delete-policy |
何时清理(before-hook-creation、hook-succeeded、hook-failed) |
这里最容易写错的是删除策略。Hook 资源不归发布所有。 因此,如果不指定策略,每次升级后已完成的 Job 都会留在命名空间中,下一次 Hook 试图使用相同名称创建资源时就会发生冲突。
生产现场中的常见情况
第一,关闭后仍然存在。 如果通过条件关闭子 Chart 后,渲染结果中仍能看到它的名称,说明父 Chart 在自己的模板中直接创建了与子 Chart 相关的资源。条件只会关闭子 Chart,不会关闭父 Chart 的模板。
第二,伞形 Chart 的代价。 把多个服务组合在一个伞形 Chart 中,虽然可以一次部署,非常方便,但即使只修改一个服务,也必须发布全部内容。单个发布的故障半径会相应扩大。如果独立部署很重要,更适合把它们保持为子 Chart,并分别发布。
第三,Hook 不会随回滚而回滚。 使用 Hook 执行迁移 Job 后,即使部署失败并回滚,数据库模式也不会恢复原状。通过 Hook 执行的任务,应尽量做到可撤销,或重复执行也安全。
下一次实验要做什么
创建 /root/helm/deps/cache 子 Chart 和 /root/helm/deps/platform 父 Chart,并通过 file:// 本地仓库连接。确认锁文件生成,在父 Chart 中覆盖子 Chart 的值,再通过条件关闭和开启子 Chart。用标签确认 global 值同时到达两侧,最后添加 Hook Job,制作伞形 Chart 渲染报告。