Helm charts: шаблонизация Kubernetes-манифестов на практике
Каждый, кто деплоил больше двух сервисов в Kubernetes, знает боль: десятки YAML-файлов, которые отличаются только именем, портом и парой переменных окружения. Helm решает эту проблему — превращает манифесты в параметризованные шаблоны с управлением зависимостями и жизненным циклом релизов.
Зачем Helm: проблема копипасты YAML
Без Helm типичный деплой микросервиса — это 5–7 файлов: Deployment, Service, Ingress, HPA, ConfigMap, Secret, ServiceAccount. Для 10 сервисов получаем 50–70 файлов, где 80% содержимого идентично. Kustomize частично решает проблему через overlays, но не даёт полноценной логики (циклы, условия) и не управляет релизами.
Helm даёт:
- Шаблонизацию — Go templates с функциями Sprig.
- Пакетирование — чарт = единица деплоя с версионированием.
- Управление релизами — install, upgrade, rollback с историей.
- Зависимости — подключение subcharts (PostgreSQL, Redis) одной строкой.
Структура чарта
my-service/
├── Chart.yaml # метаданные: имя, версия, зависимости
├── values.yaml # дефолтные значения параметров
├── templates/
│ ├── _helpers.tpl # переиспользуемые шаблоны
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── hpa.yaml
│ └── secrets.yaml
└── charts/ # зависимости (subcharts)
Chart.yaml — паспорт чарта:
apiVersion: v2
name: my-service
version: 1.3.0
appVersion: "2.1.0"
description: Backend API service
dependencies:
- name: postgresql
version: "15.x"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
values.yaml — параметры по умолчанию, которые переопределяются при деплое через -f или --set.
Шаблонизация: Values, range, if, include
Шаблоны Helm используют Go template syntax. Основные конструкции:
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-service.fullname" . }}
labels:
{{- include "my-service.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "my-service.selectorLabels" . | nindent 6 }}
template:
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: {{ .Values.service.port }}
env:
{{- range $key, $val := .Values.env }}
- name: {{ $key }}
value: {{ $val | quote }}
{{- end }}
{{- if .Values.resources }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- end }}
Ключевые приёмы:
{{ .Values.x }}— подстановка значения из values.yaml.{{- range }}— цикл по map или list (дефис убирает лишние пробелы).{{- if }}— условный рендеринг блока.{{ include "name" . | nindent N }}— вставка именованного шаблона с отступом.{{ toYaml .Values.x | nindent N }}— вставка произвольного YAML-блока.
Хелперы в _helpers.tpl:
{{- define "my-service.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
Хуки: pre-install, post-upgrade — миграции БД
Helm hooks позволяют запускать Job-ы в определённые моменты жизненного цикла релиза. Классический кейс — миграции базы данных перед обновлением приложения:
# templates/migration-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "my-service.fullname" . }}-migrate
annotations:
"helm.sh/hook": pre-upgrade,pre-install
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["npm", "run", "migrate"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ include "my-service.fullname" . }}-secrets
key: database-url
backoffLimit: 3
Доступные хуки: pre-install, post-install, pre-upgrade, post-upgrade, pre-delete, post-delete, pre-rollback, post-rollback. Вес (hook-weight) определяет порядок выполнения при нескольких хуках одного типа.
Зависимости: subchart и condition
Helm позволяет подключать другие чарты как зависимости. Это удобно для dev-окружений, где нужна локальная БД:
# Chart.yaml
dependencies:
- name: postgresql
version: "15.5.x"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
- name: redis
version: "19.x"
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
# values.yaml
postgresql:
enabled: true # включено для dev
auth:
postgresPassword: devpass
database: myapp
redis:
enabled: false # выключено, используем managed
В production переопределяем через values-prod.yaml:
# values-prod.yaml
postgresql:
enabled: false # используем RDS
redis:
enabled: false # используем ElastiCache
Команда деплоя: helm upgrade --install my-service ./my-service -f values-prod.yaml -n production.
Практика: деплоим микросервис с Ingress, HPA, Secrets
Полный values.yaml для production-ready сервиса:
replicaCount: 3
image:
repository: ghcr.io/myorg/api-service
tag: "2.1.0"
pullPolicy: IfNotPresent
service:
port: 8080
type: ClusterIP
ingress:
enabled: true
className: nginx
hosts:
- host: api.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: api-tls
hosts:
- api.example.com
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 20
targetCPUUtilization: 70
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mi
env:
NODE_ENV: production
LOG_LEVEL: info
secrets:
DATABASE_URL: "postgresql://user:pass@rds.amazonaws.com:5432/myapp"
JWT_SECRET: "super-secret-key"
Шаблон HPA с условным рендерингом:
# templates/hpa.yaml
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "my-service.fullname" . }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "my-service.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilization }}
{{- end }}
Деплой одной командой:
helm upgrade --install api-service ./my-service \
-f values-prod.yaml \
--set image.tag=$CI_COMMIT_SHA \
--namespace production \
--wait --timeout 5m
Флаг --wait заставляет Helm дождаться, пока все поды станут Ready. Если деплой не завершится за 5 минут — автоматический rollback.
Вывод
Helm превращает хаос из десятков YAML-файлов в управляемые, версионированные пакеты. Шаблонизация через Go templates покрывает 95% потребностей, хуки решают проблему миграций, а subcharts позволяют собирать окружение из готовых компонентов. Начните с простого чарта для одного сервиса, отладьте через helm template (рендерит без деплоя), и постепенно добавляйте HPA, Ingress и зависимости по мере роста проекта.