本章概述

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 调试技巧总结

  1. 使用 –debug 标志:显示详细的渲染过程
  2. 分段测试:逐个测试模板文件
  3. 输出中间结果:在模板中添加调试输出
  4. 验证语法:使用 helm lint 检查语法
  5. 测试边界条件:测试极端值和空值情况

8.10 本章小结

核心概念回顾

  1. 高级条件逻辑:复杂的 if/else 判断和多条件组合
  2. 循环和迭代:range 循环、嵌套循环、条件过滤
  3. 模板函数:内置函数、自定义函数、管道操作
  4. 模板继承:基础模板、组合模式、库模式
  5. 调试技巧:调试输出、条件调试、模板测试
  6. 性能优化:缓存计算、减少复杂度、批量处理
  7. 安全考虑:输入验证、权限控制、安全清理

技术要点总结

  • 模板系统基于 Go text/template,支持复杂的逻辑控制
  • 合理使用模板继承可以提高代码复用性
  • 调试技巧对于复杂模板开发至关重要
  • 性能优化需要平衡功能性和效率
  • 安全性是模板开发中不可忽视的方面

最佳实践

  1. 模块化设计:将复杂逻辑拆分为小的模板函数
  2. 命名规范:使用清晰的模板和变量命名
  3. 文档注释:为复杂模板添加详细注释
  4. 测试驱动:编写全面的模板测试用例
  5. 性能监控:定期检查模板渲染性能
  6. 安全审查:定期审查模板的安全性

下一章预告

下一章我们将学习「安全与最佳实践」,探讨 Helm 在企业环境中的安全配置、权限管理、合规性要求、安全扫描、密钥管理等内容,帮助您构建安全可靠的 Helm 部署体系。