2.1 Chart 结构解析

2.1.1 Chart 目录结构

my-app/
├── Chart.yaml          # Chart 元数据文件
├── values.yaml         # 默认配置值
├── charts/             # 依赖的子 Chart
├── templates/          # 模板文件目录
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── configmap.yaml
│   ├── _helpers.tpl    # 模板助手函数
│   └── NOTES.txt       # 安装后的提示信息
├── .helmignore         # 忽略文件列表
└── README.md           # Chart 说明文档

2.1.2 Chart.yaml 详解

# Chart.yaml - Chart 元数据
apiVersion: v2
name: my-app
description: A Helm chart for my application
type: application
version: 0.1.0
appVersion: "1.0.0"

# 可选字段
keywords:
  - web
  - application
  - microservice

home: https://example.com
sources:
  - https://github.com/example/my-app

maintainers:
  - name: John Doe
    email: john@example.com
    url: https://johndoe.dev

# 依赖声明
dependencies:
  - name: postgresql
    version: "11.6.12"
    repository: https://charts.bitnami.com/bitnami
    condition: postgresql.enabled

# 注解
annotations:
  category: Application
  licenses: Apache-2.0

2.1.3 values.yaml 结构

# values.yaml - 默认配置值

# 应用配置
replicaCount: 1

image:
  repository: nginx
  pullPolicy: IfNotPresent
  tag: "1.21.0"

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

# 服务账户
serviceAccount:
  create: true
  annotations: {}
  name: ""

# Pod 注解和标签
podAnnotations: {}
podLabels: {}

# 安全上下文
podSecurityContext:
  fsGroup: 2000

securityContext:
  capabilities:
    drop:
    - ALL
  readOnlyRootFilesystem: true
  runAsNonRoot: true
  runAsUser: 1000

# 服务配置
service:
  type: ClusterIP
  port: 80
  targetPort: 8080

# Ingress 配置
ingress:
  enabled: false
  className: ""
  annotations: {}
  hosts:
    - host: chart-example.local
      paths:
        - path: /
          pathType: Prefix
  tls: []

# 资源限制
resources:
  limits:
    cpu: 500m
    memory: 512Mi
  requests:
    cpu: 250m
    memory: 256Mi

# 自动扩缩容
autoscaling:
  enabled: false
  minReplicas: 1
  maxReplicas: 100
  targetCPUUtilizationPercentage: 80

# 节点选择
nodeSelector: {}
tolerations: []
affinity: {}

2.2 Go 模板语法基础

2.2.1 模板语法概述

Helm 使用 Go 的 text/template 包进行模板渲染,支持变量替换、条件判断、循环等功能。

graph LR
    A[模板文件] --> B[Go Template 引擎]
    C[Values] --> B
    D[内置对象] --> B
    B --> E[渲染后的 YAML]
    E --> F[Kubernetes 资源]

2.2.2 基本语法

