TT Lab
开始
学习 学习路径 课程

Helm Chart 的制作与发布

仓库中的图表只有 tgz 和 index.yaml

在 TT Lab 中继续学习

一句话总结

Chart 仓库不是服务器软件,而是提供一份 index.yaml 和若干 tgz 下载的静态 HTTP 目录。

为什么需要它

Chart 做得很好,到了分发给团队这一步,大家都会滑一跤。最常见的方式是直接告知 Git 仓库里的路径。helm install myapp ./charts/myapp 运行得很好。但这种方式没有版本。要确认昨天部署的和今天部署的是不是同一个 Chart,只有 git log 这一条路,要回滚还得背提交哈希。在生产故障的正中间,总会遇到回答不了“当时用的 Chart 到底是什么状态”这个问题的时刻。

打包好的 Chart 用一个文件回答了这个问题。catalog-1.1.0.tgz 的名称和版本写在文件名里,内容哪怕有一个字节不同,索引里的 digest 就会不同。部署记录里即使只写着“catalog 1.1.0”,也能重新取出它究竟是什么。

version 与 appVersion——分别变动的两个数字

Chart.yaml 里有两个数字,把这两个当成同一个东西,部署时的对话就会一直对不上。

字段 是什么的版本 什么时候升
version Chart 本身 模板、默认值、依赖变化时
appVersion Chart 所承载运送的软件 应用镜像标签变化时

哪怕只改资源限制默认值,version 也必须升。要部署的镜像没变,所以 appVersion 不变。反过来,只重新构建应用并更换标签,appVersion 会升,如果为了体现这个值动了模板,version 也会一起升。所以这两个值大多是不同的数字,倒是相同才是偶然。

文件名里只有 version。即使用 helm package --app-version 覆盖 appVersion,tgz 名称也不变。helm search repo --versions 或 helm list 把两个值并排显示,原因就在这里。

索引不会自动更新这一事实

index.yaml 是仓库的目录。但这个目录不会因为上传了 tgz 就自动增加。必须由人重新运行 helm repo index <디렉터리>(占位符为目录),而这时该命令只看那个目录里现在有的 tgz,把目录从头重写。

helm package catalog --version 1.2.0 -d stage
helm repo index stage --merge repo/index.yaml   # 옛 목차를 합친다

如果在只有构建产物的 stage 中不带 --merge 就生成索引并上传,目录里就只剩下刚做出的那一个版本。tgz 文件明明还在服务器上,在 helm search 和 helm pull 中却消失了——不是文件被删了,而是从目录里掉出去了,所以看磁盘也看不出原因。

接收的一方也把目录当作缓存。执行 helm repo add 后,会以 ~/.cache/helm/repository/<이름>-index.yaml(占位符为名称)的形式下载一份副本,helm search 读取的不是服务器,而是这份副本。仓库里上传了新版本却在搜索中没出现,大多是没运行 helm repo update。

除了 HTTP 仓库的另一条路

运送 Chart 的方法不只有 index.yaml + tgz 这一种。Helm 3 可以把 OCI registry 用作 Chart 仓库,用 helm push <차트.tgz> oci://<레지스트리>/<경로>(占位符依次为 Chart 压缩包、registry、路径)上传。这种方式没有目录——因为 registry 已经有标签列表了。所以结构上不会发生漏掉 helm repo index --merge 而把旧版本弄丢的事故,并且直接使用与容器镜像相同的认证、权限体系。反过来,很难用 helm search repo 一次性浏览,而且各种 registry 的支持程度也不同。

完整性方面也有机制。用 helm package --sign --key <이름> --keyring <경로>(占位符依次为名称、路径)打包,会在 tgz 旁边同时生成 .prov 文件,接收方用 helm verify 或 helm install --verify 确认签名。索引的 digest 可以确认文件在传输中没有被改变,但它不会说明是谁做的,这是两者作用不同之处。只在公司内部使用的仓库,通常 digest 就够了,而对外分发的 Chart,则倾向于加上签名。

在现场相遇的样子

第一次搭建内部仓库时,人们会去找专用服务器。实际上,只要把一个对象存储桶作为静态网站开放,由 CI 运行 helm package 和 helm repo index --merge 并上传结果就结束了。需要认证的话,在前面放一个反向代理。这种简单既是优点也是陷阱——更新目录的责任完全在流水线上,所以一份写错的索引就会抹掉整个仓库的历史。

在依赖方面,不区分 helm dependency build 和 update 导致的问题很常见。update 会重新解析 Chart.yaml 的版本范围并重写 Chart.lock。如果把范围像 ^1.0.0 这样放开,昨天和今天的构建就可能拉取到不同的 Chart。build 则原样拉取 lock 中写的版本,如果 lock 与 Chart.yaml 不一致,就不拉取而是停下。如果 CI 中使用的是 update,那条流水线做的就不是可重现的构建。

下一项实验要做什么

打包两个版本的 Chart 并生成索引,用 python3 -m http.server 启动那个目录,把它注册为真正的仓库。挑选版本下载,把第三个版本用 --merge 合并上传,然后把它锁定为另一个 Chart 的依赖。最后故意制造声明与锁定不一致的状态,亲眼看 helm dependency build 会以什么话停下。