本章概述
Helm 模板系统基于 Go 的 text/template 包,提供了强大的模板功能。随着应用复杂度的增加,我们需要掌握更高级的模板技巧来处理复杂的配置场景。本章将深入探讨 Helm 模板的高级用法,包括复杂的条件逻辑、循环处理、自定义函数、模板继承、调试技巧等内容。
学习目标
- 掌握复杂的条件判断和逻辑控制
- 学会使用高级循环和迭代技巧
- 了解模板函数的高级用法
- 掌握模板继承和包含机制
- 学会自定义模板函数
- 掌握模板调试和故障排除技巧
- 了解性能优化和最佳实践
- 学习模板安全和验证
8.1 高级条件逻辑
8.1.1 复杂条件判断
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }}
labels:
{{- include "myapp.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "myapp.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- if .Values.podAnnotations }}
{{- toYaml .Values.podAnnotations | nindent 8 }}
{{- end }}
{{- if and .Values.metrics.enabled .Values.metrics.podAnnotations }}
{{- toYaml .Values.metrics.podAnnotations | nindent 8 }}
{{- end }}
labels:
{{- include "myapp.selectorLabels" . | nindent 8 }}
{{- if .Values.podLabels }}
{{- toYaml .Values.podLabels | nindent 8 }}
{{- end }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "myapp.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort | default 8080 }}
protocol: TCP
{{- if .Values.metrics.enabled }}
- name: metrics
containerPort: {{ .Values.metrics.port | default 9090 }}
protocol: TCP
{{- end }}
{{- if or .Values.livenessProbe.enabled .Values.readinessProbe.enabled }}
{{- if .Values.livenessProbe.enabled }}
livenessProbe:
{{- if eq .Values.livenessProbe.type "http" }}
httpGet:
path: {{ .Values.livenessProbe.path | default "/health" }}
port: http
{{- else if eq .Values.livenessProbe.type "tcp" }}
tcpSocket:
port: http
{{- else if eq .Values.livenessProbe.type "exec" }}
exec:
command:
{{- toYaml .Values.livenessProbe.command | nindent 16 }}
{{- end }}
initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds | default 30 }}
periodSeconds: {{ .Values.livenessProbe.periodSeconds | default 10 }}
timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds | default 5 }}
failureThreshold: {{ .Values.livenessProbe.failureThreshold | default 3 }}
{{- end }}
{{- if .Values.readinessProbe.enabled }}
readinessProbe:
{{- if eq .Values.readinessProbe.type "http" }}
httpGet:
path: {{ .Values.readinessProbe.path | default "/ready" }}
port: http
{{- else if eq .Values.readinessProbe.type "tcp" }}
tcpSocket:
port: http
{{- else if eq .Values.readinessProbe.type "exec" }}
exec:
command:
{{- toYaml .Values.readinessProbe.command | nindent 16 }}
{{- end }}
initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds | default 5 }}
periodSeconds: {{ .Values.readinessProbe.periodSeconds | default 10 }}
timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds | default 5 }}
failureThreshold: {{ .Values.readinessProbe.failureThreshold | default 3 }}
{{- end }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- if or .Values.env .Values.envFrom .Values.config.enabled .Values.secrets.enabled }}
env:
{{- if .Values.env }}
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
{{- if kindIs "string" $value }}
value: {{ $value | quote }}
{{- else if hasKey $value "value" }}
value: {{ $value.value | quote }}
{{- else if hasKey $value "valueFrom" }}
valueFrom:
{{- toYaml $value.valueFrom | nindent 16 }}
{{- end }}
{{- end }}
{{- end }}
{{- if .Values.config.enabled }}
- name: CONFIG_FILE
value: "/etc/config/app.yaml"
{{- end }}
{{- if .Values.envFrom }}
envFrom:
{{- toYaml .Values.envFrom | nindent 12 }}
{{- end }}
{{- end }}
{{- if or .Values.config.enabled .Values.secrets.enabled .Values.persistence.enabled }}
volumeMounts:
{{- if .Values.config.enabled }}
- name: config
mountPath: /etc/config
readOnly: true
{{- end }}
{{- if .Values.secrets.enabled }}
- name: secrets
mountPath: /etc/secrets
readOnly: true
{{- end }}
{{- if .Values.persistence.enabled }}
- name: data
mountPath: {{ .Values.persistence.mountPath | default "/data" }}
{{- end }}
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
{{- end }}
{{- if .Values.sidecar.enabled }}
- name: {{ .Values.sidecar.name | default "sidecar" }}
image: "{{ .Values.sidecar.image.repository }}:{{ .Values.sidecar.image.tag }}"
imagePullPolicy: {{ .Values.sidecar.image.pullPolicy | default "IfNotPresent" }}
{{- if .Values.sidecar.command }}
command:
{{- toYaml .Values.sidecar.command | nindent 12 }}
{{- end }}
{{- if .Values.sidecar.args }}
args:
{{- toYaml .Values.sidecar.args | nindent 12 }}
{{- end }}
{{- if .Values.sidecar.env }}
env:
{{- toYaml .Values.sidecar.env | nindent 12 }}
{{- end }}
{{- if .Values.sidecar.resources }}
resources:
{{- toYaml .Values.sidecar.resources | nindent 12 }}
{{- end }}
{{- end }}
{{- if or .Values.config.enabled .Values.secrets.enabled .Values.persistence.enabled .Values.extraVolumes }}
volumes:
{{- if .Values.config.enabled }}
- name: config
configMap:
name: {{ include "myapp.fullname" . }}
{{- end }}
{{- if .Values.secrets.enabled }}
- name: secrets
secret:
secretName: {{ include "myapp.fullname" . }}
{{- end }}
{{- if .Values.persistence.enabled }}
- name: data
{{- if .Values.persistence.existingClaim }}
persistentVolumeClaim:
claimName: {{ .Values.persistence.existingClaim }}
{{- else }}
persistentVolumeClaim:
claimName: {{ include "myapp.fullname" . }}
{{- end }}
{{- end }}
{{- with .Values.extraVolumes }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
8.1.2 多条件组合判断
# templates/_helpers.tpl
{{/*
检查是否需要创建 Ingress
*/}}
{{- define "myapp.createIngress" -}}
{{- if and .Values.ingress.enabled (or .Values.ingress.hosts .Values.ingress.tls) -}}
true
{{- end -}}
{{- end -}}
{{/*
检查是否需要 TLS 配置
*/}}
{{- define "myapp.needsTLS" -}}
{{- if and .Values.ingress.enabled .Values.ingress.tls (gt (len .Values.ingress.tls) 0) -}}
true
{{- end -}}
{{- end -}}
{{/*
检查是否为生产环境
*/}}
{{- define "myapp.isProduction" -}}
{{- if or (eq .Values.environment "production") (eq .Values.environment "prod") -}}
true
{{- end -}}
{{- end -}}
{{/*
获取资源限制
*/}}
{{- define "myapp.resources" -}}
{{- if include "myapp.isProduction" . -}}
{{- .Values.resources.production | toYaml -}}
{{- else -}}
{{- .Values.resources.development | toYaml -}}
{{- end -}}
{{- end -}}
# templates/ingress.yaml
{{- if include "myapp.createIngress" . -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "myapp.fullname" . }}
labels:
{{- include "myapp.labels" . | nindent 4 }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- if include "myapp.needsTLS" . }}
cert-manager.io/cluster-issuer: {{ .Values.ingress.certManager.issuer | default "letsencrypt-prod" }}
{{- end }}
{{- end }}
spec:
{{- if include "myapp.needsTLS" . }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
{{- if .pathType }}
pathType: {{ .pathType }}
{{- else }}
pathType: Prefix
{{- end }}
backend:
service:
name: {{ include "myapp.fullname" $ }}
port:
number: {{ $.Values.service.port }}
{{- end }}
{{- end }}
{{- end }}
8.2 高级循环和迭代
8.2.1 复杂数据结构遍历
# values.yaml
microservices:
user-service:
image: "user-service:1.0.0"
port: 8080
replicas: 3
env:
DATABASE_URL: "postgresql://localhost/users"
REDIS_URL: "redis://localhost:6379"
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
order-service:
image: "order-service:1.0.0"
port: 8081
replicas: 2
env:
DATABASE_URL: "postgresql://localhost/orders"
KAFKA_BROKERS: "kafka:9092"
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
notification-service:
image: "notification-service:1.0.0"
port: 8082
replicas: 1
env:
SMTP_HOST: "smtp.example.com"
SMTP_PORT: "587"
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
databases:
- name: "users"
type: "postgresql"
version: "13"
storage: "10Gi"
config:
max_connections: 100
shared_buffers: "256MB"
- name: "orders"
type: "postgresql"
version: "13"
storage: "20Gi"
config:
max_connections: 200
shared_buffers: "512MB"
- name: "cache"
type: "redis"
version: "6"
storage: "5Gi"
config:
maxmemory: "2gb"
maxmemory-policy: "allkeys-lru"
# templates/microservices.yaml
{{- range $serviceName, $serviceConfig := .Values.microservices }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $serviceName }}
labels:
app: {{ $serviceName }}
chart: {{ $.Chart.Name }}-{{ $.Chart.Version }}
release: {{ $.Release.Name }}
heritage: {{ $.Release.Service }}
spec:
replicas: {{ $serviceConfig.replicas | default 1 }}
selector:
matchLabels:
app: {{ $serviceName }}
release: {{ $.Release.Name }}
template:
metadata:
labels:
app: {{ $serviceName }}
release: {{ $.Release.Name }}
spec:
containers:
- name: {{ $serviceName }}
image: {{ $serviceConfig.image }}
ports:
- containerPort: {{ $serviceConfig.port }}
{{- if $serviceConfig.env }}
env:
{{- range $envKey, $envValue := $serviceConfig.env }}
- name: {{ $envKey }}
value: {{ $envValue | quote }}
{{- end }}
{{- end }}
{{- if $serviceConfig.resources }}
resources:
{{- toYaml $serviceConfig.resources | nindent 10 }}
{{- end }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ $serviceName }}
labels:
app: {{ $serviceName }}
chart: {{ $.Chart.Name }}-{{ $.Chart.Version }}
release: {{ $.Release.Name }}
heritage: {{ $.Release.Service }}
spec:
type: ClusterIP
ports:
- port: {{ $serviceConfig.port }}
targetPort: {{ $serviceConfig.port }}
protocol: TCP
name: http
selector:
app: {{ $serviceName }}
release: {{ $.Release.Name }}
{{- end }}
# templates/databases.yaml
{{- range $index, $db := .Values.databases }}
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ $db.name }}-{{ $db.type }}
labels:
app: {{ $db.name }}-{{ $db.type }}
chart: {{ $.Chart.Name }}-{{ $.Chart.Version }}
release: {{ $.Release.Name }}
heritage: {{ $.Release.Service }}
spec:
serviceName: {{ $db.name }}-{{ $db.type }}
replicas: 1
selector:
matchLabels:
app: {{ $db.name }}-{{ $db.type }}
release: {{ $.Release.Name }}
template:
metadata:
labels:
app: {{ $db.name }}-{{ $db.type }}
release: {{ $.Release.Name }}
spec:
containers:
- name: {{ $db.type }}
{{- if eq $db.type "postgresql" }}
image: postgres:{{ $db.version }}
env:
- name: POSTGRES_DB
value: {{ $db.name }}
- name: POSTGRES_USER
value: {{ $db.name }}_user
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: {{ $db.name }}-{{ $db.type }}-secret
key: password
{{- range $configKey, $configValue := $db.config }}
- name: {{ $configKey | upper }}
value: {{ $configValue | quote }}
{{- end }}
{{- else if eq $db.type "redis" }}
image: redis:{{ $db.version }}
command:
- redis-server
{{- range $configKey, $configValue := $db.config }}
- --{{ $configKey }}
- {{ $configValue | quote }}
{{- end }}
{{- end }}
ports:
{{- if eq $db.type "postgresql" }}
- containerPort: 5432
{{- else if eq $db.type "redis" }}
- containerPort: 6379
{{- end }}
volumeMounts:
- name: data
mountPath: {{ if eq $db.type "postgresql" }}/var/lib/postgresql/data{{ else if eq $db.type "redis" }}/data{{ end }}
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: {{ $db.storage }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ $db.name }}-{{ $db.type }}
labels:
app: {{ $db.name }}-{{ $db.type }}
chart: {{ $.Chart.Name }}-{{ $.Chart.Version }}
release: {{ $.Release.Name }}
heritage: {{ $.Release.Service }}
spec:
type: ClusterIP
ports:
{{- if eq $db.type "postgresql" }}
- port: 5432
targetPort: 5432
{{- else if eq $db.type "redis" }}
- port: 6379
targetPort: 6379
{{- end }}
protocol: TCP
name: {{ $db.type }}
selector:
app: {{ $db.name }}-{{ $db.type }}
release: {{ $.Release.Name }}
{{- end }}
8.2.2 条件循环和过滤
# templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "myapp.fullname" . }}
labels:
{{- include "myapp.labels" . | nindent 4 }}
data:
# 只包含启用的配置项
{{- range $key, $value := .Values.config }}
{{- if and (ne $key "enabled") $value }}
{{- if kindIs "string" $value }}
{{ $key }}: {{ $value | quote }}
{{- else }}
{{ $key }}: {{ $value | toYaml | quote }}
{{- end }}
{{- end }}
{{- end }}
# 生成环境特定配置
{{- if .Values.environment }}
environment: {{ .Values.environment | quote }}
{{- end }}
# 动态生成服务发现配置
services.yaml: |
services:
{{- range $serviceName, $serviceConfig := .Values.microservices }}
{{- if $serviceConfig.enabled | default true }}
{{ $serviceName }}:
host: {{ $serviceName }}
port: {{ $serviceConfig.port }}
{{- if $serviceConfig.healthCheck }}
healthCheck:
path: {{ $serviceConfig.healthCheck.path | default "/health" }}
interval: {{ $serviceConfig.healthCheck.interval | default "30s" }}
{{- end }}
{{- end }}
{{- end }}
# 生成数据库连接配置
database.yaml: |
databases:
{{- range $db := .Values.databases }}
{{- if eq $db.type "postgresql" }}
{{ $db.name }}:
driver: postgresql
host: {{ $db.name }}-postgresql
port: 5432
database: {{ $db.name }}
username: {{ $db.name }}_user
{{- if $db.ssl | default false }}
sslmode: require
{{- else }}
sslmode: disable
{{- end }}
{{- else if eq $db.type "redis" }}
{{ $db.name }}:
driver: redis
host: {{ $db.name }}-redis
port: 6379
{{- if $db.password }}
auth: true
{{- end }}
{{- end }}
{{- end }}
8.2.3 嵌套循环处理
# values.yaml
environments:
development:
namespaces:
- name: "dev-frontend"
resources:
requests:
memory: "128Mi"
cpu: "100m"
- name: "dev-backend"
resources:
requests:
memory: "256Mi"
cpu: "200m"
ingress:
domain: "dev.example.com"
tls: false
staging:
namespaces:
- name: "staging-frontend"
resources:
requests:
memory: "256Mi"
cpu: "200m"
- name: "staging-backend"
resources:
requests:
memory: "512Mi"
cpu: "400m"
ingress:
domain: "staging.example.com"
tls: true
production:
namespaces:
- name: "prod-frontend"
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
- name: "prod-backend"
resources:
requests:
memory: "1Gi"
cpu: "1000m"
limits:
memory: "2Gi"
cpu: "2000m"
ingress:
domain: "example.com"
tls: true
# templates/multi-env.yaml
{{- range $envName, $envConfig := .Values.environments }}
{{- range $nsIndex, $namespace := $envConfig.namespaces }}
---
apiVersion: v1
kind: Namespace
metadata:
name: {{ $namespace.name }}
labels:
environment: {{ $envName }}
chart: {{ $.Chart.Name }}-{{ $.Chart.Version }}
release: {{ $.Release.Name }}
heritage: {{ $.Release.Service }}
annotations:
description: "Namespace for {{ $envName }} environment"
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: {{ $namespace.name }}-quota
namespace: {{ $namespace.name }}
spec:
hard:
{{- if $namespace.resources.requests }}
requests.memory: {{ mul ($namespace.resources.requests.memory | replace "Mi" "" | int) 10 }}Mi
requests.cpu: {{ mul ($namespace.resources.requests.cpu | replace "m" "" | int) 10 }}m
{{- end }}
{{- if $namespace.resources.limits }}
limits.memory: {{ mul ($namespace.resources.limits.memory | replace "Mi" "" | replace "Gi" "000" | int) 5 }}Mi
limits.cpu: {{ mul ($namespace.resources.limits.cpu | replace "m" "" | int) 5 }}m
{{- end }}
persistentvolumeclaims: "10"
services: "20"
secrets: "10"
configmaps: "10"
---
apiVersion: v1
kind: LimitRange
metadata:
name: {{ $namespace.name }}-limits
namespace: {{ $namespace.name }}
spec:
limits:
- default:
{{- if $namespace.resources.limits }}
memory: {{ $namespace.resources.limits.memory }}
cpu: {{ $namespace.resources.limits.cpu }}
{{- else }}
memory: {{ mul ($namespace.resources.requests.memory | replace "Mi" "" | int) 2 }}Mi
cpu: {{ mul ($namespace.resources.requests.cpu | replace "m" "" | int) 2 }}m
{{- end }}
defaultRequest:
memory: {{ $namespace.resources.requests.memory }}
cpu: {{ $namespace.resources.requests.cpu }}
type: Container
{{- end }}
{{- if $envConfig.ingress }}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $envName }}-ingress
namespace: {{ (index $envConfig.namespaces 0).name }}
annotations:
kubernetes.io/ingress.class: nginx
{{- if $envConfig.ingress.tls }}
cert-manager.io/cluster-issuer: letsencrypt-prod
{{- end }}
spec:
{{- if $envConfig.ingress.tls }}
tls:
- hosts:
- {{ $envConfig.ingress.domain }}
secretName: {{ $envName }}-tls
{{- end }}
rules:
- host: {{ $envConfig.ingress.domain }}
http:
paths:
{{- range $nsIndex, $namespace := $envConfig.namespaces }}
- path: /{{ $namespace.name | replace (printf "%s-" $envName) "" }}
pathType: Prefix
backend:
service:
name: {{ $namespace.name }}-service
port:
number: 80
{{- end }}
{{- end }}
{{- end }}
8.3 模板函数高级用法
8.3.1 字符串处理函数
# templates/_helpers.tpl
{{/*
生成安全的资源名称
*/}}
{{- define "myapp.safeName" -}}
{{- $name := . -}}
{{- $name | lower | replace "_" "-" | replace "." "-" | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
生成标签安全的值
*/}}
{{- define "myapp.labelValue" -}}
{{- $value := . -}}
{{- $value | replace "/" "-" | replace ":" "-" | lower | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{/*
生成环境变量名
*/}}
{{- define "myapp.envVarName" -}}
{{- $name := . -}}
{{- $name | upper | replace "-" "_" | replace "." "_" -}}
{{- end -}}
{{/*
格式化版本号
*/}}
{{- define "myapp.version" -}}
{{- $version := .Chart.AppVersion | default .Chart.Version -}}
{{- if hasPrefix "v" $version -}}
{{- $version -}}
{{- else -}}
{{- printf "v%s" $version -}}
{{- end -}}
{{- end -}}
{{/*
生成密码
*/}}
{{- define "myapp.generatePassword" -}}
{{- $length := . | default 16 -}}
{{- randAlphaNum $length -}}
{{- end -}}
{{/*
格式化存储大小
*/}}
{{- define "myapp.formatStorage" -}}
{{- $size := . -}}
{{- if hasSuffix "Gi" $size -}}
{{- $size -}}
{{- else if hasSuffix "Mi" $size -}}
{{- $size -}}
{{- else if hasSuffix "G" $size -}}
{{- printf "%sGi" ($size | trimSuffix "G") -}}
{{- else if hasSuffix "M" $size -}}
{{- printf "%sMi" ($size | trimSuffix "M") -}}
{{- else -}}
{{- printf "%sGi" $size -}}
{{- end -}}
{{- end -}}
8.3.2 数据处理函数
# templates/_helpers.tpl
{{/*
合并配置
*/}}
{{- define "myapp.mergeConfig" -}}
{{- $base := index . 0 -}}
{{- $override := index . 1 -}}
{{- $result := deepCopy $base -}}
{{- range $key, $value := $override -}}
{{- if hasKey $result $key -}}
{{- if and (kindIs "map" $value) (kindIs "map" (index $result $key)) -}}
{{- $_ := set $result $key (include "myapp.mergeConfig" (list (index $result $key) $value) | fromYaml) -}}
{{- else -}}
{{- $_ := set $result $key $value -}}
{{- end -}}
{{- else -}}
{{- $_ := set $result $key $value -}}
{{- end -}}
{{- end -}}
{{- $result | toYaml -}}
{{- end -}}
{{/*
过滤启用的项目
*/}}
{{- define "myapp.filterEnabled" -}}
{{- $items := . -}}
{{- $result := dict -}}
{{- range $key, $value := $items -}}
{{- if and (kindIs "map" $value) (hasKey $value "enabled") -}}
{{- if $value.enabled -}}
{{- $_ := set $result $key $value -}}
{{- end -}}
{{- else -}}
{{- $_ := set $result $key $value -}}
{{- end -}}
{{- end -}}
{{- $result -}}
{{- end -}}
{{/*
计算资源总和
*/}}
{{- define "myapp.sumResources" -}}
{{- $resources := . -}}
{{- $totalCpu := 0 -}}
{{- $totalMemory := 0 -}}
{{- range $key, $value := $resources -}}
{{- if hasKey $value "resources" -}}
{{- if hasKey $value.resources "requests" -}}
{{- if hasKey $value.resources.requests "cpu" -}}
{{- $cpu := $value.resources.requests.cpu | replace "m" "" | int -}}
{{- $totalCpu = add $totalCpu $cpu -}}
{{- end -}}
{{- if hasKey $value.resources.requests "memory" -}}
{{- $memory := $value.resources.requests.memory | replace "Mi" "" | replace "Gi" "000" | int -}}
{{- $totalMemory = add $totalMemory $memory -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- dict "cpu" (printf "%dm" $totalCpu) "memory" (printf "%dMi" $totalMemory) -}}
{{- end -}}
8.3.3 自定义验证函数
# templates/_helpers.tpl
{{/*
验证必需字段
*/}}
{{- define "myapp.required" -}}
{{- $value := index . 0 -}}
{{- $message := index . 1 -}}
{{- if not $value -}}
{{- fail $message -}}
{{- end -}}
{{- $value -}}
{{- end -}}
{{/*
验证端口范围
*/}}
{{- define "myapp.validatePort" -}}
{{- $port := . | int -}}
{{- if or (lt $port 1) (gt $port 65535) -}}
{{- fail (printf "Port %d is out of valid range (1-65535)" $port) -}}
{{- end -}}
{{- $port -}}
{{- end -}}
{{/*
验证存储大小格式
*/}}
{{- define "myapp.validateStorage" -}}
{{- $size := . -}}
{{- if not (or (hasSuffix "Gi" $size) (hasSuffix "Mi" $size) (hasSuffix "Ti" $size)) -}}
{{- fail (printf "Invalid storage size format: %s. Must end with Gi, Mi, or Ti" $size) -}}
{{- end -}}
{{- $size -}}
{{- end -}}
{{/*
验证环境名称
*/}}
{{- define "myapp.validateEnvironment" -}}
{{- $env := . -}}
{{- $validEnvs := list "development" "staging" "production" "test" -}}
{{- if not (has $env $validEnvs) -}}
{{- fail (printf "Invalid environment: %s. Must be one of: %s" $env (join ", " $validEnvs)) -}}
{{- end -}}
{{- $env -}}
{{- end -}}
{{/*
验证副本数
*/}}
{{- define "myapp.validateReplicas" -}}
{{- $replicas := . | int -}}
{{- if lt $replicas 1 -}}
{{- fail (printf "Replica count must be at least 1, got %d" $replicas) -}}
{{- end -}}
{{- if and (include "myapp.isProduction" $) (lt $replicas 2) -}}
{{- fail "Production environment requires at least 2 replicas for high availability" -}}
{{- end -}}
{{- $replicas -}}
{{- end -}}
8.4 模板继承和包含
8.4.1 模板继承模式
# templates/_base.tpl
{{/*
基础 Deployment 模板
*/}}
{{- define "base.deployment" -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }}
labels:
{{- include "myapp.labels" . | nindent 4 }}
{{- with .extraLabels }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if not .autoscaling.enabled }}
replicas: {{ include "myapp.validateReplicas" .replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "myapp.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- with .podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "myapp.selectorLabels" . | nindent 8 }}
{{- with .podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec:
{{- include "base.podSpec" . | nindent 6 }}
{{- end -}}
{{/*
基础 Pod 规范模板
*/}}
{{- define "base.podSpec" -}}
{{- with .imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 2 }}
{{- end }}
serviceAccountName: {{ include "myapp.serviceAccountName" . }}
securityContext:
{{- toYaml .podSecurityContext | nindent 2 }}
containers:
{{- include "base.mainContainer" . | nindent 2 }}
{{- include "base.sidecarContainers" . | nindent 2 }}
{{- include "base.volumes" . }}
{{- include "base.nodeSelection" . }}
{{- end -}}
{{/*
基础主容器模板
*/}}
{{- define "base.mainContainer" -}}
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .securityContext | nindent 4 }}
image: "{{ .image.repository }}:{{ .image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .image.pullPolicy }}
{{- include "base.containerPorts" . | nindent 2 }}
{{- include "base.probes" . | nindent 2 }}
{{- include "base.resources" . | nindent 2 }}
{{- include "base.env" . | nindent 2 }}
{{- include "base.volumeMounts" . | nindent 2 }}
{{- end -}}
# templates/web-deployment.yaml
{{- $webConfig := deepCopy .Values -}}
{{- $_ := set $webConfig "extraLabels" (dict "component" "web" "tier" "frontend") -}}
{{- $_ := set $webConfig "podLabels" (dict "app.kubernetes.io/component" "web") -}}
{{- $_ := set $webConfig.image "repository" .Values.web.image.repository -}}
{{- $_ := set $webConfig.image "tag" .Values.web.image.tag -}}
{{- $_ := set $webConfig "replicaCount" .Values.web.replicaCount -}}
{{- $_ := set $webConfig "resources" .Values.web.resources -}}
{{- $_ := set $webConfig "env" .Values.web.env -}}
{{- include "base.deployment" $webConfig }}
# templates/api-deployment.yaml
{{- $apiConfig := deepCopy .Values -}}
{{- $_ := set $apiConfig "extraLabels" (dict "component" "api" "tier" "backend") -}}
{{- $_ := set $apiConfig "podLabels" (dict "app.kubernetes.io/component" "api") -}}
{{- $_ := set $apiConfig.image "repository" .Values.api.image.repository -}}
{{- $_ := set $apiConfig.image "tag" .Values.api.image.tag -}}
{{- $_ := set $apiConfig "replicaCount" .Values.api.replicaCount -}}
{{- $_ := set $apiConfig "resources" .Values.api.resources -}}
{{- $_ := set $apiConfig "env" .Values.api.env -}}
{{- include "base.deployment" $apiConfig }}
8.4.2 模板组合模式
# templates/_components.tpl
{{/*
容器端口组件
*/}}
{{- define "components.containerPorts" -}}
ports:
{{- if .service.port }}
- name: http
containerPort: {{ .service.targetPort | default .service.port }}
protocol: TCP
{{- end }}
{{- if .metrics.enabled }}
- name: metrics
containerPort: {{ .metrics.port | default 9090 }}
protocol: TCP
{{- end }}
{{- if .debug.enabled }}
- name: debug
containerPort: {{ .debug.port | default 5005 }}
protocol: TCP
{{- end }}
{{- range .extraPorts }}
- name: {{ .name }}
containerPort: {{ .port }}
protocol: {{ .protocol | default "TCP" }}
{{- end }}
{{- end -}}
{{/*
健康检查组件
*/}}
{{- define "components.healthChecks" -}}
{{- if .livenessProbe.enabled }}
livenessProbe:
{{- include "components.probe" .livenessProbe | nindent 2 }}
initialDelaySeconds: {{ .livenessProbe.initialDelaySeconds | default 30 }}
periodSeconds: {{ .livenessProbe.periodSeconds | default 10 }}
timeoutSeconds: {{ .livenessProbe.timeoutSeconds | default 5 }}
failureThreshold: {{ .livenessProbe.failureThreshold | default 3 }}
{{- end }}
{{- if .readinessProbe.enabled }}
readinessProbe:
{{- include "components.probe" .readinessProbe | nindent 2 }}
initialDelaySeconds: {{ .readinessProbe.initialDelaySeconds | default 5 }}
periodSeconds: {{ .readinessProbe.periodSeconds | default 10 }}
timeoutSeconds: {{ .readinessProbe.timeoutSeconds | default 5 }}
failureThreshold: {{ .readinessProbe.failureThreshold | default 3 }}
{{- end }}
{{- if .startupProbe.enabled }}
startupProbe:
{{- include "components.probe" .startupProbe | nindent 2 }}
initialDelaySeconds: {{ .startupProbe.initialDelaySeconds | default 10 }}
periodSeconds: {{ .startupProbe.periodSeconds | default 10 }}
timeoutSeconds: {{ .startupProbe.timeoutSeconds | default 5 }}
failureThreshold: {{ .startupProbe.failureThreshold | default 30 }}
{{- end }}
{{- end -}}
{{/*
探针配置组件
*/}}
{{- define "components.probe" -}}
{{- if eq .type "http" }}
httpGet:
path: {{ .path | default "/health" }}
port: {{ .port | default "http" }}
{{- if .httpHeaders }}
httpHeaders:
{{- toYaml .httpHeaders | nindent 4 }}
{{- end }}
{{- else if eq .type "tcp" }}
tcpSocket:
port: {{ .port | default "http" }}
{{- else if eq .type "exec" }}
exec:
command:
{{- toYaml .command | nindent 4 }}
{{- else if eq .type "grpc" }}
grpc:
port: {{ .port | default "grpc" }}
{{- if .service }}
service: {{ .service }}
{{- end }}
{{- end }}
{{- end -}}
{{/*
环境变量组件
*/}}
{{- define "components.environment" -}}
{{- if or .env .envFrom .configMaps .secrets }}
env:
{{- range $key, $value := .env }}
- name: {{ $key }}
{{- if kindIs "string" $value }}
value: {{ $value | quote }}
{{- else if hasKey $value "value" }}
value: {{ $value.value | quote }}
{{- else if hasKey $value "valueFrom" }}
valueFrom:
{{- toYaml $value.valueFrom | nindent 4 }}
{{- end }}
{{- end }}
{{- range .configMaps }}
- name: {{ .envName | default (printf "%s_CONFIG" (.name | upper | replace "-" "_")) }}
valueFrom:
configMapKeyRef:
name: {{ .name }}
key: {{ .key }}
{{- end }}
{{- range .secrets }}
- name: {{ .envName | default (printf "%s_SECRET" (.name | upper | replace "-" "_")) }}
valueFrom:
secretKeyRef:
name: {{ .name }}
key: {{ .key }}
{{- end }}
{{- if .envFrom }}
envFrom:
{{- toYaml .envFrom | nindent 2 }}
{{- end }}
{{- end }}
{{- end -}}
8.4.3 模板库模式
# templates/_library.tpl
{{/*
标准标签库
*/}}
{{- define "library.labels" -}}
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ include "myapp.chart" . }}
{{- if .component }}
app.kubernetes.io/component: {{ .component }}
{{- end }}
{{- if .partOf }}
app.kubernetes.io/part-of: {{ .partOf }}
{{- end }}
{{- end -}}
{{/*
标准选择器标签库
*/}}
{{- define "library.selectorLabels" -}}
app.kubernetes.io/name: {{ include "myapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- if .component }}
app.kubernetes.io/component: {{ .component }}
{{- end }}
{{- end -}}
{{/*
标准注解库
*/}}
{{- define "library.annotations" -}}
{{- if .annotations }}
{{- toYaml .annotations }}
{{- end }}
{{- if .checksum }}
checksum/config: {{ .checksum }}
{{- end }}
{{- if .timestamp }}
deployment.kubernetes.io/revision: {{ now | unixEpoch | quote }}
{{- end }}
{{- end -}}
{{/*
标准资源名称库
*/}}
{{- define "library.fullname" -}}
{{- $name := default .Chart.Name .Values.nameOverride -}}
{{- if .component -}}
{{- printf "%s-%s-%s" .Release.Name $name .component | trunc 63 | trimSuffix "-" -}}
{{- else -}}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
{{- end -}}
{{- end -}}
{{/*
标准服务账户库
*/}}
{{- define "library.serviceAccountName" -}}
{{- if .serviceAccount.create -}}
{{- default (include "library.fullname" .) .serviceAccount.name -}}
{{- else -}}
{{- default "default" .serviceAccount.name -}}
{{- end -}}
{{- end -}}
{{/*
标准镜像库
*/}}
{{- define "library.image" -}}
{{- $registry := .image.registry | default .global.imageRegistry -}}
{{- $repository := .image.repository -}}
{{- $tag := .image.tag | default .Chart.AppVersion -}}
{{- if $registry -}}
{{- printf "%s/%s:%s" $registry $repository $tag -}}
{{- else -}}
{{- printf "%s:%s" $repository $tag -}}
{{- end -}}
{{- end -}}
8.5 模板调试技巧
8.5.1 调试输出技巧
# templates/debug.yaml
{{- if .Values.debug.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "myapp.fullname" . }}-debug
labels:
{{- include "myapp.labels" . | nindent 4 }}
data:
# 输出所有 Values
values.yaml: |
{{- .Values | toYaml | nindent 4 }}
# 输出 Chart 信息
chart.yaml: |
{{- .Chart | toYaml | nindent 4 }}
# 输出 Release 信息
release.yaml: |
{{- .Release | toYaml | nindent 4 }}
# 输出 Capabilities 信息
capabilities.yaml: |
{{- .Capabilities | toYaml | nindent 4 }}
# 输出模板函数结果
template-functions.yaml: |
fullname: {{ include "myapp.fullname" . }}
chart: {{ include "myapp.chart" . }}
labels: |
{{- include "myapp.labels" . | nindent 6 }}
selectorLabels: |
{{- include "myapp.selectorLabels" . | nindent 6 }}
# 输出计算结果
computed-values.yaml: |
isProduction: {{ include "myapp.isProduction" . }}
needsTLS: {{ include "myapp.needsTLS" . }}
totalResources: |
{{- include "myapp.sumResources" .Values.microservices | nindent 6 }}
filteredServices: |
{{- include "myapp.filterEnabled" .Values.microservices | toYaml | nindent 6 }}
{{- end }}
8.5.2 条件调试
# templates/_debug.tpl
{{/*
调试输出宏
*/}}
{{- define "debug.print" -}}
{{- if .Values.debug.enabled -}}
{{- printf "DEBUG: %s = %v" (index . 0) (index . 1) | print -}}
{{- end -}}
{{- end -}}
{{/*
调试断言
*/}}
{{- define "debug.assert" -}}
{{- $condition := index . 0 -}}
{{- $message := index . 1 -}}
{{- if not $condition -}}
{{- if $.Values.debug.enabled -}}
{{- printf "ASSERTION FAILED: %s" $message | fail -}}
{{- else -}}
{{- printf "Warning: %s" $message | print -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{/*
调试跟踪
*/}}
{{- define "debug.trace" -}}
{{- if .Values.debug.enabled -}}
{{- printf "TRACE: Entering template %s with context: %v" (index . 0) (index . 1) | print -}}
{{- end -}}
{{- end -}}
# 在模板中使用调试
# templates/deployment.yaml
{{- include "debug.trace" (list "deployment.yaml" .) }}
{{- include "debug.print" (list "replicaCount" .Values.replicaCount) }}
{{- include "debug.assert" (list (gt (.Values.replicaCount | int) 0) "Replica count must be greater than 0") }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }}
# ... 其他配置
8.5.3 模板测试
#!/bin/bash
# scripts/test-templates.sh
set -e
CHART_DIR="."
TEST_VALUES_DIR="test-values"
OUTPUT_DIR="test-output"
echo "Testing Helm templates..."
# 创建输出目录
mkdir -p $OUTPUT_DIR
# 测试默认值
echo "Testing with default values..."
helm template test $CHART_DIR > $OUTPUT_DIR/default.yaml
kubectl --dry-run=client apply -f $OUTPUT_DIR/default.yaml
# 测试不同的 values 文件
for values_file in $TEST_VALUES_DIR/*.yaml; do
if [ -f "$values_file" ]; then
test_name=$(basename "$values_file" .yaml)
echo "Testing with $test_name values..."
helm template test $CHART_DIR -f "$values_file" > "$OUTPUT_DIR/$test_name.yaml"
kubectl --dry-run=client apply -f "$OUTPUT_DIR/$test_name.yaml"
echo "✓ $test_name test passed"
fi
done
# 测试模板语法
echo "Linting templates..."
helm lint $CHART_DIR
# 测试模板渲染
echo "Testing template rendering..."
helm template test $CHART_DIR --debug > $OUTPUT_DIR/debug.yaml
echo "All template tests passed!"
# test-values/production.yaml
environment: production
replicaCount: 3
image:
tag: "1.0.0"
pullPolicy: Always
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
ingress:
enabled: true
tls: true
hosts:
- host: app.example.com
paths:
- path: /
pathType: Prefix
monitoring:
enabled: true
serviceMonitor:
enabled: true
security:
podSecurityPolicy:
enabled: true
networkPolicy:
enabled: true
# test-values/development.yaml
environment: development
replicaCount: 1
image:
tag: "latest"
pullPolicy: Always
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
autoscaling:
enabled: false
ingress:
enabled: true
tls: false
hosts:
- host: app-dev.example.com
paths:
- path: /
pathType: Prefix
debug:
enabled: true
monitoring:
enabled: false
8.6 性能优化
8.6.1 模板性能优化
# templates/_optimized.tpl
{{/*
缓存复杂计算结果
*/}}
{{- define "optimized.computeOnce" -}}
{{- if not .computed -}}
{{- $computed := dict -}}
{{- $_ := set $computed "fullname" (include "myapp.fullname" .) -}}
{{- $_ := set $computed "labels" (include "myapp.labels" . | fromYaml) -}}
{{- $_ := set $computed "selectorLabels" (include "myapp.selectorLabels" . | fromYaml) -}}
{{- $_ := set $computed "serviceAccountName" (include "myapp.serviceAccountName" .) -}}
{{- $_ := set . "computed" $computed -}}
{{- end -}}
{{- .computed -}}
{{- end -}}
{{/*
避免重复计算
*/}}
{{- define "optimized.memoize" -}}
{{- $key := index . 0 -}}
{{- $template := index . 1 -}}
{{- $context := index . 2 -}}
{{- if not $context.memoized -}}
{{- $_ := set $context "memoized" dict -}}
{{- end -}}
{{- if not (hasKey $context.memoized $key) -}}
{{- $result := include $template $context -}}
{{- $_ := set $context.memoized $key $result -}}
{{- end -}}
{{- index $context.memoized $key -}}
{{- end -}}
{{/*
批量处理优化
*/}}
{{- define "optimized.batchProcess" -}}
{{- $items := index . 0 -}}
{{- $template := index . 1 -}}
{{- $context := index . 2 -}}
{{- $results := list -}}
{{- range $item := $items -}}
{{- $itemContext := deepCopy $context -}}
{{- $_ := set $itemContext "item" $item -}}
{{- $result := include $template $itemContext -}}
{{- $results = append $results $result -}}
{{- end -}}
{{- $results -}}
{{- end -}}
8.6.2 减少模板复杂度
# templates/_simplified.tpl
{{/*
简化的资源定义
*/}}
{{- define "simplified.deployment" -}}
{{- $config := . -}}
apiVersion: apps/v1
kind: Deployment
metadata:
{{- include "simplified.metadata" $config | nindent 2 }}
spec:
{{- include "simplified.deploymentSpec" $config | nindent 2 }}
{{- end -}}
{{/*
简化的元数据
*/}}
{{- define "simplified.metadata" -}}
name: {{ .name | default (include "myapp.fullname" .) }}
labels:
{{- include "myapp.labels" . | nindent 2 }}
{{- with .extraLabels }}
{{- toYaml . | nindent 2 }}
{{- end }}
{{- with .annotations }}
annotations:
{{- toYaml . | nindent 2 }}
{{- end }}
{{- end -}}
{{/*
简化的部署规范
*/}}
{{- define "simplified.deploymentSpec" -}}
{{- if not .autoscaling.enabled }}
replicas: {{ .replicaCount | default 1 }}
{{- end }}
selector:
matchLabels:
{{- include "myapp.selectorLabels" . | nindent 4 }}
template:
metadata:
labels:
{{- include "myapp.selectorLabels" . | nindent 6 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: {{ include "library.image" . }}
{{- include "simplified.containerConfig" . | nindent 6 }}
{{- end -}}
8.7 模板安全
8.7.1 输入验证和清理
# templates/_security.tpl
{{/*
清理用户输入
*/}}
{{- define "security.sanitize" -}}
{{- $input := . -}}
{{- $input | replace ";" "" | replace "|" "" | replace "&" "" | replace "$" "" -}}
{{- end -}}
{{/*
验证镜像标签
*/}}
{{- define "security.validateImageTag" -}}
{{- $tag := . -}}
{{- if not $tag -}}
{{- fail "Image tag cannot be empty" -}}
{{- end -}}
{{- if eq $tag "latest" -}}
{{- if include "myapp.isProduction" $ -}}
{{- fail "Using 'latest' tag is not allowed in production" -}}
{{- end -}}
{{- end -}}
{{- if not (regexMatch "^[a-zA-Z0-9._-]+$" $tag) -}}
{{- fail (printf "Invalid image tag format: %s" $tag) -}}
{{- end -}}
{{- $tag -}}
{{- end -}}
{{/*
验证资源名称
*/}}
{{- define "security.validateResourceName" -}}
{{- $name := . -}}
{{- if not (regexMatch "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" $name) -}}
{{- fail (printf "Invalid resource name: %s. Must match RFC 1123" $name) -}}
{{- end -}}
{{- if gt (len $name) 63 -}}
{{- fail (printf "Resource name too long: %s (max 63 characters)" $name) -}}
{{- end -}}
{{- $name -}}
{{- end -}}
{{/*
验证环境变量值
*/}}
{{- define "security.validateEnvValue" -}}
{{- $value := . -}}
{{- if regexMatch ".*[\$`].*" $value -}}
{{- fail (printf "Environment variable contains unsafe characters: %s" $value) -}}
{{- end -}}
{{- $value -}}
{{- end -}}
8.7.2 权限控制模板
# templates/rbac.yaml
{{- if .Values.rbac.create }}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "myapp.fullname" . }}
labels:
{{- include "myapp.labels" . | nindent 4 }}
rules:
{{- range .Values.rbac.rules }}
- apiGroups:
{{- range .apiGroups }}
- {{ . | quote }}
{{- end }}
resources:
{{- range .resources }}
- {{ . | quote }}
{{- end }}
verbs:
{{- range .verbs }}
- {{ . | quote }}
{{- end }}
{{- if .resourceNames }}
resourceNames:
{{- range .resourceNames }}
- {{ . | quote }}
{{- end }}
{{- end }}
{{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: {{ include "myapp.fullname" . }}
labels:
{{- include "myapp.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "myapp.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ include "myapp.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}
8.8 实践练习
8.8.1 练习1:创建复杂的微服务模板
# values.yaml
microservices:
frontend:
enabled: true
image:
repository: "myapp/frontend"
tag: "1.0.0"
replicas: 2
service:
type: ClusterIP
port: 80
ingress:
enabled: true
host: "app.example.com"
resources:
requests:
memory: "128Mi"
cpu: "100m"
backend:
enabled: true
image:
repository: "myapp/backend"
tag: "1.0.0"
replicas: 3
service:
type: ClusterIP
port: 8080
database:
enabled: true
type: "postgresql"
resources:
requests:
memory: "256Mi"
cpu: "200m"
8.8.2 练习2:实现模板继承
# 创建基础模板
helm create microservice-base
# 创建具体服务模板
helm create user-service
helm create order-service
# 在具体服务中继承基础模板
8.8.3 练习3:添加调试功能
# 在 values.yaml 中添加调试配置
debug:
enabled: false
verbose: false
dryRun: false
# 在模板中添加调试输出
{{- if .Values.debug.enabled }}
# DEBUG: Current context
# {{- . | toYaml | nindent 2 }}
{{- end }}
8.9 故障排除
8.9.1 常见模板错误
# 语法错误调试
helm template myapp . --debug
# 检查特定模板
helm template myapp . --show-only templates/deployment.yaml
# 验证生成的 YAML
helm template myapp . | kubectl apply --dry-run=client -f -
# 检查模板函数
helm template myapp . --debug 2>&1 | grep "function"
8.9.2 性能问题诊断
# 测量模板渲染时间
time helm template myapp .
# 分析模板复杂度
find templates -name "*.yaml" -exec wc -l {} + | sort -n
# 检查循环复杂度
grep -r "range" templates/ | wc -l
8.9.3 调试技巧总结
- 使用 –debug 标志:显示详细的渲染过程
- 分段测试:逐个测试模板文件
- 输出中间结果:在模板中添加调试输出
- 验证语法:使用 helm lint 检查语法
- 测试边界条件:测试极端值和空值情况
8.10 本章小结
核心概念回顾
- 高级条件逻辑:复杂的 if/else 判断和多条件组合
- 循环和迭代:range 循环、嵌套循环、条件过滤
- 模板函数:内置函数、自定义函数、管道操作
- 模板继承:基础模板、组合模式、库模式
- 调试技巧:调试输出、条件调试、模板测试
- 性能优化:缓存计算、减少复杂度、批量处理
- 安全考虑:输入验证、权限控制、安全清理
技术要点总结
- 模板系统基于 Go text/template,支持复杂的逻辑控制
- 合理使用模板继承可以提高代码复用性
- 调试技巧对于复杂模板开发至关重要
- 性能优化需要平衡功能性和效率
- 安全性是模板开发中不可忽视的方面
最佳实践
- 模块化设计:将复杂逻辑拆分为小的模板函数
- 命名规范:使用清晰的模板和变量命名
- 文档注释:为复杂模板添加详细注释
- 测试驱动:编写全面的模板测试用例
- 性能监控:定期检查模板渲染性能
- 安全审查:定期审查模板的安全性
下一章预告
下一章我们将学习「安全与最佳实践」,探讨 Helm 在企业环境中的安全配置、权限管理、合规性要求、安全扫描、密钥管理等内容,帮助您构建安全可靠的 Helm 部署体系。