变量引用

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Values.nameOverride | default .Chart.Name }}
  labels:
    app: {{ .Chart.Name }}
    version: {{ .Chart.Version }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Chart.Name }}
  template:
    metadata:
      labels:
        app: {{ .Chart.Name }}
    spec:
      containers:
      - name: {{ .Chart.Name }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        imagePullPolicy: {{ .Values.image.pullPolicy }}
        ports:
        - containerPort: {{ .Values.service.targetPort }}

条件判断

# templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ include "my-app.fullname" . }}
  labels:
    {{- include "my-app.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: {{ .Values.service.targetPort }}
      protocol: TCP
      name: http
  selector:
    {{- include "my-app.selectorLabels" . | nindent 4 }}

{{- if eq .Values.service.type "LoadBalancer" }}
  loadBalancerIP: {{ .Values.service.loadBalancerIP }}
{{- end }}

{{- if .Values.service.annotations }}
  annotations:
    {{- toYaml .Values.service.annotations | nindent 4 }}
{{- end }}

循环遍历

# templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "my-app.fullname" . }}-config
data:
  {{- range $key, $value := .Values.config }}
  {{ $key }}: {{ $value | quote }}
  {{- end }}
  
  # 遍历列表
  allowed-hosts: |
    {{- range .Values.allowedHosts }}
    - {{ . }}
    {{- end }}

2.2.3 管道和函数

常用函数

# 字符串处理
name: {{ .Values.name | upper }}           # 转大写
name: {{ .Values.name | lower }}           # 转小写
name: {{ .Values.name | title }}           # 首字母大写
name: {{ .Values.name | quote }}           # 添加引号
name: {{ .Values.name | squote }}          # 添加单引号

# 默认值
name: {{ .Values.name | default "my-app" }}
port: {{ .Values.port | default 8080 }}

# 类型转换
replicas: {{ .Values.replicas | int }}
enabled: {{ .Values.enabled | bool }}

# 编码
data: {{ .Values.data | b64enc }}          # Base64 编码
password: {{ .Values.password | sha256sum }} # SHA256 哈希

# 日期时间
created: {{ now | date "2006-01-02T15:04:05Z" }}

YAML 处理

# toYaml 函数
resources:
  {{- toYaml .Values.resources | nindent 2 }}

# 缩进控制
labels:
  {{- include "my-app.labels" . | nindent 4 }}

# 去除空白
metadata:
  {{- with .Values.podAnnotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}

2.3 内置对象详解

2.3.1 根对象

# .Values - 来自 values.yaml 和用户提供的值
image: {{ .Values.image.repository }}

# .Chart - Chart.yaml 中的信息
name: {{ .Chart.Name }}
version: {{ .Chart.Version }}

# .Release - 当前 Release 信息
release-name: {{ .Release.Name }}
release-namespace: {{ .Release.Namespace }}
release-service: {{ .Release.Service }}

# .Capabilities - Kubernetes 集群能力
api-version: {{ .Capabilities.APIVersions.Has "apps/v1" }}
kube-version: {{ .Capabilities.KubeVersion.Version }}

# .Template - 当前模板信息
template-name: {{ .Template.Name }}
template-basepath: {{ .Template.BasePath }}

2.3.2 .Values 对象使用

# 访问嵌套值
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}

# 使用 with 简化访问
{{- with .Values.image }}
image: {{ .repository }}:{{ .tag }}
pullPolicy: {{ .pullPolicy }}
{{- end }}

# 检查值是否存在
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
# ...
{{- end }}

2.3.3 .Chart 对象应用

# 使用 Chart 信息作为标签
labels:
  app.kubernetes.io/name: {{ .Chart.Name }}
  app.kubernetes.io/version: {{ .Chart.AppVersion }}
  app.kubernetes.io/managed-by: {{ .Release.Service }}
  helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}

# 生成资源名称
name: {{ .Chart.Name }}-{{ .Release.Name }}

2.4 模板助手函数

2.4.1 _helpers.tpl 文件

{{/*
_helpers.tpl - 模板助手函数定义
*/}}

{{/*
展开 Chart 全名
*/}}
{{- define "my-app.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
创建 Chart 名称和版本标签
*/}}
{{- define "my-app.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
通用标签
*/}}
{{- define "my-app.labels" -}}
helm.sh/chart: {{ include "my-app.chart" . }}
{{ include "my-app.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
选择器标签
*/}}
{{- define "my-app.selectorLabels" -}}
app.kubernetes.io/name: {{ include "my-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
创建服务账户名称
*/}}
{{- define "my-app.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "my-app.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

{{/*
生成安全的资源名称
*/}}
{{- define "my-app.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

2.4.2 使用助手函数

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "my-app.fullname" . }}
  labels:
    {{- include "my-app.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "my-app.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "my-app.selectorLabels" . | nindent 8 }}
    spec:
      serviceAccountName: {{ include "my-app.serviceAccountName" . }}
      containers:
      - name: {{ .Chart.Name }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"

2.5 实践练习

2.5.1 练习1:创建基础 Chart

# 1. 创建新的 Chart
helm create web-app
cd web-app

# 2. 查看生成的文件结构
tree .

# 3. 检查模板语法
helm lint .

# 4. 渲染模板查看输出
helm template my-release .

# 5. 安装测试
helm install my-web-app . --dry-run --debug

2.5.2 练习2:自定义模板

# 修改 values.yaml
replicaCount: 2

image:
  repository: nginx
  tag: "1.21.0"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80
  targetPort: 80

# 添加自定义配置
config:
  server_name: "my-web-app"
  worker_processes: "auto"
  client_max_body_size: "1m"

allowedHosts:
  - "example.com"
  - "www.example.com"
  - "api.example.com"
# 创建 templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "web-app.fullname" . }}-config
  labels:
    {{- include "web-app.labels" . | nindent 4 }}
data:
  nginx.conf: |
    server {
        listen {{ .Values.service.targetPort }};
        server_name {{ .Values.config.server_name }};
        
        {{- range .Values.allowedHosts }}
        # Allow {{ . }}
        {{- end }}
        
        client_max_body_size {{ .Values.config.client_max_body_size }};
        
        location / {
            root /usr/share/nginx/html;
            index index.html index.htm;
        }
    }
  
  {{- range $key, $value := .Values.config }}
  {{ $key }}: {{ $value | quote }}
  {{- end }}

2.5.3 练习3:条件渲染

# 修改 values.yaml 添加功能开关
features:
  monitoring: true
  logging: false
  backup: true

monitoring:
  enabled: true
  port: 9090
  path: "/metrics"

logging:
  level: "info"
  format: "json"
# 创建 templates/monitoring.yaml
{{- if .Values.features.monitoring }}
apiVersion: v1
kind: Service
metadata:
  name: {{ include "web-app.fullname" . }}-monitoring
  labels:
    {{- include "web-app.labels" . | nindent 4 }}
    component: monitoring
spec:
  type: ClusterIP
  ports:
    - port: {{ .Values.monitoring.port }}
      targetPort: monitoring
      protocol: TCP
      name: monitoring
  selector:
    {{- include "web-app.selectorLabels" . | nindent 4 }}
---
apiVersion: v1
kind: ServiceMonitor
metadata:
  name: {{ include "web-app.fullname" . }}
  labels:
    {{- include "web-app.labels" . | nindent 4 }}
spec:
  selector:
    matchLabels:
      {{- include "web-app.selectorLabels" . | nindent 6 }}
      component: monitoring
  endpoints:
  - port: monitoring
    path: {{ .Values.monitoring.path }}
{{- end }}

2.6 调试和验证

2.6.1 模板调试技巧

# 1. 语法检查
helm lint my-chart/

# 2. 渲染模板(不安装)
helm template my-release my-chart/

# 3. 调试模式渲染
helm template my-release my-chart/ --debug

# 4. 指定 Values 文件
helm template my-release my-chart/ -f custom-values.yaml

# 5. 设置特定值
helm template my-release my-chart/ --set replicaCount=3

# 6. 只渲染特定模板
helm template my-release my-chart/ -s templates/deployment.yaml

2.6.2 常见错误和解决

# 错误1:模板语法错误
# Error: template: my-chart/templates/deployment.yaml:10:14: 
# executing "my-chart/templates/deployment.yaml" at <.Values.image.tag>: 
# nil pointer evaluating interface {}.tag

# 解决:检查 Values 路径是否正确
{{- with .Values.image }}
image: {{ .repository }}:{{ .tag | default "latest" }}
{{- end }}

# 错误2:缩进问题
# Error: YAML parse error on my-chart/templates/service.yaml

# 解决:使用正确的缩进函数
labels:
  {{- include "my-app.labels" . | nindent 4 }}

# 错误3:未定义的模板函数
# Error: template: my-chart/templates/deployment.yaml:5:22: 
# executing "my-chart/templates/deployment.yaml" at <include "my-app.fullname" .>: 
# error calling include: template: no template "my-app.fullname" associated with template

# 解决:确保在 _helpers.tpl 中定义了函数

2.7 本章小结

2.7.1 核心概念回顾

  • Chart 结构:标准的目录布局和文件组织
  • 模板语法:Go template 的基本语法和高级特性
  • 内置对象:.Values、.Chart、.Release 等对象的使用
  • 助手函数:可重用的模板片段定义和调用

2.7.2 技术要点总结

  1. 文件组织:合理的目录结构和文件命名
  2. 语法掌握:变量引用、条件判断、循环遍历
  3. 函数应用:内置函数和自定义函数的使用
  4. 调试技能:模板渲染和错误排查方法
  5. 最佳实践:代码复用和可维护性

2.7.3 最佳实践

  • 使用有意义的变量名和注释
  • 合理使用助手函数避免重复代码
  • 添加适当的条件判断和默认值
  • 保持模板的简洁和可读性
  • 定期进行语法检查和测试

2.7.4 下一章预告

下一章我们将深入学习 Values 文件与配置管理,包括: - Values 文件的层级结构 - 配置覆盖和合并机制 - 环境特定配置管理 - 配置验证和约束

通过下一章的学习,你将掌握如何灵活地管理不同环境的配置。