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

Helm Chart 的制作与发布

--set 不是便捷选项,而是一门小语言

在 TT Lab 中继续学习

一句话总结

--set 不是传值的捷径,而是有语法和类型规则的小语言,而且与用 values 文件传同样的值相比,结果可能不同。

为什么这会成为问题

假设部署脚本里有这样一行。

helm upgrade api ./api --set image.tag=8

渲染结果是 image: registry.local/api:8,看起来没有任何问题。但如果 Chart 的模板是这个样子,事情就不同了。

image: {{ .Values.image.repository }}:{{ .Values.image.tag }}

--set image.tag=8 会把值作为数字 8 放进去。上面的模板是字符串拼接,所以结果看起来一样,但在单独输出标签的位置(注解、标签、ConfigMap 的 data)上,数字会原样输出,API 服务器会拒绝。反过来,如果在 values 文件中写 tag: "8",放进去的就是字符串。写同一个值的两种方法会产生不同的类型,这是本主题的核心。

--set 决定类型的规则

在 Helm v3.16 上亲自确认的结果如下。

写下的值 放进去的类型
8 数字
1.10 字符串(有小数点就不会被读成整数)
0755 字符串(因为开头的 0 必须保留)
true 布尔值
null 删除该键

这里经常产生误解。笼统地背成“长得像版本的值很危险”,就会把 1.10 和 8 当成一回事,而实际上有问题的只有会被读成整数的值和会被读成布尔值的值。所以需要 --set-string 的位置也在这里。与其背规则,不如把值转储渲染一次,亲眼确认类型更快。

null 要特别小心。与放入空字符串的 --set key= 不同,--set key=null 会把键本身去掉。如果 Chart 只用 if .Values.x 来分支,两者的行为相同,但在按键是否存在来判断的地方就会分开。

语法——点、逗号、花括号、方括号、反斜杠

--set image.repository=registry.local/web,replicas=5   # 점은 깊이, 쉼표는 구분
--set 'args={alpha,beta,gamma}'                        # 중괄호는 리스트 통째로
--set 'args[0].name=first,args[0].value=1'             # 대괄호는 원소 자리
--set 'nodeSelector.kubernetes\.io/os=linux'           # 역슬래시는 점의 의미를 끈다

该代码块中的韩文注释依次说明:点表示层级深度,逗号表示分隔;花括号表示整个列表;方括号表示元素位置;反斜杠使点失去原有含义。

键名里带点,在 Kubernetes 中非常常见。kubernetes.io/os、app.kubernetes.io/name、prometheus.io/scrape 都是这样。如果不转义,就会悄悄生成 kubernetes 下面叫 io/os 的嵌套 map,渲染成功,但想要的标签却没有加上。

复杂的值另有专用选项。--set-json 把值原样按 JSON 接收,类型也按意图放入;--set-file 把文件的内容作为值放入。传整个证书正文或配置文件时,--set-file 是唯一现实的方法。如果写成 --set config.ca=/path/ca.pem,路径字符串就成了值。

以后能知道是用什么部署的吗

--set 真正的成本不是语法也不是类型,而是不留记录。部署结束后问“现在生产用什么值在运行”,用 -f values-prod.yaml 部署的团队,打开仓库里的一个文件就能回答。用 --set 部署的团队则要从 release 里取出来。

helm get values api            # 사용자가 준 값만
helm get values api --all      # 차트 기본값까지 합쳐진 최종 값

该代码块中的两处韩文注释说明:第一条只显示用户给出的值,第二条显示合并了 Chart 默认值的最终值。

容易觉得能取出来就没问题,但这些值没有经过代码评审,没有任何地方记录是谁、为什么那样定的,集群一消失它们也随之消失。所以值超过两三个,还是挪到文件里更好。值得留下的 --set 是每次部署必定不同的一两个——通常是镜像标签。

而恰恰是那个标签,是出类型事故的位置。用 --set-string image.tag=$TAG 钉死,或者在 Chart 的模板里用 {{ .Values.image.tag | quote }} 包起来,无论传值的一方怎么做都安全了。传值的人有很多,Chart 只有一处,所以在 Chart 一侧防御成本更低。

在现场相遇的样子

部署脚本里的 --set 开始变长时,两个问题会一起到来。第一,没有记录传过什么。虽然可以用 helm get values 取出来,但仓库里没有,所以没经过代码评审。第二,shell 引号和 Helm 语法叠在一起,读起来变得困难。花括号和方括号 shell 也会特殊处理,所以要用单引号括起来,漏掉这一点,就会把 shell 先展开的结果传给 Helm。

所以实际工作中的界线大体是这样——有结构的值放进 values 文件,只有每次部署不同的一两个值才用 --set。 镜像标签是每次部署都不同的典型值,所以常常留在 --set 里,而那个位置恰恰是出类型事故的位置。对标签使用 --set-string,或者在 Chart 一侧加上 | quote,无论从哪边传进来都安全。如果做 Chart 的人能做什么防御,先做那一边更好——传值的人有好几个,而 Chart 只有一处。

下一项实验要做什么

先做一个把传入的值原样以 JSON 输出的 Chart,然后把 --set 的语法一个个试过去。转义键里的点,确认整数和带小数点值的类型如何分开,使用 --set-json 和 --set-file,用 null 删除键。最后把同样的两个值,用 values 文件、--set、--set-string 三种方式传入,把渲染结果并排比较。