渲染结果不是 YAML——作用域与空白
目标
分别故意制造这几件事:with、range 改变点;空白修剪把行粘在一起;去掉引号的值变成数字和布尔值,然后进而学会用 --debug、required、fail、helm lint --strict 来拦住它们。
为什么重要
模板事故大部分不是因为不懂函数,而是因为结果不再是 YAML。所以错误消息不会指出原因。mapping values are not allowed in this context 是 YAML 解析器的话,不是模板的话,所以无论怎么盯着那一行看,也看不出罪魁祸首是一个 -}}。同样,with 里说 .Release 是 nil 的消息,在不知道“点被改变了”这一事实之前也读不懂。再加上引号问题,渲染本身好好的,集群却拒绝——这是在部署流水线最晚的位置爆出来的一类。把这四种各亲手做一遍,以后只看两行消息就能决定该看哪里。
步骤
- 创建
/root/hc-scope/frontierChart(名称frontier,版本0.1.0),在values.yaml中放app(nameportal、teamcore、port8080)、notes(ownersre、runbookwiki/portal)、envs(STAGE、PROD)、release(tag"1.10"、enabled"no")。templates/base.yaml是名称为.Values.app.name、data.port是加了引号的端口的 ConfigMap。把helm template web /root/hc-scope/frontier的结果保存到/root/hc-scope/out/base.yaml。 - 创建
/root/hc-scope/frontier/templates/scoped.yaml。名称为<app.name>-scoped,在其下用{{- with .Values.app }}块包起来,在块里把data.release写成{{ .Release.Name }},data.team写成{{ .team }}。渲染会失败——把那个输出保存到/root/hc-scope/out/with-error.txt。 - 在
/root/hc-scope/frontier/templates/scoped.yaml中保持with块不变,只修改data.release,让 release 名称能被渲染出来。把渲染结果保存到/root/hc-scope/out/scoped.yaml(release 名称web)。data 里必须出现release: web和team: core两行。 - 创建
/root/hc-scope/frontier/templates/envs.yaml。名称为<app.name>-envs,把.Values.envs以索引和值两个变量接收并遍历,在data中每行输出一条<소문자 환경이름>: "<인덱스>-<app.port>"(占位符依次为小写环境名称、索引、app.port)。渲染结果中必须出现stage: "0-8080"和prod: "1-8080"。把结果保存到/root/hc-scope/out/envs.yaml。 - 另外创建
/root/hc-scope/brokenChart(名称broken),在values.yaml中放app(nameportal、teamcore)和notes(ownersre、runbookwiki/portal)。templates/portal.yaml的样子是:在annotations:下面一行用{{- toYaml .Values.notes | indent 4 -}}插入注解,其后是labels:。渲染会失败——把普通输出保存到/root/hc-scope/out/ws-error.txt,把加了--debug的输出保存到/root/hc-scope/out/ws-debug.txt。这个 Chart 不要修,原样保留。 - 把
/root/hc-scope/broken复制为/root/hc-scope/fixed(Chart 名称改为fixed),只修改templates/portal.yaml,让渲染成功。注解块要在annotations:下缩进四个空格,分成两行,labels.team必须是core。把结果保存到/root/hc-scope/out/fixed.yaml。 - 创建
/root/hc-scope/frontier/templates/release.yaml。名称是<릴리스이름>-release(占位符为 release 名称),data.tag不加引号地插入.Values.release.tag,data.enabled插入.Values.release.enabled。只渲染这个模板,把经过yq -o=json的结果保存到/root/hc-scope/out/quote-yaml12.json,把这份渲染输入kubectl apply --dry-run=server的输出保存到/root/hc-scope/out/quote-error.txt。然后给两个值加上quote修正,把再次通过服务端验证的输出保存到/root/hc-scope/out/quote-ok.txt。 - 创建
/root/hc-scope/frontier/templates/guard.yaml。端口小于 1024 时用fail停下,data.team用required在值缺失时停下。用正常值时必须输出名称为<app.name>-guard的 ConfigMap。把用--set app.port=80渲染的输出保存到/root/hc-scope/out/guard-fail.txt,把用--set app.team=null渲染的输出保存到/root/hc-scope/out/guard-required.txt,并把不带选项渲染的完整结果保存到/root/hc-scope/out/guard-ok.yaml。 - 创建
/root/hc-scope/lintbadChart(名称lintbad),把templates/cm.yaml中 ConfigMap 的名称设为Bad_Name。把helm lint的输出和退出码保存到/root/hc-scope/out/lint-plain.txt,把helm lint --strict的输出和退出码保存到/root/hc-scope/out/lint-strict.txt。在两个文件的最后一行附上exit=<종료 코드>(占位符为退出码)。同一个 Chart,通过与否却不同,就是这一步的答案。
参考
helm template --debug会把无法解析为 YAML 的结果也原样显示- 用
helm template <릴리스> <차트> -s templates/<파일>(占位符依次为 release、Chart、文件)只渲染一个模板 - 在键的正下方插入块时,用
nindent而不是indent - 常见错误:在
with块内原样使用.Release、.Chart——根是$ - 常见错误:标签末尾的
-}}把下一行粘到了上一行 - 官方文档:https://helm.sh/docs/chart_template_guide/control_structures/ · https://helm.sh/docs/chart_template_guide/variables/ · https://helm.sh/docs/chart_template_guide/yaml_techniques/
先铺好值结构,渲染一次
创建 /root/hc-scope/frontier Chart(名称 frontier,版本 0.1.0),在 values.yaml 中放 app(name portal、team core、port 8080)、notes(owner sre、runbook wiki/portal)、envs(STAGE、PROD)、release(tag "1.10"、enabled "no")。templates/base.yaml 是名称为 .Values.app.name、data.port 是加了引号的端口的 ConfigMap。把 helm template web /root/hc-scope/frontier 的结果保存到 /root/hc-scope/out/base.yaml。
用 helm create 创建会附带骨架模板,所以本实验自己创建目录和文件更干净。需要的只有 Chart.yaml、values.yaml、templates/ 三样。release 名称在本实验中始终用 web。
在 with 里找不到 .Release
创建 /root/hc-scope/frontier/templates/scoped.yaml。名称为 <app.name>-scoped,在其下用 {{- with .Values.app }} 块包起来,在块里把 data.release 写成 {{ .Release.Name }},data.team 写成 {{ .team }}。渲染会失败——把那个输出保存到 /root/hc-scope/out/with-error.txt。
with 在条件为真时会改变点(.)所指的对象。 在块内,点不再是根,而是 .Values.app。所以能找到 .team,却没有 .Release。错误会输出到标准错误,所以要用 2>&1 一并接收。请仔细读消息里“是什么为 nil”。
用美元符号重新抓住根
在 /root/hc-scope/frontier/templates/scoped.yaml 中保持 with 块不变,只修改 data.release,让 release 名称能被渲染出来。把渲染结果保存到 /root/hc-scope/out/scoped.yaml(release 名称 web)。data 里必须出现 release: web 和 team: core 两行。
模板开始时的根上下文绑定在 $ 上,即使在 with 或 range 里也不会改变。这是在保留缩小到 .Values.app 的便利的同时,只想取出根的东西时用的旋钮。
在 range 里也同时使用根的值
创建 /root/hc-scope/frontier/templates/envs.yaml。名称为 <app.name>-envs,把 .Values.envs 以索引和值两个变量接收并遍历,在 data 中每行输出一条 <소문자 환경이름>: "<인덱스>-<app.port>"(占位符依次为小写环境名称、索引、app.port)。渲染结果中必须出现 stage: "0-8080" 和 prod: "1-8080"。把结果保存到 /root/hc-scope/out/envs.yaml。
像 range $i, $e := .Values.envs 这样接收两个变量,就可以同时使用索引和元素,即使点变了,$i、$e 也原样有效。根的端口用 $.Values.app.port 取出。转小写的函数是 lower。
一处空白修剪就会让 YAML 崩溃
另外创建 /root/hc-scope/broken Chart(名称 broken),在 values.yaml 中放 app(name portal、team core)和 notes(owner sre、runbook wiki/portal)。templates/portal.yaml 的样子是:在 annotations: 下面一行用 {{- toYaml .Values.notes | indent 4 -}} 插入注解,其后是 labels:。渲染会失败——把普通输出保存到 /root/hc-scope/out/ws-error.txt,把加了 --debug 的输出保存到 /root/hc-scope/out/ws-debug.txt。这个 Chart 不要修,原样保留。
indent 4 不换行,只放四个空格,而末尾的 -}} 会删掉紧随其后的换行和空白。 所以注解块会粘在 annotations: 所在的行上,下一行的 labels: 也会粘在上一行末尾。错误消息是 YAML 解析器的话,不会指出原因——加上 --debug,Helm 会把坏掉的结果原样显示出来。 在那里用眼睛确认是哪一行粘在了一起。
用 nindent 把换行也交出去
把 /root/hc-scope/broken 复制为 /root/hc-scope/fixed(Chart 名称改为 fixed),只修改 templates/portal.yaml,让渲染成功。注解块要在 annotations: 下缩进四个空格,分成两行,labels.team 必须是 core。把结果保存到 /root/hc-scope/out/fixed.yaml。
nindent 4 先放入换行再缩进四个空格。前面的 {{- 只需要负责删除模板标签之前的空白,末尾不要写 -}}。当作规则来记会方便——在键正下方一行插入块时,永远用 nindent。
漏掉了引号,集群拒绝了
创建 /root/hc-scope/frontier/templates/release.yaml。名称是 <릴리스이름>-release(占位符为 release 名称),data.tag 不加引号地插入 .Values.release.tag,data.enabled 插入 .Values.release.enabled。只渲染这个模板,把经过 yq -o=json 的结果保存到 /root/hc-scope/out/quote-yaml12.json,把这份渲染输入 kubectl apply --dry-run=server 的输出保存到 /root/hc-scope/out/quote-error.txt。然后给两个值加上 quote 修正,把再次通过服务端验证的输出保存到 /root/hc-scope/out/quote-ok.txt。
用 helm template <릴리스> <차트> -s templates/release.yaml(占位符依次为 release、Chart)可以只渲染一个模板。没有引号的话,1.10 会变成数字而丢掉末尾的 0,no 按 YAML 1.1 规则会变成假。ConfigMap 的 data 只接收字符串,所以 API 服务器会以类型转换错误拒绝。如果值是给人读的字符串,就要知道加 quote 是基本做法。
值不对就在渲染阶段停下
创建 /root/hc-scope/frontier/templates/guard.yaml。端口小于 1024 时用 fail 停下,data.team 用 required 在值缺失时停下。用正常值时必须输出名称为 <app.name>-guard 的 ConfigMap。把用 --set app.port=80 渲染的输出保存到 /root/hc-scope/out/guard-fail.txt,把用 --set app.team=null 渲染的输出保存到 /root/hc-scope/out/guard-required.txt,并把不带选项渲染的完整结果保存到 /root/hc-scope/out/guard-ok.yaml。
fail 的条件可以自己写,所以用来拦住“有值但不合理的值”,required 则拦住“根本没有值的时候”。两条消息都要写成人读了就能立刻改的样子。--set app.team=null 会删掉那个键——与空字符串不同。数字比较要像 lt (int .Values.app.port) 1024 这样先过一次 int。
让警告被当作错误处理
创建 /root/hc-scope/lintbad Chart(名称 lintbad),把 templates/cm.yaml 中 ConfigMap 的名称设为 Bad_Name。把 helm lint 的输出和退出码保存到 /root/hc-scope/out/lint-plain.txt,把 helm lint --strict 的输出和退出码保存到 /root/hc-scope/out/lint-strict.txt。在两个文件的最后一行附上 exit=<종료 코드>(占位符为退出码)。同一个 Chart,通过与否却不同,就是这一步的答案。
Kubernetes 对象名称必须遵守小写 RFC 1123 规则,所以大写字母和下划线会变成警告。默认 lint 给出警告后仍以 0 结束,而严格模式把警告算作失败。CI 里使用严格模式,这样的名称就到不了部署。退出码要在命令之后马上用 $? 读取。