用模板函数把 values 安全地插进去
目标
使用 template function 将 values 安全写入 manifest,并亲手掌握值缺失、值必填、值为 block 时分别应使用什么工具。
为什么重要
Helm template 不是 YAML editor,而是字符串生成器。只有最终字符串生成后,YAML parser 才会读取它。因此,缩进错两格时,不一定直接报错,整个 block 可能消失或落到错误 parent 下。这正是 toYaml 展开 block 后必须接 nindent 的原因:indent 不会生成前置换行,第一行会直接粘到前一个 key 上。value 设计亦然。default 用于“可以缺省的值”,required 用于“缺失就不能部署的值”。若二者颠倒,要么以错误默认值悄然部署,要么 chart 变得无人可用。最后,value precedence 从低到高为 chart values.yaml、通过 -f 传入的文件(从左到右)、--set;map 会深度合并,list 则整体替换。
步骤
- 在
/root/helm/tpl/labhub-api创建 chart(可从helm create labhub-api开始)。values.yaml中image.repository为nginx、image.tag为"1.27",Chart.yaml中appVersion为"1.26"。然后用helm template labhub-api /root/helm/tpl/labhub-api > /root/helm/tpl/out/base.yaml保存 render。结果必须有 Deployment,container image 为저장소:태그形式,且不能残留花括号或<no value>。 - 将 container image tag 写成
{{ .Values.image.tag | default .Chart.AppVersion }},保存helm template labhub-api /root/helm/tpl/labhub-api --set image.tag="" > /root/helm/tpl/out/default.yaml。即使 tag 为空,image 仍应类似nginx:1.26,不得用latest作为默认值。 - 在
values.yaml中设置ingress.enabled: true与ingress.host: api.labhub.local,并在/root/helm/tpl/labhub-api/templates/ingress.yaml中用required包装 host(例如{{ required "ingress.host 를 반드시 지정하세요" .Values.ingress.host }})。随后用helm template labhub-api /root/helm/tpl/labhub-api --set ingress.host="" > /root/helm/tpl/out/required-error.txt 2>&1故意触发失败并保存输出。文件中必须显示 render 失败消息及缺少的 key。 - 将
values.yaml的resources设置为limits.cpu: 500m、limits.memory: 512Mi、requests.cpu: 100m、requests.memory: 128Mi,在 Deployment container 中以{{- toYaml .Values.resources | nindent 12 }}形式整体传入。重新 render/root/helm/tpl/out/base.yaml后,必须显示 container 的resources.limits.cpu与resources.requests.memory。 - 在
values.yaml中把env设计为map,定义APP_MODE: server、LOG_LEVEL: info、TZ: Asia/Seoul。在 Deployment 中使用range $k, $v := .Values.env生成 containerenvlist。/root/helm/tpl/out/base.yaml中 containerenv必须恰好 3 个,其中一个 name 为LOG_LEVEL。 - 在
/root/helm/tpl/labhub-api/templates/_helpers.tpl中用define创建 chart 标识字符串(name-version),并在 Deploymentmetadata.annotations中添加labhub.io/chart: {{ include "labhub-api.chart" . }}。再在/root/helm/tpl/out/include-note.txt中用一行说明template与include的区别:include 返回字符串,所以可接 pipeline 做后处理(缩进)。 - 在
/root/helm/tpl/values-base.yaml中设置replicaCount: 2、env.LOG_LEVEL: info、image.tag: base;在/root/helm/tpl/values-stage.yaml中设置replicaCount: 4、env.LOG_LEVEL: debug、image.tag: stage。执行helm template labhub-api /root/helm/tpl/labhub-api -f /root/helm/tpl/values-base.yaml -f /root/helm/tpl/values-stage.yaml --set image.tag=cli > /root/helm/tpl/out/merged.yaml。结果replicas为 4,LOG_LEVEL为debug,image tag 为cli。最后在/root/helm/tpl/out/precedence.txt中按从低到高逐行写 precedence:chartvalues.yaml、-f values-base.yaml、-f values-stage.yaml、--set。 - 添加 ConfigMap template,将
values.yaml的configmap 输出为data,并在 Pod templatemetadata.annotations中添加checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}。Pod 级spec.template.spec.securityContext.runAsNonRoot必须为true。将完整 render 保存到/root/helm/tpl/out/final.yaml(至少 3 个 object、至少 3 个 container env、无<no value>)。同时保存helm lint /root/helm/tpl/labhub-api > /root/helm/tpl/out/lint.txt,且不能有[ERROR]。
参考
- 每个实验会启动新 Pod,其他实验的 chart 不会保留。本实验也要在
/root/helm/tpl下从头创建 chart,这体现 chart 是可复现 package。 - 使用
helm template ... -s templates/deployment.yaml可只查看一个文件,添加--debug可看到失败 render 的中间结果。 - 第 4、5、6 步修改 template 后,必须重新 render 并覆盖
/root/helm/tpl/out/base.yaml。第 1、4、5、6 步评分都查看该文件。 - 常见错误 1:使用
indent导致 block 第一行粘到前一个 key。需要前置换行时应使用nindent。 - 常见错误 2:以为传入两个 values 文件后 list 也会合并。map 深度合并,list 整体替换;这正是本实验将
env设计为 map 而非 list 的原因。
引用 values 进行 render
在 /root/helm/tpl/labhub-api 创建 chart(可从 helm create labhub-api 开始)。values.yaml 中 image.repository 为 nginx、image.tag 为 "1.27",Chart.yaml 中 appVersion 为 "1.26"。然后用 helm template labhub-api /root/helm/tpl/labhub-api > /root/helm/tpl/out/base.yaml 保存 render。结果必须有 Deployment,container image 为 저장소:태그 形式,且不能残留花括号或 <no value>。
image 由 repository 与 tag 两个 values 组合。若 render 结果残留花括号或 '',说明引用的 key 不存在于 values。
用 default 填补空值
将 container image tag 写成 {{ .Values.image.tag | default .Chart.AppVersion }},保存 helm template labhub-api /root/helm/tpl/labhub-api --set image.tag="" > /root/helm/tpl/out/default.yaml。即使 tag 为空,image 仍应类似 nginx:1.26,不得用 latest 作为默认值。
即使 tag 为空,render 后 image 也必须带 tag。若从 Chart.yaml 获取替代值,chart 与 app version 会自然保持一致;latest 不是答案。
用 required 强制必填值
在 values.yaml 中设置 ingress.enabled: true 与 ingress.host: api.labhub.local,并在 /root/helm/tpl/labhub-api/templates/ingress.yaml 中用 required 包装 host(例如 {{ required "ingress.host 를 반드시 지정하세요" .Values.ingress.host }})。随后用 helm template labhub-api /root/helm/tpl/labhub-api --set ingress.host="" > /root/helm/tpl/out/required-error.txt 2>&1 故意触发失败并保存输出。文件中必须显示 render 失败消息及缺少的 key。
必填 value 为空时,render 本身必须失败。错误消息中应写明缺少哪个 key;失败输出写到标准错误,保存时必须一并捕获。
使用 toYaml 与 nindent 传递 block
将 values.yaml 的 resources 设置为 limits.cpu: 500m、limits.memory: 512Mi、requests.cpu: 100m、requests.memory: 128Mi,在 Deployment container 中以 {{- toYaml .Values.resources | nindent 12 }} 形式整体传入。重新 render /root/helm/tpl/out/base.yaml 后,必须显示 container 的 resources.limits.cpu 与 resources.requests.memory。
像 resource limit 这样整体传递的 block 不应逐行编写。两个缩进 function 的区别在于是否需要前置换行。
用 range 展开环境变量
在 values.yaml 中把 env 设计为map,定义 APP_MODE: server、LOG_LEVEL: info、TZ: Asia/Seoul。在 Deployment 中使用 range $k, $v := .Values.env 生成 container env list。/root/helm/tpl/out/base.yaml 中 container env 必须恰好 3 个,其中一个 name 为 LOG_LEVEL。
用 key 与 value 两个 variable 遍历 values map。将值加引号更安全,最终必须恰好生成三个。
定义 named template 并 include
在 /root/helm/tpl/labhub-api/templates/_helpers.tpl 中用 define 创建 chart 标识字符串(name-version),并在 Deployment metadata.annotations 中添加 labhub.io/chart: {{ include "labhub-api.chart" . }}。再在 /root/helm/tpl/out/include-note.txt 中用一行说明 template 与 include 的区别:include 返回字符串,所以可接 pipeline 做后处理(缩进)。
把 define 创建的片段插入 annotation。请记录两种调用方式中哪一种可接 pipeline,以及其原因。
确认 value precedence
在 /root/helm/tpl/values-base.yaml 中设置 replicaCount: 2、env.LOG_LEVEL: info、image.tag: base;在 /root/helm/tpl/values-stage.yaml 中设置 replicaCount: 4、env.LOG_LEVEL: debug、image.tag: stage。执行 helm template labhub-api /root/helm/tpl/labhub-api -f /root/helm/tpl/values-base.yaml -f /root/helm/tpl/values-stage.yaml --set image.tag=cli > /root/helm/tpl/out/merged.yaml。结果 replicas 为 4,LOG_LEVEL 为 debug,image tag 为 cli。最后在 /root/helm/tpl/out/precedence.txt 中按从低到高逐行写 precedence:chart values.yaml、-f values-base.yaml、-f values-stage.yaml、--set。
同时使用两个 values 文件和 command-line option。values 文件的传入顺序有意义;根据结果按从低到高写出 precedence。
添加 config checksum 与 security context
添加 ConfigMap template,将 values.yaml 的 config map 输出为 data,并在 Pod template metadata.annotations 中添加 checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}。Pod 级 spec.template.spec.securityContext.runAsNonRoot 必须为 true。将完整 render 保存到 /root/helm/tpl/out/final.yaml(至少 3 个 object、至少 3 个 container env、无 <no value>)。同时保存 helm lint /root/helm/tpl/labhub-api > /root/helm/tpl/out/lint.txt,且不能有 [ERROR]。
为避免 ConfigMap 内容变化后 Pod 不更新,可把 render 后配置文件的 hash 放入 Pod annotation。同时填写 Pod 级 security setting。