Helm 库图表开发指南:基于 lib-chart 的 Common Helper Chart 模板体系深度解析
【免费下载链接】helmThe Kubernetes Package Manager项目地址: https://gitcode.com/GitHub_Trending/hel/helm
导读
在 Helm 生态中,"库图表"(Library Chart)是一种不直接部署任何资源、只对外提供可复用模板(helpers)的特殊图表。Helm 官方仓库中的 lib-chart 就是这样一个典型的 Helper Chart(即 "Common: The Helm Helper Chart"),它把 Kubernetes 图表开发的最佳实践沉淀为一组common.*模板,让你在编写自己的应用图表时无需反复复制 metadata、labels、ports 等样板代码。本文以 lib-chart 自带的 README 为核心骨架,结合仓库内真实模板源码,系统讲解其资源模板、工具函数、覆盖(override)机制与实战用法。读完本文,你将掌握如何借助这类 Helper Chart 快速构建 Service、Deployment、ConfigMap、Secret、Ingress、PVC 等资源,并理解template/define/list组合背后的 Go 模板原理。
一、什么是 Helper Chart:库图表在 Helm 中的定位
lib-chart 在Chart.yaml中声明为库类型:
apiVersion: v1 description: Common chartbuilding components and helpers name: lib-chart version: 0.0.5 appVersion: 0.0.5 home: https://helm.sh maintainers: - name: technosophos email: technosophos@gmail.com - name: prydonius email: adnan@bitnami.com type: Library其中type: Library是图表类型的核心标志。在 Helm 源码中,该字段定义于 internal/chart/v3/metadata.go,且校验逻辑要求其取值只能是application或library(见同文件校验代码)。Library类型的图表不会被直接渲染出任何 Kubernetes 资源,它只作为依赖被子图表引用,向调用方暴露模板命名空间。
这个库图表的设计目标是:
- 降低图表编写成本:把最佳实践固化为一套可组合的
common.*模板,写图表更快; - 减少样板代码:通过"基础模板 + 覆盖模板"的模式,让你只写需要差异化的部分。
此外,该图表在仓库中承担着测试验证的职责:它被helm install(见 pkg/cmd/install_test.go)与helm template(见 pkg/cmd/template_test.go)命令引用,用于验证 Helm 对库图表的加载与渲染行为,对应的黄金输出文件为pkg/cmd/testdata/output/install-lib-chart.txt与pkg/cmd/testdata/output/template-lib-chart.txt。
二、核心机制:template、define 与 list 的配合
2.1 覆盖(Override)模式的调用范式
Kubernetes 定义了从Secret到StatefulSet的多种资源类型,lib-chart 选取其中最常用的类型做了封装。其核心思想是:每个资源模板都要求你定义一个"覆盖模板"(哪怕它是空的),并把该模板的名字传给基础模板:
{{- template "common.service" (list . "mychart.service") -}} {{- define "mychart.service" -}} ## Define overrides for your Service resource here, e.g. # metadata: # labels: # custom: label # spec: # ports: # - port: 8080 {{- end -}}注意common.service模板实际上接收两个参数:
- 根上下文(通常是
.); - 一个包含 Service 覆盖定义的模板名称。
2.2 为什么需要list:Go 模板单参数限制
Go 模板语言的一个局限是:一个模板只能接收一个参数。为了绕过这个限制,lib-chart 使用list函数把多个参数构造成一个列表传给模板。common.service模板拿到这个列表后,负责以根上下文渲染基础模板,并把你的覆盖内容合并进去。
2.3 底层合并逻辑:common.util.merge
从源码 templates/_util.tpl 可以看到合并的关键实现:
{{- define "common.util.merge" -}} {{- $top := first . -}} {{- $overrides := fromYaml (include (index . 1) $top) | default (dict ) -}} {{- $tpl := fromYaml (include (index . 2) $top) | default (dict ) -}} {{- toYaml (merge $overrides $tpl) -}} {{- end -}}该模板接收一个三元素数组:顶层上下文、覆盖模板名(目标)、基础模板名(源)。它先把两个模板分别渲染并fromYaml解析成 map,再通过 Sprig 的merge函数将覆盖层合并到基础层(覆盖优先),最后toYaml输出。这正是"基础默认值 + 按需覆盖"能力的来源。
而每个资源模板(如common.service)的包装实现(见 templates/_service.yaml)就是把调用方传入的列表append上基础模板名后转交给common.util.merge:
{{- define "common.service" -}} {{- template "common.util.merge" (append . "common.service.tpl") -}} {{- end -}}这种"基础定义 + 覆盖合并"的模式,让你只需定义差异部分,就能快速生成一个基础资源,无需复制标准 metadata 与 labels。
三、资源模板详解
每个已实现的资源模板在下文逐一说明。
3.1common.service:一键创建基础 Service
common.service模板(源码见 templates/_service.yaml)生成的基础 Service 带以下默认值:
- Service 类型由
.Values.service.type配置,支持 ClusterIP、NodePort、LoadBalancer; - 在 80 端口上配置命名端口
http(targetPort: http); - Selector 默认设为
app.kubernetes.io/name: {{ template "common.name" }}与app.kubernetes.io/instance: {{ .Release.Name | quote }},与 Deployment 资源中使用的默认值保持一致。
示例:定义一个 mail 服务和 web 服务两个 Service:
{{- template "common.service" (list . "mychart.mail.service") -}} {{- define "mychart.mail.service" -}} metadata: name: {{ template "common.fullname" . }}-mail # overrides the default name to add a suffix labels: # appended to the labels section protocol: mail spec: ports: # composes the `ports` section of the service definition. - name: smtp port: 25 targetPort: 25 - name: imaps port: 993 targetPort: 993 selector: # this is appended to the default selector protocol: mail {{- end -}} --- {{ template "common.service" (list . "mychart.web.service") -}} {{- define "mychart.web.service" -}} metadata: name: {{ template "common.fullname" . }}-www # overrides the default name to add a suffix labels: # appended to the labels section protocol: www spec: ports: # composes the `ports` section of the service definition. - name: www port: 80 targetPort: 8080 {{- end -}}上面模板生成了两个 Service:一个 web 服务、一个 mail 服务。Service 定义中最重要的部分是ports对象,它决定了服务监听的端口。大多数情况下selector会被自动计算出来,但你也可以整体替换或追加内容。
上述示例的渲染输出(假设 release 名为release-name):
apiVersion: v1 kind: Service metadata: labels: app.kubernetes.io/name: service helm.sh/chart: service-0.1.0 app.kubernetes.io/managed-by: Helm protocol: mail app.kubernetes.io/instance: release-name name: release-name-service-mail spec: ports: - name: smtp port: 25 targetPort: 25 - name: imaps port: 993 targetPort: 993 selector: app.kubernetes.io/name: service app.kubernetes.io/instance: release-name protocol: mail type: ClusterIP --- apiVersion: v1 kind: Service metadata: labels: app.kubernetes.io/name: service helm.sh/chart: service-0.1.0 app.kubernetes.io/managed-by: Helm protocol: www app.kubernetes.io/instance: release-name name: release-name-service-www spec: ports: - name: www port: 80 targetPort: 8080 type: ClusterIP注意metadata.name通过common.fullname加上后缀(-mail、-www),protocol标签被追加到标准标签集合上,而selector同样在默认 selector 基础上追加了protocol。
3.2common.deployment:基础 Deployment
common.deployment模板(源码见 templates/_deployment.yaml)定义一个基础 Deployment,底层复用了common.container(见下一节)。从源码看,它渲染出的 Pod 模板标签为app.kubernetes.io/name: {{ template "common.name" . }}与app.kubernetes.io/instance: {{ .Release.Name | quote }}。
之所以不使用标准标签集(helm.sh/chart、app.kubernetes.io/managed-by等)作为 Pod 模板标签与 selector,是因为这些标签在升级过程中可能发生变化,导致 ReplicaSet 和 Pod 无法正确匹配。
示例用法:
{{- template "common.deployment" (list . "mychart.deployment") -}} {{- define "mychart.deployment" -}} ## Define overrides for your Deployment resource here, e.g. spec: replicas: {{ .Values.replicaCount }} {{- end -}}3.3common.container:容器级基础模板
common.container模板(源码见 templates/_container.yaml)生成可在Deployment或ReplicaSet中复用的基础 Container spec,包含以下默认值:
- 容器名设置为图表名(
.Chart.Name); - 使用
.Values.image描述要运行的镜像,默认结构如下:image: repository: nginx tag: stable pullPolicy: IfNotPresent - 在 80 端口暴露命名端口
http; - 使用
.Values.resources布局计算资源。
示例用法——创建一个使用common.container填充 PodSpec 容器列表的 Deployment:
{{- template "common.deployment" (list . "mychart.deployment") -}} {{- define "mychart.deployment" -}} ## Define overrides for your Deployment resource here, e.g. spec: template: spec: containers: - {{ template "common.container" (list . "mychart.deployment.container") }} {{- end -}} {{- define "mychart.deployment.container" -}} ## Define overrides for your Container here, e.g. livenessProbe: httpGet: path: / port: 80 readinessProbe: httpGet: path: / port: 80 {{- end -}}其使用方式与其他资源模板一致:必须定义并引用一个包含容器对象覆盖内容的模板。
容器定义中最重要的是要运行的镜像。如上所述,镜像默认取自.Values.image。最佳实践是在图表的 values 中定义 image、tag 与 pullPolicy,这样运维人员可以轻松更换镜像仓库,或指定特定 tag 与版本。另一个应当暴露给图表使用者的配置是容器所需的计算资源(resources),因为它高度依赖具体运维环境。一个示例values.yaml:
image: repository: nginx tag: stable pullPolicy: IfNotPresent resources: limits: cpu: 100m memory: 128Mi requests: cpu: 100m memory: 128Mi将上述 values 套用到前面模板后的渲染输出:
apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: app.kubernetes.io/name: deployment helm.sh/chart: deployment-0.1.0 app.kubernetes.io/managed-by: Helm app.kubernetes.io/instance: release-name name: release-name-deployment spec: template: metadata: labels: app.kubernetes.io/name: deployment spec: containers: - image: nginx:stable imagePullPolicy: IfNotPresent livenessProbe: httpGet: path: / port: 80 name: deployment ports: - containerPort: 80 name: http readinessProbe: httpGet: path: / port: 80 resources: limits: cpu: 100m memory: 128Mi requests: cpu: 100m memory: 128Mi3.4common.configmap:空 ConfigMap 基座
common.configmap模板创建一个空 ConfigMap 资源,供你通过覆盖模板填充配置数据。
示例用法(通过.Files.Get读取图表内文件内容):
{{- template "common.configmap" (list . "mychart.configmap") -}} {{- define "mychart.configmap" -}} data: zeus: cat athena: cat julius: cat one: |- {{ .Files.Get "file1.txt" }} {{- end -}}渲染输出:
apiVersion: v1 data: athena: cat julius: cat one: This is a file. zeus: cat kind: ConfigMap metadata: labels: app.kubernetes.io/name: configmap helm.sh/chart: configmap-0.1.0 app.kubernetes.io/managed-by: Helm app.kubernetes.io/instance: release-name name: release-name-configmap3.5common.secret:空 Secret 基座
common.secret模板创建一个空 Secret 资源,供你通过覆盖模板填充密钥数据。典型做法是配合b64enc进行 Base64 编码:
{{- template "common.secret" (list . "mychart.secret") -}} {{- define "mychart.secret" -}} data: zeus: {{ print "cat" | b64enc }} athena: {{ print "cat" | b64enc }} julius: {{ print "cat" | b64enc }} one: |- {{ .Files.Get "file1.txt" | b64enc }} {{- end -}}渲染输出:
apiVersion: v1 data: athena: Y2F0 julius: Y2F0 one: VGhpcyBpcyBhIGZpbGUuCg== zeus: Y2F0 kind: Secret metadata: labels: app.kubernetes.io/name: secret helm.sh/chart: secret-0.1.0 app.kubernetes.io/managed-by: Helm app.kubernetes.io/instance: release-name name: release-name-secret type: Opaque3.6common.ingress:可配置的 Ingress
common.ingress模板提供一个结构良好的 Ingress 资源,可通过.Values.ingress配置。示例 values:
ingress: hosts: - chart-example.local annotations: kubernetes.io/ingress.class: nginx kubernetes.io/tls-acme: "true" tls: - secretName: chart-example-tls hosts: - chart-example.local示例用法:
{{- template "common.ingress" (list . "mychart.ingress") -}} {{- define "mychart.ingress" -}} {{- end -}}渲染输出:
apiVersion: extensions/v1beta1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx kubernetes.io/tls-acme: "true" labels: app.kubernetes.io/name: ingress helm.sh/chart: ingress-0.1.0 app.kubernetes.io/managed-by: Helm app.kubernetes.io/instance: release-name name: release-name-ingress spec: rules: - host: chart-example.local http: paths: - backend: serviceName: release-name-ingress servicePort: 80 path: / tls: - hosts: - chart-example.local secretName: chart-example-tls3.7common.persistentvolumeclaim:可配置的 PVC
common.persistentvolumeclaim用于轻松为图表添加PersistentVolumeClaim资源,通过.Values.persistence配置,支持的配置项如下:
| 值 | 说明 |
|---|---|
| persistence.enabled | 是否申请持久卷。若为 false,common.volume.pvc将改用 emptyDir |
| persistence.storageClass | StorageClass 名称 |
| persistence.accessMode | 持久卷的访问模式 |
| persistence.size | 持久卷大小 |
| persistence.existingClaim | 若已定义,则不创建 PVC,common.volume.pvc辅助函数直接使用该 claim |
示例 values:
persistence: enabled: true storageClass: fast accessMode: ReadWriteOnce size: 8Gi示例用法:
{{- template "common.persistentvolumeclaim" (list . "mychart.persistentvolumeclaim") -}} {{- define "mychart.persistentvolumeclaim" -}} {{- end -}}渲染输出:
apiVersion: v1 kind: PersistentVolumeClaim metadata: labels: app.kubernetes.io/name: persistentvolumeclaim helm.sh/chart: persistentvolumeclaim-0.1.0 app.kubernetes.io/managed-by: Helm app.kubernetes.io/instance: release-name name: release-name-persistentvolumeclaim spec: accessModes: - ReadWriteOnce resources: requests: storage: 8Gi storageClassName: "fast"四、Partial API Objects:spec 片段级辅助模板
编写 Kubernetes 资源时,下面这些辅助模板可用于构造 spec 中的局部片段。
4.1 EnvVar 辅助模板
在容器 spec 中使用 EnvVar 辅助模板,可简化键值环境变量的书写,或直接引用 Secret 作为值:
{{- template "common.deployment" (list . "mychart.deployment") -}} {{- define "mychart.deployment" -}} spec: template: spec: containers: - {{ template "common.container" (list . "mychart.deployment.container") }} {{- end -}} {{- define "mychart.deployment.container" -}} {{- $fullname := include "common.fullname" . -}} env: - {{ template "common.envvar.value" (list "ZEUS" "cat") }} - {{ template "common.envvar.secret" (list "ATHENA" "secret-name" "athena") }} {{- end -}}渲染输出:
... spec: containers: - env: - name: ZEUS value: cat - name: ATHENA valueFrom: secretKeyRef: key: athena name: secret-name ...其中common.envvar.value构造普通键值对,common.envvar.secret则构造valueFrom.secretKeyRef引用。
4.2 Volume 辅助模板
在Deploymentspec 中使用 Volume 辅助模板,可方便地定义 ConfigMap 卷与 PVC 卷:
{{- template "common.deployment" (list . "mychart.deployment") -}} {{- define "mychart.deployment" -}} spec: template: spec: volumes: - {{ template "common.volume.configMap" (list "config" "configmap-name") }} - {{ template "common.volume.pvc" (list "data" "pvc-name" .Values.persistence) }} {{- end -}}渲染输出:
... spec: volumes: - configMap: name: configmap-name name: config - name: data persistentVolumeClaim: claimName: pvc-name ...从源码 templates/_volume.tpl 可以看到common.volume.pvc的完整逻辑:它接收三个参数(卷名、claim 名、.Values.persistence对象),当persistence.enabled为 false 时输出emptyDir: {},否则输出persistentVolumeClaim,且claimName优先取persistence.existingClaim,未设置时才回退到传入的 claim 名。其行为可归纳为:
| 值 | 说明 |
|---|---|
| persistence.enabled | 若为 false,则创建 emptyDir 代替 |
| persistence.existingClaim | 若已设置,则使用它替代传入的 claim 名 |
五、工具函数(Utilities)详解
5.1common.fullname:资源命名模板
common.fullname生成适合填入 Kubernetes metadataname:字段的名称。用法:
name: {{ template "common.fullname" . }}以下 values 会影响其结果:
# 默认情况下,fullname 使用 '{{ .Release.Name }}-{{ .Chart.Name }}'。 # 该值可覆盖默认组合,改用给定字符串。 fullnameOverride: "some-name" # 添加前缀 fullnamePrefix: "pre-" # 追加后缀 fullnameSuffix: "-suf" # 以上各项的全局版本 global: fullnamePrefix: "pp-" fullnameSuffix: "-ps"示例输出:
--- # 使用上述 values 时 name: pp-pre-some-name-suf-ps --- # 默认情况,release 为 "happy-panda"、chart 为 "wordpress" 时 name: happy-panda-wordpress从源码 templates/_fullname.tpl 可以看到精确的拼接顺序为全局前缀 + 前缀 + 名称 + 后缀 + 全局后缀,最终结果经过lower、trunc 54、trimSuffix "-"处理。
该函数的输出会被截断到 54 个字符,从而为自定义覆盖预留 9 个字符。因此你可以在自己的图表中轻松扩展这个名称:
{{- define "my.fullname" -}} {{ template "common.fullname" . }}-my-stuff {{- end -}}5.2common.fullname.unique:唯一名称变体
common.fullname.unique在 common name 末尾追加一个 7 字符的唯一序列(源码实现为randAlphaNum 7 | lower),参数与common.fullname完全相同。
示例模板:
uniqueName: {{ template "common.fullname.unique" . }}示例输出:
uniqueName: release-name-fullname-jl0dbwx它同样受前缀、后缀定义以及.Values.fullnameOverride影响。注意:该函数的有效最大长度是 63 个字符,而非 54 个。
5.3common.name:app 标签命名模板
common.name生成适合填入app标签的名称。用法:
app: {{ template "common.name" . }}以下 values 会影响其结果:
# 默认情况下,name 使用 '{{ .Chart.Name }}'。 # 该值可覆盖默认值,改用给定字符串。 nameOverride: "some-name" # 添加前缀 namePrefix: "pre-" # 追加后缀 nameSuffix: "-suf" # 以上各项的全局版本 global: namePrefix: "pp-" nameSuffix: "-ps"示例输出:
--- # 使用上述 values 时 name: pp-pre-some-name-suf-ps --- # 默认情况,chart 为 "wordpress" 时 name: wordpress该函数输出同样被截断到 54 个字符,预留 9 个字符用于自定义覆盖,可这样扩展:
{{- define "my.name" -}} {{ template "common.name" . }}-my-stuff {{- end -}}与common.fullname的区别在于:common.name仅基于.Chart.Name(默认值),不包含 release 名;源码实现同样位于 templates/_name.tpl。
5.4common.metadata:标准 metadata 生成器
common.metadata辅助函数生成 Kubernetes 资源的metadata:段。它接收三个对象:
.top:顶层上下文;.fullnameOverride:用该名称覆盖 fullname;.metadata.labels:键值对形式的标签;.annotations:键值对形式的注解;.hook:钩子名称(可多个)。
它生成标准标签、注解、钩子以及 name 字段。示例模板:
{{ template "common.metadata" (dict "top" . "metadata" .Values.bio) }} --- {{ template "common.metadata" (dict "top" . "metadata" .Values.pet "fullnameOverride" .Values.pet.fullnameOverride) }}示例 values:
bio: name: example labels: first: matt last: butcher nick: technosophos annotations: format: bio destination: archive hook: pre-install pet: fullnameOverride: Zeus示例输出:
metadata: name: release-name-metadata labels: app.kubernetes.io/name: metadata app.kubernetes.io/managed-by: "Helm" app.kubernetes.io/instance: "release-name" helm.sh/chart: metadata-0.1.0 first: "matt" last: "butcher" nick: "technosophos" annotations: "destination": "archive" "format": "bio" "helm.sh/hook": "pre-install" --- metadata: name: Zeus labels: app.kubernetes.io/name: metadata app.kubernetes.io/managed-by: "Helm" app.kubernetes.io/instance: "release-name" helm.sh/chart: metadata-0.1.0 annotations:大多数定义资源类型的 common 模板(如common.configmap、common.job)都使用该模板生成 metadata,因此它们都继承了同样的labels、annotations、nameOverride与hook字段能力。从源码 templates/_metadata.yaml 可以看到其基础实现:name使用common.fullname,labels通过common.labels.standard生成(相关实现可参考 templates/_metadata_labels.tpl 与 templates/_metadata_annotations.tpl)。
5.5common.labelize:map 转标签
common.labelize把 map 转为一组标签。示例模板:
{{- $map := dict "first" "1" "second" "2" "third" "3" -}} {{- template "common.labelize" $map -}}示例输出:
first: "1" second: "2" third: "3"5.6common.labels.standard:标准标签集
common.labels.standard输出标准标签集合。示例用法:
{{ template "common.labels.standard" . }}示例输出:
app.kubernetes.io/name: labelizer app.kubernetes.io/managed-by: "Tiller" app.kubernetes.io/instance: "release-name" helm.sh/chart: labelizer-0.1.05.7common.hook:钩子注解便捷模板
common.hook是定义钩子的便捷模板。示例模板:
{{ template "common.hook" "pre-install,post-install" }}示例输出:
"helm.sh/hook": "pre-install,post-install"5.8common.chartref:合法的标签值引用
common.chartref输出图表名称与版本,并对 Kubernetes 标签字段中非法字符进行转义。示例模板:
chartref: {{ template "common.chartref" . }}对于图表foo且版本为1.2.3-beta.55+1234,渲染结果为:
chartref: foo-1.2.3-beta.55_1234(注意:+是标签值中的非法字符,因此被替换为_。)
六、实战建议与注意事项
- 警惕随机数据函数:使用会生成随机数据的函数(如
common.fullname.unique)时要小心,它们可能触发非预期的升级或其他副作用。 - 命名约定:文中示例统一以
release-name作为 release 名称,便于对照输出。 - 覆盖模板可以为空:即使你完全不需要自定义,也必须定义一个(可为空的)覆盖模板并传名给基础模板,这是该模式的固定调用约定。
- values 分层设计:将 image(repository/tag/pullPolicy)与 resources 暴露到 values 中,是让图表可被运维人员灵活配置的关键实践。
- 动手验证:可以直接在仓库中使用
helm template或helm install --dry-run对 lib-chart 做渲染验证,Helm 官方测试也依赖这一图表验证库类型图表的加载与渲染行为。
总结
lib-chart 展示了 Helm 库图表(Library Chart)的完整设计范式:以type: Library声明类型、以common.*命名空间组织模板、以common.util.merge实现"基础 + 覆盖"的合并机制,并以list绕过 Go 模板单参数限制。通过common.service、common.deployment、common.container、common.configmap、common.secret、common.ingress、common.persistentvolumeclaim等资源模板与common.fullname、common.name、common.metadata等工具函数,图表作者可以在不复制样板代码的前提下快速产出规范、可配置的 Kubernetes 资源。这套模式既是 Helm 图表开发的最佳实践沉淀,也是理解 Helm 模板引擎能力边界的绝佳教材。
【免费下载链接】helmThe Kubernetes Package Manager项目地址: https://gitcode.com/GitHub_Trending/hel/helm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考