Модуль 15 / 19
Helm, Kustomize и воспроизводимая доставка
Содержание
1. Цели и результат модуля
Вы научитесь отделять исходные manifests от rendered output, выбирать Kustomize или Helm и проводить изменение через render, validation, diff, apply/upgrade и rollback.
- Kustomize преобразует Kubernetes resources без шаблонного языка
- Helm упаковывает templates, values и release lifecycle
- rendered YAML нужно проверять до mutation
- version chart не равна appVersion
- секреты нельзя безопасно хранить в обычном values.yaml
2. Теория: воспроизводимая доставка
Воспроизводимость требует зафиксированных версий tool/chart/image, reviewable inputs и одинакового render. Команда, зависящая от latest, локальных файлов или ручного --set без записи, плохо воспроизводится.
Package manager не заменяет CI, policy, observability и backup. Он отвечает за формирование и lifecycle набора Kubernetes objects.
3. Kustomize: base и overlays
Kustomize работает со структурированными Kubernetes resources. Base содержит общее, overlay добавляет environment-specific namespace, patches, images, replicas и generators. Он встроен в kubectl как apply -k и kustomize.
configMapGenerator добавляет content hash к имени и обновляет references, поэтому изменение конфигурации меняет Pod template и может вызвать rollout.
4. Helm: chart, values и release
Chart содержит Chart.yaml, defaults values.yaml, templates и optional schema. Helm render использует release context и values; install/upgrade сохраняют release revisions в кластере. appVersion — метаданные приложения, version — версия package chart.
Values — публичный configuration interface chart. Слишком гибкие templates становятся непроверяемой программой; values.schema.json делает контракт явным.
5. Install, upgrade и rollback
upgrade --install идемпотентно выбирает создание или изменение release. --wait ждёт готовности поддерживаемых resources до timeout. history показывает revisions, rollback создаёт новую revision на основе старой — не перематывает историю clock.
Rollback manifests не откатывает внешние side effects, database migration или содержимое PV. Совместимость данных планируется отдельно.
6. CRD, hooks, secrets и ownership
CRDs в chart имеют особый lifecycle и обычно не удаляются автоматически. Hooks — Jobs/resources вне обычного порядка, их side effects нужно делать идемпотентными. Перед установкой чужого chart изучайте rendered RBAC, securityContext, images и cluster-scoped objects.
Helm values и rendered Secret — не secret storage. Используйте внешнее управление секретами и не передавайте чувствительные значения в command history.
7. Методические указания
- Фиксируйте tool, chart и image versions
- Сначала lint/render, затем server dry-run и diff
- Сохраняйте environment inputs в Git
- Не редактируйте rendered YAML вручную
- Не смешивайте ownership Helm и kubectl одного поля
- Проверяйте cluster-scoped resources и hooks
- Тестируйте rollback вместе с данными
- Не используйте --set для недокументированных production changes
8. Лабораторная работа: Kustomize overlay
Base задаёт один Deployment, overlay — namespace, prefix, две replicas и APP_MODE=lab. Проверьте hash ConfigMap и автоматически переписанную reference.
mkdir -p /tmp/k8s-m15/kustomize/base /tmp/k8s-m15/kustomize/overlays/lab
cat > /tmp/k8s-m15/kustomize/base/deployment.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata: {name: app}
spec:
replicas: 1
selector: {matchLabels: {app: kustomize-web}}
template:
metadata: {labels: {app: kustomize-web}}
spec:
containers:
- name: nginx
image: nginx:1.29-alpine
envFrom: [{configMapRef: {name: app-config}}]
EOF
cat > /tmp/k8s-m15/kustomize/base/kustomization.yaml <<'EOF'
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources: [deployment.yaml]
configMapGenerator:
- name: app-config
literals: [APP_MODE=base]
EOF
cat > /tmp/k8s-m15/kustomize/overlays/lab/kustomization.yaml <<'EOF'
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: k8s-m15-kustomize
namePrefix: lab-
resources: [../../base]
replicas:
- {name: app, count: 2}
configMapGenerator:
- name: app-config
behavior: merge
literals: [APP_MODE=lab]
EOFkubectl create namespace k8s-m15-kustomize
kubectl kustomize /tmp/k8s-m15/kustomize/overlays/lab > /tmp/k8s-m15/rendered.yaml
sed -n '1,160p' /tmp/k8s-m15/rendered.yaml
kubectl apply --dry-run=server -f /tmp/k8s-m15/rendered.yaml
kubectl diff -k /tmp/k8s-m15/kustomize/overlays/lab || [ "$?" -eq 1 ]
kubectl apply --server-side --field-manager=kustomize-lab -k /tmp/k8s-m15/kustomize/overlays/lab
kubectl -n k8s-m15-kustomize rollout status deployment/lab-app --timeout=120s
kubectl -n k8s-m15-kustomize get deployment,configmap,pod
kubectl -n k8s-m15-kustomize get deployment lab-app -o jsonpath='{.spec.template.spec.containers[0].envFrom[0].configMapRef.name}{"\n"}'9. Лабораторная работа: собственный Helm chart
Минимальный chart имеет явный values contract. Выполните lint, сохраните render, проведите server dry-run и только затем создайте release.
mkdir -p /tmp/k8s-m15/chart/templates
cat > /tmp/k8s-m15/chart/Chart.yaml <<'EOF'
apiVersion: v2
name: course-web
description: Minimal chart for Kubernetes Fundamentals module 15
type: application
version: 0.1.0
appVersion: "1.29"
EOF
cat > /tmp/k8s-m15/chart/values.yaml <<'EOF'
replicaCount: 2
image: {repository: nginx, tag: 1.29-alpine}
service: {port: 80}
EOF
cat > /tmp/k8s-m15/chart/values.schema.json <<'EOF'
{"$schema":"https://json-schema.org/draft-07/schema#","type":"object","required":["replicaCount","image","service"],"properties":{"replicaCount":{"type":"integer","minimum":1,"maximum":5},"image":{"type":"object","required":["repository","tag"]},"service":{"type":"object","required":["port"]}}}
EOF
cat > /tmp/k8s-m15/chart/templates/_helpers.tpl <<'EOF'
{{- define "course-web.name" -}}course-web{{- end -}}
EOF
cat > /tmp/k8s-m15/chart/templates/deployment.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "course-web.name" . }}
labels: {app.kubernetes.io/name: {{ include "course-web.name" . }}, app.kubernetes.io/managed-by: {{ .Release.Service }}}
spec:
replicas: {{ .Values.replicaCount }}
selector: {matchLabels: {app.kubernetes.io/name: {{ include "course-web.name" . }}}}
template:
metadata: {labels: {app.kubernetes.io/name: {{ include "course-web.name" . }}}}
spec:
containers:
- name: nginx
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports: [{name: http, containerPort: {{ .Values.service.port }}}]
EOF
cat > /tmp/k8s-m15/chart/templates/service.yaml <<'EOF'
apiVersion: v1
kind: Service
metadata: {name: {{ include "course-web.name" . }}}
spec:
selector: {app.kubernetes.io/name: {{ include "course-web.name" . }}}
ports: [{name: http, port: {{ .Values.service.port }}, targetPort: http}]
EOFhelm version --short
helm lint /tmp/k8s-m15/chart
helm template course-web /tmp/k8s-m15/chart --namespace k8s-m15-helm > /tmp/k8s-m15/helm-rendered.yaml
kubectl apply --dry-run=server -f /tmp/k8s-m15/helm-rendered.yaml
helm upgrade --install course-web /tmp/k8s-m15/chart \
--namespace k8s-m15-helm --create-namespace --wait --timeout 3m
helm status course-web -n k8s-m15-helm
kubectl -n k8s-m15-helm get deployment,service,pod10. Лабораторная работа: upgrade и rollback
Измените replicas через values, исследуйте effective values/history, затем откатитесь к revision 1. После rollback история получает новую revision.
helm upgrade course-web /tmp/k8s-m15/chart -n k8s-m15-helm \
--set replicaCount=3 --wait --timeout 3m
helm get values course-web -n k8s-m15-helm --all
helm history course-web -n k8s-m15-helm
kubectl -n k8s-m15-helm get deployment course-web
helm rollback course-web 1 -n k8s-m15-helm --wait --timeout 3m
helm history course-web -n k8s-m15-helm
kubectl -n k8s-m15-helm get deployment course-web11. Диагностика до mutation
Строка zero нарушает JSON Schema integer и upgrade не создаёт revision. Сломанный Kustomize path также прекращает render; уже применённый Deployment не меняется.
helm upgrade course-web /tmp/k8s-m15/chart -n k8s-m15-helm \
--set-string replicaCount=zero || true
helm history course-web -n k8s-m15-helm
kubectl -n k8s-m15-helm get deployment course-web -o jsonpath='replicas={.spec.replicas}{"\n"}'
cp -a /tmp/k8s-m15/kustomize/overlays/lab /tmp/k8s-m15/kustomize/overlays/broken
sed -i 's#../../base#../../missing-base#' /tmp/k8s-m15/kustomize/overlays/broken/kustomization.yaml
kubectl kustomize /tmp/k8s-m15/kustomize/overlays/broken || true
kubectl -n k8s-m15-kustomize get deployment lab-appПорядок: versions/dependencies → input values/overlay → lint/build → rendered YAML → schema/server dry-run → diff → release events/status → workload diagnostics.
12. Контроль, критерии освоения и cleanup
- Чем base отличается от overlay?
- Зачем hash ConfigMap?
- version против appVersion?
- Что хранит Helm revision?
- Rollback удаляет database migration?
- Зачем values schema?
- Почему rendered Secret небезопасен?
- Чем hook опаснее обычного resource?
- Можно ли смешивать Helm и kubectl ownership?
- Почему latest разрушает воспроизводимость?
helm uninstall course-web -n k8s-m15-helm
kubectl delete namespace k8s-m15-helm k8s-m15-kustomize --ignore-not-found --wait=true
rm -rf /tmp/k8s-m15
helm list -A
kubectl get namespace k8s-m15-helm k8s-m15-kustomize 2>&1 || trueМодуль освоен, если вы получаете одинаковый render из зафиксированных inputs, проводите Helm upgrade/rollback и блокируете неверное значение до mutation.