仓库中的图表只有 tgz 和 index.yaml
一句话总结
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 会以什么话停下。