✻ Урок 5.9 · Тема 5: Kubernetes и Helm
Helm: упаковываем «Заметки» в чарт
Содержание урока
Зачем это нужно
К этому уроку приложение «Заметки» живёт в четырёх отдельных файлах-манифестах: Deployment, Service, ConfigMap и HTTPRoute. Манифест (manifest) это YAML-файл с описанием объекта Kubernetes: «вот такой Deployment, вот столько реплик, вот такой образ». Ты писал их в уроках 5.2, 5.3, 5.4 и 5.6.
Теперь представь, что нужно второе окружение, например dev рядом с prod. Самый простой путь: скопировать файлы и поправить три строки (хост, число реплик, тег образа). Через месяц копий будет три, правки разойдутся, и никто не вспомнит, чем prod отличается от dev намеренно, а чем случайно. Helm (по-русски «штурвал»; это программа, которая собирает YAML из шаблонов и отправляет его в кластер) решает это так: манифест превращается в шаблон (template) с пропусками, как бланк в канцелярии, а значения пропусков лежат в отдельном маленьком файле. Тег образа (image tag) это метка версии у образа контейнера, например 0.4.1: по ней кластер понимает, какую именно сборку приложения запускать (подробно в уроке 6.3 и дальше по курсу).
Второе, что даёт Helm и чего нет у kubectl apply: история установок и откат одной командой. Откат (rollback) это возврат к предыдущей рабочей версии, как «отменить» в редакторе: без него после неудачного выпуска ты в панике ищешь вчерашние файлы. Выкатил плохую версию, набрал helm rollback, и через минуту всё как было.
На работе Helm встречается каждый день. Почти всё чужое ставится готовыми чартами (chart, то есть готовыми «пакетами» Helm с шаблонами внутри): мониторинг, контроллеры входа (программы, которые принимают трафик снаружи и раздают его сервисам, урок 5.4), операторы баз данных. Оператор (operator) это программа в кластере, которая сама ухаживает за сложной штукой вроде базы данных: создаёт реплики, делает бэкапы, чинит сбои, как дежурный админ, который не спит (подробно в уроке 9.2). Свои сервисы команды тоже упаковывают в чарты, чтобы деплоить одинаково.
Шаг проекта: приложение «Заметки» переезжает в чарт helm/notes версии 0.1.0, релиз notes ставится в namespace notes, а старые манифесты приложения удаляются из k8s/base/.
Что нужно знать
- Урок 5.2: Pod и Deployment: чарт генерирует Deployment, нужно понимать его поля (
replicas,selector,template). - Урок 5.3: Service и DNS: Service
notesнаходит поды по меткам (labels, пары «ключ: значение» на объекте, как бирки на чемодане), метки понадобятся в хелперах. Хелпер (helper) это кусок шаблона, который записан один раз и вызывается по имени из других шаблонов, как функция; разберём ниже. - Урок 5.4: Вход в кластер: Gateway
notes-gwи HTTPRoute, который мы упакуем в чарт. - Урок 5.6: ConfigMap и Secret: ConfigMap
notes-configпереезжает в чарт, а Secretnotes-dbостаётся снаружи. - Урок 5.7: Пробы, ресурсы, rolling update: пробы и ресурсы переезжают в шаблон. Rolling update это постепенная замена старых подов новыми без остановки сервиса: сначала поднимается новый, потом гасится старый, как смена караула.
- Урок 5.8: Job, CronJob, DaemonSet: CronJob бэкапа остаётся платформенным манифестом.
Картина целиком
Helm похож на форму с пропусками в канцелярии. Бланк заявления один: «Я, __, прошу __». Сотрудник берёт бланк, вписывает значения и получает готовый документ. Бланк можно использовать сотни раз, а значения для каждого случая записаны отдельно.
- Чарт (chart) это бланк: каталог с шаблонами манифестов.
- Values (значения) это то, что вписывается в пропуски: файл
values.yaml. - Релиз (release) это уже заполненный и «сданный» документ: чарт, установленный в кластер под именем.
- Ревизия (revision) это номер версии релиза: каждое обновление даёт новую.
Вот весь путь от файлов до кластера:
flowchart LR
subgraph M["твоя машина: helm/notes/"]
CH["Chart.yaml"]
V["values.yaml<br>values-dev.yaml"]
T["templates/<br>deployment, service, ..."]
end
V --> R
T --> R
R["рендер: шаблоны + значения<br>= обычный YAML (в памяти)<br>команда helm upgrade --install"] -->|"YAML"| API["API-сервер кластера<br>создаёт Deployment, Service, ..."]
R -->|"записывает «ревизия 1»"| S["Secret<br>sh.helm.release.v1.notes.v1"]
Главное, что нужно запомнить: Helm работает на твоей машине. Он берёт шаблоны, подставляет значения (это называется рендер, render: «отрисовать» готовый документ из бланка), получает обычный YAML и отправляет его в API-сервер (API server, «приёмная» кластера, через которую проходят все команды, урок 5.1), как это сделал бы kubectl apply. Отдельной программы Helm внутри кластера нет. Запись «в кластере установлена ревизия 1» лежит в обычном Secret.
Дальше по порядку: что внутри чарта, как значения попадают в шаблон, какие бывают конструкции в шаблоне, что такое релиз и его история, и что в чарт класть не нужно.
Теория
Зачем нужен Helm: проблема копий
Kubernetes принимает только готовый YAML, в нём нет ни переменных, ни условий. Для одного окружения это нормально. Для двух начинается копирование файлов, а копии живут отдельной жизнью: в одну внесли исправление, в другую забыли. К тому же у kubectl apply нет памяти: он не знает, что ты применял вчера, и не умеет вернуться назад.
Бухгалтерия печатает счета. Можно набирать каждый счёт с нуля (копии файлов), а можно завести бланк с пропусками «клиент», «сумма», «дата». Бланк исправили один раз, и все следующие счета правильные. Оговорка: бланк делает только текст. Отправить его клиенту, записать в журнал и при ошибке отозвать умеет уже сам Helm как программа, у бланка этого нет.
Helm делает четыре вещи:
- Шаблонизация: подставляет значения в шаблоны.
- Упаковка: собирает шаблоны и значения по умолчанию в один каталог-чарт, который можно положить в реестр и поставить в другом кластере.
- Учёт: запоминает, что и с какими значениями установлено (релиз и его ревизии).
- Откат: возвращает состояние любой прошлой ревизии.
Хост приложения notes.lab в манифесте HTTPRoute написан прямо в файле. Чтобы для dev он был dev.notes.lab, без Helm пришлось бы держать два файла. С Helm в шаблоне стоит пропуск, а значение задаётся при установке: --set gateway.host=dev.notes.lab. Файл шаблона один.
Осторожно, путаница: Helm часто называют «менеджером пакетов Kubernetes» и думают, что он что-то запускает в кластере. Это не так: он только формирует YAML и отправляет его в API-сервер, дальше всё делает сам Kubernetes. Если Helm сломается, работающие поды это не затронет.
Прикинь сам: ты удалил
helmсо своего ноутбука. Что станет с подами в кластере?
Ничего: поды и Service живут сами по себе. Пропадёт только удобство управления релизом (обновление и откат), а программу можно поставить снова.
Проверь понимание: что изменится в кластере, если удалить программу
helmсо своего ноутбука? Перестанет ли приложение работать?
Ответ
Ничего не изменится. Поды, Service и остальные объекты живут в кластере сами по себе. Пропадёт только возможность управлять релизом: обновлять, откатывать. Программу можно поставить снова, она прочитает историю из кластера.
Главное: Helm работает на твоей машине: рендерит шаблоны в YAML и отправляет его в API-сервер, а внутри кластера отдельной программы нет.
Чтобы шаблоны можно было хранить и передавать, их складывают в чарт.
Чарт: что внутри каталога
Шаблоны нужно где-то хранить и как-то передавать другим людям. Чарт (chart) это договорённость о структуре каталога: Helm знает, где искать шаблоны, значения и описание пакета. Без такой договорённости у каждой команды были бы свои папки и свои скрипты сборки.
Чарт похож на коробку с конструктором: внутри инструкция (Chart.yaml), список деталей по умолчанию (values.yaml) и сами формы для деталей (templates/). Оговорка: в отличие от конструктора, коробку можно открыть и поменять любую деталь при сборке, не ломая саму коробку.
Для «Заметок» получится такое дерево:
helm/notes/
├── Chart.yaml паспорт чарта: имя, версия чарта, версия приложения
├── values.yaml значения по умолчанию (то, что вписывается в пропуски)
├── values-dev.yaml значения для окружения dev, переопределяют часть умолчаний
└── templates/ шаблоны манифестов
├── _helpers.tpl общие «кусочки» для повторного использования (не манифест)
├── configmap.yaml
├── deployment.yaml
├── service.yaml
└── httproute.yaml
Каждый файл:
Chart.yamlобязателен. В нёмapiVersion: v2(формат чарта, актуальный для Helm 3 и 4),name(имя чарта),version(версия самого чарта) иappVersion(версия приложения, о разнице ниже).values.yamlтоже обязателен по смыслу: здесь лежат все настройки с разумными умолчаниями.templates/содержит файлы манифестов с пропусками. Всё, что лежит в этом каталоге и не начинается с_, Helm считает манифестом и отправляет в кластер.- Файл, имя которого начинается с подчёркивания (
_helpers.tpl), манифестом не является. В нём хранятся именованные «кусочки», их подключают в других шаблонах (подробнее в разделе проinclude). values-dev.yamlне читается автоматически. Его нужно явно передать флагом-f.
Как из этого получается манифест, ты увидишь в следующих разделах.
Так выглядит Chart.yaml нашего чарта, поле за полем:
apiVersion: v2 # формат чарта (v2 у Helm 3 и 4)
name: notes # имя чарта; по нему релиз называют по умолчанию
description: Приложение Заметки (без БД, Gateway и секретов) # для людей
type: application # application ставится в кластер, library только даёт шаблоны другим чартам
version: 0.1.0 # версия шаблонов; растёт, когда меняешь чарт
appVersion: "0.4.1" # версия приложения; у нас это тег образа notes:0.4.1
appVersion взят в кавычки специально. Без них YAML прочитает 0.4.1 как строку сам, но 1.10 превратился бы в число 1.1, и версия исказилась бы. Кавычки для версий стоит писать всегда.
Осторожно, путаница: каталог templates/ не «шаблоны Jinja» и не обычные манифесты: это файлы на языке Go-шаблонов (разбираем ниже), поэтому их нельзя применять через kubectl apply -f напрямую. Если применить, kubectl увидит {{ и вернёт ошибку разбора YAML.
Прикинь сам: ты положил в
templates/файлnotes.tplс Deployment. Попадёт ли он в кластер? А если назвать его_notes.yaml?
notes.tpl попадёт: манифестом считается любой файл без подчёркивания в начале имени, а расширение не важно. _notes.yaml манифестом не станет, это хранилище «кусочков».
Проверь понимание: ты положил в
templates/файлnotes.tplс описанием Deployment. Попадёт ли он в кластер? А если назвать его_notes.yaml?
Ответ
Да, notes.tpl попадёт в кластер как манифест: Helm считает манифестом любой файл из templates/, имя которого не начинается с подчёркивания, а расширение не важно. Файл _notes.yaml с подчёркиванием манифестом не станет: Helm рассматривает его как хранилище именованных «кусочков» и в кластер не отправит.
Главное: в чарте обязательны
Chart.yamlиvalues.yaml, а в кластер уходит всё изtemplates/, что не начинается с_.
Теперь посмотрим, как значения из values.yaml складываются в итог.
Values: значения и их приоритет
Чтобы одни и те же шаблоны давали разные результаты, нужен слой значений, отдельный от шаблонов. Тогда отличие dev от prod это два маленьких файла, а не две копии всего приложения.
Настройки в телефоне: заводские (по умолчанию) можно переопределить своими, а временно ещё и разовым переключателем «на этот раз». Оговорка: чем «ближе» к моменту запуска команды, тем сильнее настройка, поэтому разовый переключатель побеждает всё.
Helm собирает итоговый набор значений в три слоя, каждый следующий перекрывает предыдущий:
flowchart TD
A["1. values.yaml чарта<br>самые слабые: умолчания"] -->|"перекрываются"| B["2. файлы из -f (по порядку)<br>-f values-dev.yaml -f values-local.yaml"]
B -->|"перекрываются"| C["3. --set ключ=значение<br>самые сильные: разовые правки в командной строке"]
Вложенные значения складываются по ключам, а не заменяются целиком. Если в values.yaml есть config.LOG_LEVEL: info и config.PORT: "8080", а в файле values-dev.yaml только config.LOG_LEVEL: debug, то PORT останется, а поменяется один LOG_LEVEL. Списки (массивы) наоборот заменяются целиком.
Возьмём значения нашего чарта и посчитаем итог для команды:
helm template notes helm/notes -f helm/notes/values-dev.yaml --set replicaCount=5
| Ключ | values.yaml | values-dev.yaml | –set | Итог |
|---|---|---|---|---|
replicaCount |
3 | 2 | 5 | 5 (побеждает --set) |
config.LOG_LEVEL |
info | debug | нет | debug (побеждает файл) |
config.PORT |
“8080” | нет | нет | “8080” (осталось умолчание) |
gateway.host |
notes.lab | нет | нет | notes.lab |
Из четырёх ключей три взяты из разных слоёв, и это нормально: слои не заменяют друг друга целиком.
Прикинь сам: в
values.yamlreplicaCount: 3, вvalues-dev.yaml2, а в командной строке--set replicaCount=5. Сколько реплик получишь?
Пять: --set сильнее файлов, а файл -f сильнее умолчаний. Без -f файл values-dev.yaml вообще не читается.
Осторожно: не думай, что --set удобен и его можно использовать везде. Разовую правку он делает быстро, но потом никто не помнит, с каким --set ставили релиз. Постоянные отличия записывай в файл значений и клади в git. Ещё одна ловушка: --set разбирает запись сам, поэтому --set config.PORT=8080 даст число, а не строку, что для переменной окружения важно (ниже разберём quote).
Проверь понимание: в
values.yamlстоитreplicaCount: 3, вvalues-dev.yamlreplicaCount: 2. Ты запускаешьhelm template notes helm/notesбез-f. Сколько реплик получишь?
Ответ
Три. Файл values-dev.yaml сам не подключается, его нужно указать флагом -f. Без флага работают только значения по умолчанию из values.yaml.
Главное: слои значений от слабого к сильному:
values.yaml, файлы-fпо порядку,--set; вложенные ключи складываются, списки заменяются целиком.
Значения лежат отдельно, а подставляет их шаблон.
Шаблон: как подставляются значения
Нужен способ сказать в YAML «здесь будет значение, которое возьмём из values». Для этого Helm использует язык шаблонов Go (Go templates): всё, что внутри двойных фигурных скобок {{ ... }}, Helm вычисляет, остальной текст оставляет как есть.
В форме письма: «Уважаемый {{ имя }}». Всё вне скобок печатается без изменений, а внутри скобок вставляется имя. Оговорка: внутри скобок можно не только подставить, но и вычислить: обрезать, добавить значение по умолчанию, выбрать по условию.
Внутри {{ }} доступны три главные «папки» с данными, они начинаются с точки:
.Values значения из values.yaml, -f и --set .Values.replicaCount
.Release данные об установке .Release.Name (имя релиза)
.Release.Namespace
.Release.Service (всегда "Helm")
.Chart данные из Chart.yaml .Chart.Name .Chart.Version .Chart.AppVersion
Точка в начале обозначает «текущий контекст», то есть корневой объект со всеми этими папками. Путь читается слева направо, как путь к файлу: .Values.gateway.host это «в папке Values возьми gateway, в ней host».
Символ | (вертикальная черта, пайп) знаком тебе по bash: передаёт результат слева в функцию справа. Здесь так же: {{ .Values.gateway.host | quote }} берёт значение и пропускает его через функцию quote, которая оборачивает его в кавычки.
Из нашего deployment.yaml:
spec:
replicas: {{ .Values.replicaCount }}
Разбор: Helm видит {{ .Values.replicaCount }}, идёт в значения, находит replicaCount. При -f values-dev.yaml это 2. Получается строка replicas: 2. А вот строка с образом:
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
.Values.image.repositoryдаётghcr.io/CHANGE_ME/notes;.Values.image.tagпуст (""), поэтому функцияdefaultберёт запасное значение.Chart.AppVersion, то есть0.4.1;- итог:
image: "ghcr.io/CHANGE_ME/notes:0.4.1".
Именно это ты увидишь в выводе helm template в задании 2.
Прикинь сам: в
values.yamlimage.tag: "". Какой тег получит образ и откуда он взят?
Тег 0.4.1 из .Chart.AppVersion: пустая строка считается «нет значения», и default берёт запасное.
Осторожно: не думай, что пропуск {{ }} можно поставить где угодно. Подставляется текст, поэтому после подстановки YAML обязан оставаться правильным. Слова вроде yes и on без кавычек некоторые разборщики YAML читают как булево значение, а 8080 без кавычек станет числом. Для переменных окружения и меток строки лучше пропускать через quote.
Проверь понимание: в
values.yamlimage.tag: "". Какой тег получит образ и откуда он взят?
Ответ
Тег 0.4.1. Пустая строка считается «нет значения», поэтому default подставляет .Chart.AppVersion из Chart.yaml. Так тег образа по умолчанию совпадает с версией приложения, и latest не используется.
Главное: внутри
{{ }}доступны.Values,.Releaseи.Chart; пайп|передаёт результат в функцию вродеdefaultилиquote.
Одной подстановки мало, шаблоны умеют условия, циклы и функции.
Конструкции шаблонов: условия, циклы, функции
Одной подстановки мало. Иногда объект нужен не всегда (HTTPRoute только если задан хост), иногда список надо перебрать (все переменные из config), иногда значение нужно обязательно.
Бланк с инструкциями для оператора: «если клиент юрлицо, добавь строку с ИНН», «для каждой позиции заказа напечатай строку», «если сумма не указана, остановись и позови начальника». Оговорка: оператор-Helm ошибается молча, если ты неверно записал инструкцию, поэтому результат всегда смотри через helm template.
Конструкции нашего чарта по одной:
# 1. Подстановка значения
replicas: {{ .Values.replicaCount }}
# 2. Значение по умолчанию: берётся правая часть, если левая пуста
image: {{ .Values.image.tag | default .Chart.AppVersion }}
# 3. Обязательное значение: если пусто, рендер останавливается с сообщением
- {{ required "нужен gateway.host" .Values.gateway.host | quote }}
# 4. Цикл по словарю: для каждой пары ключ-значение печатаем строку
{{- range $k, $v := .Values.config }}
{{ $k }}: {{ $v | quote }}
{{- end }}
# 5. Условие (в нашем чарте не нужно, приводим для примера)
{{- if .Values.gateway.host }}
# ... блок печатается, только если host задан
{{- end }}
# 6. Вставка целой структуры как YAML с нужным отступом
resources:
{{- toYaml .Values.resources | nindent 12 }}
Разбор непонятного:
$kи$vэто переменные цикла. Знак доллара нужен, чтобы отличить их от путей с точкой. Пара$k, $v := .Values.configчитается так: «для каждой пары в.Values.configкладу ключ в$k, значение в$v».{{- ... }}с дефисом слева убирает пробелы и перевод строки перед скобками. Справа так же работает-}}. Без дефиса в результате остаются пустые строки: для YAML они безвредны, но рядом сnindentпутают глаз.toYamlпревращает структуру из values (словарьrequests,limits) обратно в текст YAML.nindent 12это «new line + indent»: сначала добавляет перевод строки, затем сдвигает каждую строку на 12 пробелов. Число подбирают так: столько пробелов, на каком уровне вложенности должен стоять вставляемый блок.
В deployment.yaml блок resources стоит так:
spec: уровень 0
template: 2 пробела
spec: 4
containers: 6
- name: notes 8 (это элемент списка)
resources: 10 (поле контейнера)
requests: 12 <-- сюда должны встать ключи из toYaml
Поле resources: стоит на 10 пробелах. Его содержимое (requests, limits) должно быть глубже на 2, то есть на 12. Поэтому nindent 12. Итог после вставки:
resources:
limits:
cpu: 200m
memory: 128Mi
requests:
cpu: 50m
memory: 64Mi
Обрати внимание: ключи вывелись по алфавиту (limits раньше requests), потому что toYaml сортирует ключи. Порядок в YAML ничего не меняет.
Осторожно, путаница: неверное число в nindent самая частая поломка шаблонов. Что важно: результат может остаться формально верным YAML, но с неправильным смыслом. Мы проверили на этом уроке: при nindent 8 вместо 12 helm template молча отработал, а kubeconform в задании 5 пожаловался на additional properties 'limits', 'requests' not allowed: ключи requests и limits оказались на уровне контейнера. Поэтому в связке нужны обе проверки: helm template и валидатор схемы.
Прикинь сам: в
values.yamlнетgateway.host. Что сделаетrequired "нужен gateway.host" .Values.gateway.hostи чем это лучшеdefault?
required остановит рендер с сообщением ещё до обращения к кластеру. default молча подставил бы выдуманное значение, и приложение уехало бы с чужим хостом.
Проверь понимание: в
values.yamlнет ключаgateway.host. Что произойдёт с шаблоном{{ required "нужен gateway.host" .Values.gateway.host }}и чем это лучше, чемdefault "notes.lab"?
Ответ
required остановит рендер с сообщением нужен gateway.host ещё до обращения к кластеру. default молча подставил бы выдуманное значение, и приложение уехало бы в прод с чужим хостом. Для значений без разумного умолчания нужен required.
Главное:
defaultзадаёт запасное значение,requiredостанавливает рендер,rangeперебирает,toYamlсnindentвставляет блок на нужной глубине.
Повторяющиеся куски удобно записать один раз, и для этого есть хелперы.
Хелперы: define и include
Набор меток (app.kubernetes.io/name, instance, version) нужен в каждом объекте. Если копировать его в пять файлов, получим ту же проблему копий, но уже внутри чарта. Поэтому повторяющийся кусок записывают один раз и вызывают по имени.
Функция в программировании или макрос в текстовом редакторе: записали один раз, подставляем по имени.
В файле _helpers.tpl кусок объявляется через define "имя", а в шаблонах подключается через include "имя" контекст. Второй аргумент (контекст) обычно точка .: это значит «передай корневые данные, чтобы внутри работали .Release и .Chart».
Разобранный пример.
{{- define "notes.labels" -}}
app.kubernetes.io/name: notes
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version }}
{{- end }}
Здесь printf "%s-%s" .Chart.Name .Chart.Version склеивает имя и версию в notes-0.1.0. Вызов: {{- include "notes.labels" . | nindent 4 }}. Он вставляет пять строк меток и сдвигает их на 4 пробела. После рендера в метаданных Deployment получится:
labels:
app.kubernetes.io/name: notes
app.kubernetes.io/instance: notes
app.kubernetes.io/version: "0.4.1"
app.kubernetes.io/managed-by: Helm
helm.sh/chart: notes-0.1.0
В чарте два хелпера: notes.labels (полный набор меток для метаданных) и notes.selectorLabels (одна метка app.kubernetes.io/name: notes). Зачем два, мы разберём в задании 2.
Осторожно, путаница: include и template (старая команда). Используй include: только его результат можно передать дальше по пайпу в nindent. Второе: имена хелперов общие для всего чарта и всех его зависимостей, поэтому начинай имя с названия чарта (notes.labels), иначе можно случайно перекрыть чужой хелпер.
Прикинь сам: зачем в
include "notes.labels" .вторым аргументом стоит точка?
Без неё внутри хелпера не было бы .Release и .Chart: шаблон получает только то, что ему передали.
Проверь понимание: зачем в
include "notes.labels" .вторым аргументом стоит точка?
Ответ
Без неё внутри хелпера не было бы доступа к .Release и .Chart: шаблон получает только то, что ему передали. Точка передаёт весь корневой контекст.
Главное:
defineобъявляет кусок в_helpers.tpl,includeвызывает его и передаёт контекст; имя начинай с названия чарта.
Теперь поймём, как Helm хранит историю установок.
Релиз, ревизия и история
kubectl apply применяет файл и забывает. Чтобы вернуться на вчерашнюю версию, пришлось бы найти вчерашний файл в git и применить его. Helm запоминает каждую установку, поэтому «вернись на две версии назад» это одна команда.
Журнал версий документа: каждое сохранение нумеруется, любую можно открыть и сделать текущей. Оговорка: возврат создаёт новую запись в журнале с содержимым старой, а не стирает последующие.
Установленный экземпляр чарта называется релиз (release), у него есть имя (у нас notes) и живёт он в конкретном namespace. Один чарт можно поставить много раз под разными именами: notes-dev и notes-prod это два независимых релиза. Каждая установка или обновление создаёт ревизию (revision) с порядковым номером 1, 2, 3.
Состояние хранится в кластере, в Secret (объект для чувствительных данных, урок 5.6) с типом helm.sh/release.v1. По одному на ревизию, с именами вида sh.helm.release.v1.notes.v1, ...v2. Внутри упакованы: чарт, значения, итоговый манифест и статус.
Жизненный цикл ревизии:
helm upgrade --install (1-й раз) ревизия 1 deployed
helm upgrade (новые значения) ревизия 1 superseded ревизия 2 deployed
helm upgrade (плохой образ) ревизия 2 superseded ревизия 3 deployed (или failed)
helm rollback notes 2 ревизия 3 superseded ревизия 4 deployed "Rollback to 2"
Статусы: deployed (текущая), superseded (заменена более новой), failed (не удалась), pending-install и pending-upgrade (операция идёт или оборвалась).
Ты на ревизии 3 (плохой образ). Команда helm rollback notes 2 берёт манифест из ревизии 2, применяет его и записывает как ревизию 4 с описанием Rollback to 2. Номер 3 остаётся в истории, счётчик не откатывается назад. Такая история честно показывает, что происходило, включая неудачный выпуск.
Осторожно, путаница: откат возвращает только то, что описано в манифестах. Данные в базе, применённые миграции схемы, файлы на дисках он не откатывает. Если ревизия 3 успела изменить структуру таблиц, откат образа приложения не вернёт таблицы.
Прикинь сам: ты на ревизии 3 с плохим образом и сделал
helm rollback notes 2. Какой номер у текущей ревизии?
Четвёртый: откат создаёт новую запись с содержимым ревизии 2. Ревизия 3 остаётся в истории.
Проверь понимание: где Helm хранит историю релизов и что случится, если удалить эти Secret?
Ответ
В Secret типа helm.sh/release.v1 в namespace релиза, по одному на ревизию. Если их удалить, поды и Service продолжат работать, но Helm забудет о релизе: helm list его не покажет, откатиться будет нельзя, а повторный install упрётся в уже существующие объекты.
Главное: релиз это установленный чарт под именем, ревизия это номер версии; история лежит в Secret
helm.sh/release.v1, а откат не возвращает данные в базе.
Остаётся разобрать команды, от безопасных к опасным.
Команды Helm: от безопасных к опасным
Между «написал шаблон» и «изменил кластер» должны быть шаги проверки, каждый следующий рискованнее предыдущего. Порядок ниже это порядок увеличения риска.
Как устроено.
| Команда | Что делает | Кластер нужен | Риск |
|---|---|---|---|
helm lint |
Проверяет чарт статически: синтаксис, обязательные поля | нет | нулевой |
helm template |
Рендерит шаблоны и печатает готовый YAML | нет | нулевой |
helm install |
Ставит новый релиз | да | меняет кластер |
helm upgrade --install |
Ставит, если релиза нет; обновляет, если есть | да | меняет кластер |
helm history |
Показывает ревизии | да | нулевой |
helm get values / manifest |
Показывает значения и манифест установленного релиза | да | нулевой |
helm rollback |
Возвращает состояние прошлой ревизии | да | меняет кластер |
helm uninstall |
Удаляет все объекты релиза | да | опасно |
Форма upgrade --install идемпотентна (повторный запуск даёт тот же результат, а не ошибку «уже есть»), поэтому её пишут в CI-скриптах: одна команда работает и на первой установке, и на сотой.
Полезные флаги установки и обновления:
-n notes(--namespace): в какой namespace ставить и где искать релиз;-f файл: файл значений; можно указать несколько, применяются по порядку;--set ключ=значение: разовое значение;--wait: дождаться готовности ресурсов (для Deployment это значит, что нужные поды стали Ready) и только тогда вернуть управление;--timeout 3m: сколько ждать, по умолчанию 5 минут;--rollback-on-failure: если обновление не удалось, автоматически откатить на прошлую рабочую ревизию. Включает ожидание--wait. В Helm 3 и в старых статьях этот флаг называется--atomic; в Helm 4 старое имя ещё принимается, но печатает предупреждение об устаревании.
Это самая полезная команда отладки. Ты видишь, что уедет в кластер, ничего не меняя. Вывод начинается с комментария # Source: notes/templates/configmap.yaml, он подсказывает, из какого шаблона взят блок. Блоки разделены строкой ---. Если YAML сломан, добавь флаг --debug: он напечатает результат рендера даже неправильный, и по номеру строки в ошибке ты найдёшь место.
Осторожно, путаница: helm lint не запускает шаблоны на реальных значениях и не сверяет их со схемой Kubernetes. Он пропустит неверную apiVersion или лишнее поле. Поэтому схему проверяет отдельная утилита kubeconform, которую ты будешь запускать над выводом helm template.
Прикинь сам: зачем в CI пишут
helm upgrade --install, а не отдельныеinstallиupgrade?
Одна команда работает и когда релиза нет, и когда он уже есть. Обычный install при существующем релизе падает с cannot re-use a name that is still in use.
Проверь понимание: зачем в CI пишут
helm upgrade --install, а не отдельныеinstallиupgrade?
Ответ
Так одна команда работает и когда релиза ещё нет, и когда он уже есть. Иначе скрипту пришлось бы сначала проверять наличие релиза. Обычный helm install при существующем релизе падает с cannot re-use a name that is still in use.
Главное: порядок рисков такой:
lintиtemplateничего не меняют,install,upgradeиrollbackменяют кластер,uninstallопасен.
Иногда перед обновлением нужно выполнить задачу, для этого есть хуки.
Хуки
Иногда до или после обновления нужно выполнить задачу: например, применить миграцию схемы базы до запуска новых подов.
Хук (hook) это обычный манифест (чаще всего Job из урока 5.8) с аннотацией helm.sh/hook. В аннотации пишут момент запуска: pre-install, pre-upgrade, post-install, post-upgrade и другие. Helm запускает такой объект в нужный момент и ждёт его завершения. Упавший pre-upgrade хук останавливает обновление, и новые поды не появляются.
Осторожно, путаница: объекты-хуки не входят в состав релиза как обычные: helm uninstall их не удаляет; чтобы убирать их после выполнения, задают helm.sh/hook-delete-policy (hook-succeeded/hook-failed) или TTL для Job. В нашем чарте хуков нет, мы упоминаем их, чтобы ты узнал их в чужих чартах.
Прикинь сам: миграция базы запущена как хук
pre-upgrade, и Job упал. Появятся ли новые поды приложения?
Нет: упавший pre-upgrade останавливает обновление, и Helm не станет менять Deployment.
Главное: хук это обычный манифест (чаще Job) с аннотацией
helm.sh/hook;uninstallего не удаляет безhook-delete-policy.
Чарт и приложение развиваются с разной скоростью, и у них разные версии.
Версия чарта и версия приложения
Чарт и приложение развиваются с разной скоростью. Поправил пробу в шаблоне: изменился чарт, приложение то же. Вышел новый образ: изменилось приложение, шаблоны те же.
В Chart.yaml два номера. version это версия самого чарта (шаблонов), она растёт, когда меняешь шаблоны. appVersion это версия упакованного приложения, у нас это тег образа 0.4.1. Их путают, а зря: можно выпустить чарт 0.1.1 с тем же приложением 0.4.1, и наоборот.
Чарты нужно где-то хранить, чтобы ими делились. Реестр (registry) это склад с адресом: ты кладёшь туда упакованный чарт и скачиваешь его другой командой или на другой машине. Без склада чарты пришлось бы пересылать файлами. Helm 4 умеет тянуть чарты из OCI-реестров (адреса вида oci://ghcr.io/..., тех же, где хранятся Docker-образы), это современная замена старым репозиториям с файлом index.yaml. Ещё две вещи отличают v4 от v3: применение через server-side apply (сервер сам разбирается с конфликтами полей) и плагины на WebAssembly. Для этого урока разница не важна, команды те же, кроме имени флага --rollback-on-failure.
Прикинь сам: ты поправил пробу в шаблоне, а образ не менялся. Какое поле в
Chart.yamlповышаешь?
version, потому что изменились шаблоны чарта. appVersion остаётся прежним, приложение то же.
Осторожно: не путай version и appVersion: первая растёт при правке шаблонов, вторая при выходе новой версии приложения.
Главное:
versionэто версия шаблонов чарта,appVersionэто версия приложения; Helm 4 умеет брать чарты из OCI-реестров.
Перед установкой стоит посмотреть, что именно уедет в кластер.
Рендер и проверка до установки
Ошибка в шаблоне опасна тем, что видна только после отправки в кластер: отступ съехал, значение не подставилось, и Deployment не создаётся. Хочется увидеть готовый YAML заранее и ничего не сломать.
Предпросмотр печати. Перед тем как выпустить тираж на принтере, смотришь на экране, как страница выглядит. Оговорка: предпросмотр не знает, есть ли в принтере бумага. helm template не знает, примет ли кластер этот YAML, он проверяет только шаблон.
Две команды, которые ничего не меняют в кластере. helm lint проверяет чарт: синтаксис, обязательные файлы, явные ошибки. helm template делает рендер и печатает итоговый YAML в терминал. В вывод попадают только объекты из templates/, подставленные значения видны прямо в строках.
Ты поменял replicaCount на 3 в values.yaml. Запускаешь helm template и ищешь replicas: в Deployment. Видишь replicas: 3: значение подставилось. Видишь replicas: 2: значение берётся откуда-то ещё, например, из --set или файла -f с более высоким приоритетом (порядок разбирали выше).
Осторожно, путаница: успешный helm lint не значит, что релиз встанет: он не ходит в кластер. Настоящую проверку в кластере даёт установка. Поэтому порядок такой: lint, template, потом upgrade --install.
Прикинь сам:
helm templateнапечатал правильный YAML, а установка упала с ошибкой API-сервера. Где искать причину?
Рендер прошёл, значит шаблон в порядке. Ошибку выдал кластер: неверное поле, нет типа ресурса или объект уже принадлежит другому владельцу. Читай текст ошибки.
Проверь понимание:
helm templateнапечатал правильный YAML, а установка упала с ошибкой от API-сервера. Где искать причину?
Ответ
Рендер прошёл, значит шаблон и значения в порядке. Ошибку выдал кластер: объект не принят (неверное поле, нет нужного типа ресурса, объект уже принадлежит другому владельцу). Читай текст ошибки от API-сервера, а не правь шаблон наугад.
Главное: порядок такой:
lint,template, потомupgrade --install;lintиtemplateне ходят в кластер, поэтому схему проверяют отдельноkubeconform.
Теперь посмотрим, что делает helm upgrade внутри.
Что происходит внутри helm upgrade
Без понимания порядка шагов непонятно, почему Helm иногда «молчит» и ничего не меняет, а иногда ругается на чужие объекты. Порядок короткий, его полезно держать в голове.
Ремонтная бригада с прорабом. Прораб берёт новый чертёж, сверяет его со старым чертежом и с тем, что реально стоит на объекте, и посылает рабочим только разницу. Оговорка: рабочие (Kubernetes) сами решают, как выполнить задание, прораб (Helm) им не диктует.
Команда проходит шаги по порядку:
- Helm читает чарт и значения (
values.yaml,-f,--set) и делает рендер в итоговый YAML. - Достаёт из кластера манифест текущей ревизии (из Secret
sh.helm.release...) и сравнивает с новым. - Отправляет в API-сервер только те объекты, которые изменились или появились.
- Записывает новую ревизию в новый Secret, а предыдущей ставит статус
superseded. - Если указан
--wait(или--rollback-on-failure), ждёт, пока поды станут готовы, и только потом возвращает управление.
Ты поменял в values.yaml тег образа с 0.4.1 на 0.4.2 и запустил helm upgrade. Рендер даст Deployment с новым образом, а Service, ConfigMap и HTTPRoute не изменились. Helm отправит в кластер только Deployment, Kubernetes начнёт rolling update, а в истории появится ревизия 2. Service при этом никто не трогал.
Осторожно, путаница: думают, что helm upgrade без правок перезапустит поды. Нет: манифест не изменился, отправлять нечего, новая ревизия появится, а поды останутся как были. Чтобы поды перезапустились при смене ConfigMap, в шаблон кладут контрольную сумму файла как аннотацию пода, тогда манифест Deployment меняется сам (этот приём разбирается в практике).
Прикинь сам: ты запустил
helm upgradeс теми же значениями, что в текущей ревизии. Что произойдёт с подами?
Ничего: манифест не изменился, Kubernetes поды не перезапустит. В истории появится новая ревизия с той же конфигурацией.
Проверь понимание: ты запустил
helm upgradeс теми же значениями, что в текущей ревизии. Что произойдёт с подами?
Ответ
Ничего: итоговый манифест тот же, Kubernetes не видит изменений и поды не перезапускает. В истории появится новая ревизия с той же конфигурацией.
Главное: Helm сравнивает новый манифест с манифестом текущей ревизии и отправляет только разницу; ревизия растёт, даже если разницы нет.
Остаётся решить, что вообще класть в чарт.
Чего в чарт не кладём
Соблазн упаковать в чарт всё подряд. Но у разных объектов разный срок жизни, и их нельзя удалять и обновлять вместе.
Чарт содержит только приложение: Deployment, Service, ConfigMap, HTTPRoute. Остальное остаётся платформой и лежит в k8s/base/:
- Namespace (
00-namespace.yaml): он существует до релиза и после него; - Gateway (
30-envoyproxy.yaml,31-gateway.yaml): общий для нескольких сервисов; - PostgreSQL (
40-postgres.yaml): база живёт годами, приложение выпускается ежедневно; - CronJob бэкапа (
60-pg-backup-cronjob.yaml).
Разбери это на вопросе: «что случится при helm uninstall notes?» Если в чарте лежит Postgres, он удалится вместе с данными. Поэтому объекты с чужим и более долгим жизненным циклом в релиз не входят.
Секреты вне чарта. Пароль, записанный в values.yaml, попадёт в git, в вывод helm get values и в Secret релиза. Чарт вместо этого ссылается на готовый Secret по имени (existingSecret: notes-db), а сам Secret создаётся отдельно командой (как в уроке 5.6; правильный способ хранить секреты разберём в уроке 9.2).
Прикинь сам: почему
helm uninstall notesне удалит PostgreSQL?
Helm удаляет только объекты, записанные в манифест релиза. Postgres ставится отдельно из k8s/base/ и в релиз не входит.
Осторожно: не думай, что helm install можно применить поверх объектов, уже созданных через kubectl apply. Helm откажет: invalid ownership metadata. Он не берёт под контроль чужие объекты без своих меток владельца (app.kubernetes.io/managed-by: Helm, meta.helm.sh/release-name). Есть два пути: удалить старые и поставить релиз (наш выбор, кластер учебный) или дописать метки и аннотации вручную («усыновить»), если простой недопустим.
Проверь понимание: почему
helm uninstall notesне удалит PostgreSQL?
Ответ
Helm удаляет только объекты, которые сам создал и записал в манифест релиза. Postgres лежит в k8s/base/40-postgres.yaml и ставится через kubectl apply, в релиз он не входит.
Главное: в чарт кладут только приложение; Namespace, Gateway, базу и секреты держат вне релиза, а пароли в
values.yamlне пишут.
Теперь пора собрать чарт «Заметок» руками.
Практика
Кластер kind-notes из урока 5.1 запущен, в namespace notes работают Postgres, Envoy Gateway, Secret notes-db и Secret notes-tls, приложение развёрнуто манифестами 10-deployment.yaml, 20-service.yaml, 32-httproute.yaml, 50-configmap.yaml из k8s/base/. Все команды выполняются в ~/notes.
Задание 1. Установка Helm и первый чарт
Цель: поставить Helm с проверкой контрольной суммы и увидеть, что генерирует helm create.
Предскажи: что покажет sha256sum -c, если архив скачался обрезанным?
Ответ
FAILED и код возврата 1: сумма не совпадёт. Ставить такой файл нельзя.
Разбор команд перед запуском:
curl -fsSLO URLскачивает файл (флаги:-fпадать при ошибке HTTP,-sтихо,-Sно показывать ошибки,-Lидти по перенаправлениям,-Oсохранить под именем из URL). Так же ты скачивал файлы в уроке 1.6.sha256sum -c файлчитает из файла строку «контрольная сумма и имя», считает сумму у настоящего файла и сравнивает. Контрольная сумма (checksum) это «отпечаток» файла: если изменился хоть один байт, отпечаток другой. Так мы убеждаемся, что архив скачан целиком и не подменён.tar -xzfраспаковывает архив (xизвлечь,zон сжат gzip,fфайл).sudo install linux-amd64/helm /usr/local/bin/helmкопирует программу в каталог, где система ищет команды, и ставит право на запуск.
Шаги:
-
Скачай релиз Helm v4.3.0 и проверь SHA256 (для ARM замени
amd64наarm64):cd /tmp curl -fsSLO https://get.helm.sh/helm-v4.3.0-linux-amd64.tar.gz curl -fsSLO https://get.helm.sh/helm-v4.3.0-linux-amd64.tar.gz.sha256sum sha256sum -c helm-v4.3.0-linux-amd64.tar.gz.sha256sum tar -xzf helm-v4.3.0-linux-amd64.tar.gz sudo install linux-amd64/helm /usr/local/bin/helm helm version --short -
Посмотри, что генерирует
helm create. Команда создаёт заготовку чарта (helm create имя),find ... | sortпечатает все файлы каталога по алфавиту:helm create /tmp/demo find /tmp/demo -type f | sort rm -rf /tmp/demo
Что должно получиться:
helm-v4.3.0-linux-amd64.tar.gz: OK
v4.3.0
/tmp/demo/.helmignore
/tmp/demo/Chart.yaml
/tmp/demo/templates/NOTES.txt
/tmp/demo/templates/_helpers.tpl
/tmp/demo/templates/deployment.yaml
/tmp/demo/templates/hpa.yaml
/tmp/demo/templates/httproute.yaml
/tmp/demo/templates/ingress.yaml
/tmp/demo/templates/service.yaml
/tmp/demo/templates/serviceaccount.yaml
/tmp/demo/templates/tests/test-connection.yaml
/tmp/demo/values.yaml
Как читать вывод: OK после имени архива значит, что отпечаток совпал. Строка v4.3.0 это версия Helm; после неё в helm version без --short бывает ещё хэш сборки. Если ты на ARM (Mac на Apple Silicon с Linux в ВМ), качай arm64.
Объясни себе:
- Зачем сверять контрольную сумму, а не запускать
curl | bash? helm createдаёт десяток файлов (hpa.yaml,ingress.yaml,serviceaccount.yaml), которые «Заметкам» не нужны. Мы пишем чарт руками, чтобы понимать каждую строку. Чемhelm createполезен новичку, а чем вреден (подсказка: десяток лишних шаблонов)?
Типичные ошибки:
sha256sum: WARNING: 1 computed checksum did NOT match: файл скачан не полностью или подменён. Скачай заново, не устанавливай.
Задание 2. Чарт «Заметок»: Chart.yaml, values и шаблоны
Цель: описать приложение как чарт и убедиться, что helm template даёт то же, что лежало в k8s/base/.
Предскажи: что произойдёт с релизом, если ты не задашь image.tag, и что хочется от чарта в этом случае?
Ответ
Разумное поведение: взять appVersion из Chart.yaml (0.4.1). Так тег образа по умолчанию совпадает с версией приложения, а latest не появляется нигде.
Шаги:
-
Создай
helm/notes/Chart.yaml:apiVersion: v2 name: notes description: Приложение Заметки (без БД, Gateway и секретов) type: application version: 0.1.0 appVersion: "0.4.1" -
Создай
helm/notes/values.yaml. Пользователя GitHub подставь свой вместоCHANGE_ME:replicaCount: 3 image: repository: ghcr.io/CHANGE_ME/notes tag: "" # пусто: берётся appVersion из Chart.yaml pullPolicy: IfNotPresent # Готовый Secret с ключом DATABASE_URL, создан вне чарта existingSecret: notes-db config: STORE: postgres HOST: "0.0.0.0" PORT: "8080" LOG_LEVEL: info APP_VERSION: "0.4.1" resources: requests: cpu: 50m memory: 64Mi limits: cpu: 200m memory: 128Mi gateway: name: notes-gw host: notes.lab -
Создай
helm/notes/values-dev.yaml:replicaCount: 2 config: LOG_LEVEL: debug -
Создай
helm/notes/templates/_helpers.tpl:{{- define "notes.labels" -}} app.kubernetes.io/name: notes app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} app.kubernetes.io/managed-by: {{ .Release.Service }} helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version }} {{- end }} {{- define "notes.selectorLabels" -}} app.kubernetes.io/name: notes {{- end }} -
Создай
helm/notes/templates/configmap.yaml:apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }}-config labels: {{- include "notes.labels" . | nindent 4 }} data: {{- range $k, $v := .Values.config }} {{ $k }}: {{ $v | quote }} {{- end }} -
Создай
helm/notes/templates/deployment.yaml. Селектор оставляем как в уроке 5.3 (app.kubernetes.io/name: notes), иначе Service перестанет находить Pod’ы:apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Release.Name }} labels: {{- include "notes.labels" . | nindent 4 }} spec: replicas: {{ .Values.replicaCount }} strategy: type: RollingUpdate rollingUpdate: maxSurge: 1 maxUnavailable: 0 selector: matchLabels: {{- include "notes.selectorLabels" . | nindent 6 }} template: metadata: labels: {{- include "notes.labels" . | nindent 8 }} annotations: # при смене конфига меняется хеш, и Pod'ы перезапускаются checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} spec: containers: - name: notes image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: 8080 envFrom: - configMapRef: name: {{ .Release.Name }}-config # из Secret только один ключ, как в уроке 5.6 env: - name: DATABASE_URL valueFrom: secretKeyRef: name: {{ required "нужен existingSecret" .Values.existingSecret }} key: DATABASE_URL startupProbe: { httpGet: { path: /healthz, port: http }, periodSeconds: 2, failureThreshold: 30 } livenessProbe: { httpGet: { path: /healthz, port: http }, periodSeconds: 10 } readinessProbe: { httpGet: { path: /readyz, port: http }, periodSeconds: 5 } lifecycle: preStop: { exec: { command: ["sleep", "5"] } } resources: {{- toYaml .Values.resources | nindent 12 }} -
Создай
helm/notes/templates/service.yaml:apiVersion: v1 kind: Service metadata: name: {{ .Release.Name }} labels: {{- include "notes.labels" . | nindent 4 }} spec: type: ClusterIP selector: {{- include "notes.selectorLabels" . | nindent 4 }} ports: - name: http port: 8080 targetPort: http -
Создай
helm/notes/templates/httproute.yaml:apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: {{ .Release.Name }} labels: {{- include "notes.labels" . | nindent 4 }} spec: parentRefs: - name: {{ .Values.gateway.name }} hostnames: - {{ required "нужен gateway.host" .Values.gateway.host | quote }} rules: - backendRefs: - name: {{ .Release.Name }} port: 8080Что здесь важно (всё, кроме двух пунктов, разобрано в теории):
checksum/configэто аннотация пода. Значение это хеш (sha256sum) готового ConfigMap. Изменился конфиг, хеш другой, шаблон пода другой, и Kubernetes сам перезапускает поды. Без неё поды, прочитавшие переменные при старте, остались бы со старым конфигом.$.Template.BasePathдаёт путь к каталогуtemplates(символ$это корневой контекст: внутриrangeиwithточка меняется, а$нет), иinclude (print ...)рендерит соседний шаблон, чтобы посчитать его хеш.- Селектор Deployment (
selectorLabels) содержит толькоname: notes. Метки вlabelsменяются от релиза к релизу (версия, имя релиза), а селектор у существующего Deployment менять нельзя, иначеhelm upgradeупадёт с ошибкой про неизменяемое полеselector.
-
Проверь чарт и отрисуй его. Разбор:
helm lint helm/notesпроверяет чарт статически; вhelm template notes helm/notes -n notes -f ...первоеnotesэто имя релиза, второе путь к чарту,-nnamespace,-fфайл значений;grep -E '^kind|replicas:|image:|LOG_LEVEL'оставляет только строки с этими словами (-Eрасширенные регулярки,^начало строки,|«или»):helm lint helm/notes helm template notes helm/notes -n notes -f helm/notes/values-dev.yaml | grep -E '^kind|replicas:|image:|LOG_LEVEL'
Что должно получиться:
==> Linting helm/notes
[INFO] Chart.yaml: icon is recommended
1 chart(s) linted, 0 chart(s) failed
kind: ConfigMap
LOG_LEVEL: "debug"
kind: Service
kind: Deployment
replicas: 2
image: "ghcr.io/CHANGE_ME/notes:0.4.1"
kind: HTTPRoute
Как читать вывод: первые три строки это итог helm lint: icon is recommended лишь совет (иконка чарта для каталогов), не ошибка; важна последняя строка 0 chart(s) failed. Дальше идут только те строки helm template, которые пропустил grep. Helm выводит объекты в своём порядке (ConfigMap и Service раньше Deployment), не в порядке файлов. LOG_LEVEL: "debug" пришёл из values-dev.yaml, replicas: 2 тоже, а тег 0.4.1 взят из appVersion, потому что image.tag пуст. Значение CHANGE_ME в образе ты заменил своим именем в шаге 2, у тебя будет оно.
Объясни себе:
- Почему селектор Deployment вынесен в отдельный хелпер без
instanceи версии? - Что делает
checksum/configи почему в нёмincludeтого же ConfigMap?
Типичные ошибки:
Error: template: notes/templates/deployment.yaml:8:11: executing "notes/templates/deployment.yaml" at <.Values.replicaCount>: nil pointer evaluating interface {}.replicaCount: пропущен или неверно назван ключ. Сверьvalues.yaml, имена чувствительны к регистру.Error: parse error at (notes/templates/deployment.yaml:5): unexpected "}" in operand: в шаблоне лишняя или потерянная скобка}}.Error: YAML parse error on notes/templates/deployment.yaml: error converting YAML to JSON: yaml: line 12: did not find expected key: сдвиг послеnindentневерный, сравни число пробелов с уровнем вложенности.
Задание 3. Переезд на Helm и обновление
Цель: заменить ручные манифесты релизом, затем обновить значения и посмотреть, как Pod’ы перезапускаются по хешу конфига.
Предскажи: что скажет Helm, если запустить helm install при живых объектах Deployment notes из kubectl apply?
Ответ
Откажется: invalid ownership metadata. Helm не берёт под контроль чужие объекты без меток и аннотаций владения. Есть два пути: усыновить (добавить метки и аннотации руками) или удалить старые и поставить релиз. Мы выбираем второе: это учебный кластер, и короткий простой допустим. На проде так не делают, там усыновляют или ставят релиз под другим именем и переключают трафик.
Разбор команд: kubectl delete -f файл удаляет объекты, описанные в файле; git rm удаляет файл и сразу готовит это удаление для коммита. --timeout 3m ограничивает ожидание, --rollback-on-failure при неудаче сам откатывает релиз. kubectl get pods -w (-w от watch) не завершается, а печатает изменения по мере появления.
Шаги:
-
Удали app-манифесты и убери их из репозитория (Secret, Gateway и Postgres не трогаем):
kubectl -n notes delete -f k8s/base/10-deployment.yaml -f k8s/base/20-service.yaml \ -f k8s/base/32-httproute.yaml -f k8s/base/50-configmap.yaml git rm k8s/base/10-deployment.yaml k8s/base/20-service.yaml \ k8s/base/32-httproute.yaml k8s/base/50-configmap.yaml -
Установи релиз и дождись готовности.
--rollback-on-failureоткатит установку при неудаче (в Helm 3 и старых статьях этот флаг называется--atomic):helm upgrade --install notes helm/notes -n notes -f helm/notes/values-dev.yaml --rollback-on-failure --timeout 3m helm list -n notes kubectl -n notes get deploy,svc,httproute -
Поменяй уровень логов через
--setи снова обнови релиз. Следи за Pod’ами в соседнем терминале (kubectl -n notes get pods -w):helm upgrade notes helm/notes -n notes -f helm/notes/values-dev.yaml --set config.LOG_LEVEL=warning --rollback-on-failure helm history notes -n notes helm get values notes -n notes
Что должно получиться:
NAME NAMESPACE REVISION STATUS CHART APP VERSION
notes notes 1 deployed notes-0.1.0 0.4.1
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 Tue Sep 29 10:15:02 2026 superseded notes-0.1.0 0.4.1 Install complete
2 Tue Sep 29 10:17:40 2026 deployed notes-0.1.0 0.4.1 Upgrade complete
USER-SUPPLIED VALUES:
config:
LOG_LEVEL: warning
replicaCount: 2
Во втором терминале видно, как старые Pod’ы заменяются новыми по одному: maxUnavailable: 0.
Как читать вывод: в helm list колонка REVISION растёт с каждым обновлением, STATUS deployed значит «текущая ревизия применена». В helm history предыдущая ревизия стала superseded («заменена»). Колонка UPDATED у тебя будет с твоим временем (в helm list она тоже есть, мы её здесь опустили для краткости). helm get values показывает только то, что ты передал: replicaCount: 2 из файла и LOG_LEVEL: warning из --set, но не умолчания из values.yaml. Чтобы увидеть всё, добавь --all.
Вывод команд с кластером в этом уроке не запускался (см. «Проверено на версиях»), форматы взяты из документации Helm.
Объясни себе:
- Почему во второй ревизии Pod’ы пересоздались, хотя образ не менялся?
- Чем
--setопасен для воспроизводимости по сравнению с файлом значений?
Типичные ошибки:
Error: INSTALLATION FAILED: Unable to continue with install: Deployment "notes" in namespace "notes" exists and cannot be imported into the current release: invalid ownership metadata: старые объекты не удалены. Удали их (шаг 1) или усынови.Error: UPGRADE FAILED: context deadline exceeded: Pod’ы не стали готовыми за 3 минуты.--rollback-on-failureоткатит сам, причину ищи вkubectl -n notes describe pod(обычно неверный образ или пустой Secret).Error: INSTALLATION FAILED: no matches for kind "HTTPRoute" in version "gateway.networking.k8s.io/v1": не установлены CRD Gateway API (урок 5.4).
Задание 4. Откат и hooks
Цель: намеренно выкатить плохую версию и откатиться.
Предскажи: если выкатить несуществующий тег образа без --rollback-on-failure, что покажет helm list: deployed или failed? Что будет с трафиком?
Ответ
Без --wait Helm считает установку успешной, как только API принял объекты: статус deployed. Новые Pod’ы будут в ImagePullBackOff, но благодаря maxUnavailable: 0 и readiness-пробе старые продолжат обслуживать трафик. Это ещё одна причина писать --rollback-on-failure или --wait в CI: иначе статус релиза лжёт.
Разбор команд: --set image.tag=9.9.9 задаёт тег, которого нет в реестре; curl -sk тихо (-s) обращается к https, не проверяя сертификат (-k, у нас самоподписанный). helm rollback notes 2 -n notes --wait возвращает манифест ревизии 2 и ждёт готовности.
Шаги:
-
Выкати несуществующий тег:
helm upgrade notes helm/notes -n notes -f helm/notes/values-dev.yaml --set image.tag=9.9.9 kubectl -n notes get pods curl -sk https://notes.lab/healthz -
Откатись на прошлую ревизию и посмотри историю:
helm history notes -n notes helm rollback notes 2 -n notes --wait helm history notes -n notes
Что должно получиться:
NAME READY STATUS RESTARTS AGE
notes-6d9c7b8f54-x2k7p 0/1 ImagePullBackOff 0 20s
notes-7f5c9d6b48-abcde 1/1 Running 0 3m
notes-7f5c9d6b48-fghij 1/1 Running 0 3m
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
2 Tue Sep 29 10:17:40 2026 superseded notes-0.1.0 0.4.1 Upgrade complete
3 Tue Sep 29 10:21:05 2026 superseded notes-0.1.0 0.4.1 Upgrade complete
4 Tue Sep 29 10:22:31 2026 deployed notes-0.1.0 0.4.1 Rollback to 2
curl на шаге 1 отвечает 200: старые Pod’ы живы.
Как читать вывод: у нового пода ImagePullBackOff: Kubernetes не смог скачать образ и ждёт с нарастающими паузами. Два старых пода Running: пока новый не станет Ready, старые не удаляются (maxUnavailable: 0). В истории после отката нет ревизии со статусом «плохая» в последней строке: ревизия 3 осталась superseded, а deployed теперь ревизия 4 с описанием Rollback to 2.
Объясни себе:
- Почему откат создал ревизию 4, а не вернул счётчик на 2?
- Зачем
--rollback-on-failureв CI, если есть ручнойrollback?
Типичные ошибки:
Error: no revision for release "notes": указан номер ревизии, которой нет вhelm history.
Задание 5. Шаг проекта: чарт в репозитории
Цель: зафиксировать чарт 0.1.0 в git и проверить, что платформенные манифесты остались в k8s/base/.
Предскажи: какие файлы остались в k8s/base/ и почему helm uninstall notes не удалит Postgres?
Ответ
Остались 00-namespace, 30-envoyproxy, 31-gateway, 40-postgres, 60-pg-backup-cronjob (плюс 70-netpol... появится позже). Postgres не входит в релиз: helm uninstall удаляет только объекты, которые Helm сам создал и записал в манифест релиза.
Шаги:
-
Проверь чарт на схему Kubernetes.
kubeconformсверяет YAML со схемами объектов Kubernetes (-strictругается на лишние поля,-summaryпечатает итог,-ignore-missing-schemasпропускает объекты, схемы которых он не знает); читает он из stdin, поэтому выводhelm templateпередаётся через|:helm template notes helm/notes -n notes | kubeconform -strict -ignore-missing-schemas -summary -
Проверь состав
k8s/base/, зафиксируй в git и убедись, что в чарте нет секретов:ls k8s/base/ git add helm/notes k8s/base && git commit -m "helm: чарт notes 0.1.0, приложение переехало из k8s/base" grep -rn "password\|DATABASE_URL" helm/notes || echo "секретов в чарте нет"
Что должно получиться:
Summary: 4 resources found parsing stdin - Valid: 3, Invalid: 0, Errors: 0, Skipped: 1
00-namespace.yaml
30-envoyproxy.yaml
31-gateway.yaml
40-postgres.yaml
60-pg-backup-cronjob.yaml
секретов в чарте нет
Как читать вывод: Valid: 3 это ConfigMap, Service и Deployment; Skipped: 1 это HTTPRoute, у kubeconform нет схемы для CRD (расширения Kubernetes, поэтому -ignore-missing-schemas). Если бы в шаблоне были лишние поля, появилась бы строка вроде Deployment notes is invalid: ... additional properties 'limits', 'requests' not allowed (этот текст получен на настоящем прогоне с неверным nindent).
Объясни себе:
- Почему
helm lintпрошёл бы и с невернойapiVersion, а kubeconform нет? - Почему Secret
notes-dbвне чарта и как чарт узнаёт его имя?
Типичные ошибки:
Error: validating ...: chart.metadata.version is required: вChart.yamlнетversion, сверь с заданием 2.
Нейросеть может предложить флаг Helm, которого нет в твоей версии. Сверь любой флаг с helm upgrade –help и прогони helm template и kubeconform до установки.
Сломай и почини
Скрипт ломает чарт в ~/notes/helm/notes, кластер не трогает. Скачай его и запусти с номером сценария 1, 2 или 3 (скрипт не читай, разбор ниже, запускай без sudo):
curl -fsSL -o /tmp/break-5.9.sh https://raw.githubusercontent.com/distinguished-sre/learning/main/devops/project/notes/break/5.9/break.sh
bash /tmp/break-5.9.sh 1
Перед первой правкой скрипт сохраняет копии файлов в ~/.cache/break-5.9, а bash /tmp/break-5.9.sh fix возвращает чарт в исходное состояние (безопасно запускать повторно).
Симптом
Ты запускаешь helm template notes helm/notes (или helm install), и Helm возвращает ошибку вместо YAML. Записывай точный текст: он и есть первая подсказка. Сценарии дают три разные ошибки.
Гипотезы
Шаблон обращается к ключу, которого нет в values; YAML после подстановки сломан отступами; обязательное значение пустое; релиз с таким именем уже есть.
Проверки
helm lint helm/notes # ловит часть ошибок статически
helm template notes helm/notes --debug | tail -30 # --debug печатает результат даже при неверном YAML
helm list -A --all # релизы всех namespace, в том числе failed и pending
В ошибке читай три вещи: файл шаблона (notes/templates/deployment.yaml), строку и столбец (27:28), выражение в угловых скобках (<.Values.image.repository>).
Исправление
Разбор всех сценариев
-
Из
values.yamlпропал блокimage. Ошибка:Error: notes/templates/deployment.yaml:27:28 executing "notes/templates/deployment.yaml" at <.Values.image.repository>: nil pointer evaluating interface {}.repositoryШаблон просит
.Values.image.repository, а ключаimageнет: Helm не может взять поле у «ничего» (nil pointer). Верни блокimageвvalues.yaml(или запустиfix). Чтобы шаблон был защищён, значения без разумной пустоты надо описывать черезrequired. -
Вместо
nindent 12стоитindent 12, и перед блокомresourcesнет перевода строки. Ошибка:Error: YAML parse error on notes/templates/deployment.yaml: error converting YAML to JSON: yaml: line 51: did not find expected keyНомер строки относится к готовому YAML, а не к шаблону. Найди место через
helm template --debug: блокrequestsсклеился со строкойresources:. Верни{{- toYaml .Values.resources | nindent 12 }}. -
В
values.yamlпустойgateway.host. Ошибка:Error: execution error at (notes/templates/httproute.yaml:11:9): нужен gateway.hostЭто сообщение из твоего же
required: так и задумано, рендер остановился до обращения к кластеру. Задайgateway.hostвvalues.yamlили через--set gateway.host=notes.lab.
Отдельный частый случай без скрипта: Error: INSTALLATION FAILED: cannot re-use a name that is still in use. Релиз notes уже существует, в том числе в статусе failed. Смотри helm list -A --all, затем либо helm upgrade --install, либо helm uninstall notes -n notes.
Общий приём: helm lint, затем helm template --debug, kubeconform, и только потом install.
ИИ в помощь
Нейросеть хорошо объясняет шаблоны Helm и находит опечатки в них, но не видит твой кластер и может выдумать флаги или поля. Общие правила: ИИ-помощник.
Задача: разобрать шаблон, который отрендерился странно.
Я учу Helm. Вот кусок шаблона и вывод helm template:
<вставь шаблон и вывод без паролей>
Объясни, откуда взялся каждый отступ и каждое значение, и где вероятная ошибка в nindent или в путях values.
Проверь ответ: запусти helm template с правкой и сверь отступы с теорией про nindent, а потом прогони вывод через kubeconform. Типичная ошибка: нейросеть советует число для nindent «на глаз», не посчитав глубину.
Задача: разобраться с приоритетом значений.
Вот values.yaml, values-dev.yaml и команда helm template с флагами -f и --set:
<вставь файлы и команду без паролей>
Посчитай итоговое значение каждого ключа и скажи, какой слой победил.
Проверь ответ: выведи итог командой helm template или helm get values --all и сравни с ответом. Типичная ошибка: нейросеть забывает, что значения в списках заменяются целиком, а не склеиваются.
Задача: объяснить ошибку установки.
helm upgrade --install вернул ошибку: <вставь текст ошибки>.
Объясни простыми словами, что это значит, и дай три проверки от самой дешёвой.
Проверь ответ: проверь каждый предложенный флаг в helm upgrade --help, а ошибку найди в разделе «Сломай и почини». Типичная ошибка: нейросеть советует --force или ручное удаление объектов, не спросив, что в них хранится.
Словарик урока
| Термин | Простыми словами |
|---|---|
| Манифест (manifest) | YAML-файл с описанием объекта Kubernetes |
| Helm | Программа на твоей машине: подставляет значения в шаблоны и ставит результат в кластер с историей |
| Чарт (chart) | Каталог с шаблонами манифестов, значениями по умолчанию и паспортом Chart.yaml |
| Values (значения) | Настройки, которые подставляются в пропуски шаблонов: values.yaml, -f, --set |
| Шаблон (template) | Манифест с пропусками {{ ... }} на языке Go-шаблонов |
| Рендер (render) | Подстановка значений в шаблоны, результат обычный YAML |
| Релиз (release) | Установленный в кластер экземпляр чарта под именем |
| Ревизия (revision) | Номер версии релиза; каждое обновление и откат создают новую |
Хелпер (_helpers.tpl) |
Именованный кусок шаблона, который вызывают через include |
nindent |
Функция: перевод строки и сдвиг каждой строки на N пробелов |
required |
Функция: останавливает рендер с сообщением, если значение пусто |
| Идемпотентность | Повторный запуск даёт тот же результат: upgrade --install |
| Хук (hook) | Объект с аннотацией helm.sh/hook, запускается до или после операции |
appVersion и version |
Версия приложения и версия самого чарта |
| Контрольная сумма (checksum) | «Отпечаток» файла: меняется при любом изменении содержимого |
| OCI-реестр | Хранилище чартов и образов с адресом oci://... |
| Реестр (registry) | Склад с адресом, где лежат упакованные чарты или образы |
helm lint и helm template |
Проверка чарта без кластера и печать готового YAML до установки |
| Оператор (operator) | Программа в кластере, которая сама ухаживает за сложной системой, например базой |
| Откат (rollback) | Возврат к прошлой рабочей версии |
| Тег образа (image tag) | Метка версии образа, например 0.4.1 |
Вопросы с собеседований
Раздел для повторения: ответь вслух, потом открой ответ. Короткие вопросы с пометкой [на скорость] тренируй на время: ответ за 30 секунд.
1. [junior] [часто] Что такое Helm, чарт и релиз и зачем это нужно?
Ответ
Helm - пакетный менеджер для Kubernetes. Чарт - это пакет: шаблоны манифестов плюс values.yaml со значениями по умолчанию. Релиз - конкретная установка чарта в кластер под своим именем, например helm install notes ./chart. Значения я переопределяю через -f values-prod.yaml или --set, а Helm рендерит шаблоны и применяет манифесты. Каждая операция Helm (helm upgrade, helm rollback) делает новую ревизию релиза, поэтому можно посмотреть историю helm history notes и откатиться helm rollback notes 1. Правки руками через kubectl ревизий не создают.
Что хотят услышать: чарт = шаблоны + values, релиз = установка, ревизии и откат, переиспользование одного чарта для разных окружений.
Красный флаг: путают чарт и релиз или говорят, что Helm просто «копирует YAML в кластер» без шаблонов и истории.
2. [junior] [часто] Ты выкатил релиз, приложение начало отдавать 500. Как откатиться?
Ответ
helm history notes показывает ревизии, helm rollback notes <ревизия> --wait возвращает нужную. Откат создаёт новую ревизию с содержимым старой. Потом разбираюсь в причине по логам.
Что хотят услышать: history, rollback, --wait, откат это новая ревизия; понимание, что откат не возвращает данные в БД и миграции.
Красный флаг: «сделаю kubectl edit и поправлю руками», после чего релиз расходится с реальностью.
3. [middle] [часто] Как передать разные настройки для dev и prod?
Ответ
Общий values.yaml с умолчаниями и по файлу на окружение: -f values-prod.yaml. Файлы лежат в git. --set только для разовых вещей, потому что он не воспроизводим. Секреты не в values.
Что хотят услышать: порядок приоритета, файлы в git, отдельные релизы или namespace, причина против --set; упоминание Kustomize как альтернативы (урок 5.10).
Красный флаг: отдельная копия чарта под каждое окружение.
4. [junior] Чем helm lint отличается от helm template?
Ответ
lint статически проверяет чарт и не показывает результат. template рендерит YAML и печатает его, кластер тоже не нужен. Использую оба: lint ловит структурные ошибки, template показывает, что реально уедет.
Что хотят услышать: template --debug, связка с kubeconform в CI, --dry-run=server как проверка на кластере.
Красный флаг: «они одно и то же» или «проверяю только на проде».
5. [junior] [на скорость] Где Helm хранит состояние релиза?
Ответ
В Secret типа helm.sh/release.v1 в namespace релиза, по одному на ревизию. Серверной части в кластере нет.
Что хотят услышать: Secret, по ревизии, нет Tiller, --history-max ограничивает число.
Красный флаг: «в базе Helm» или «в etcd Helm-сервера».
6. [middle] Поменяли ConfigMap, сделали helm upgrade, а Pod’ы работают со старым конфигом. Почему?
Ответ
Переменные из ConfigMap читаются при старте контейнера, шаблон Pod не изменился, поэтому rolling update не запустился. Решение: аннотация checksum/config с хешем ConfigMap, тогда любое изменение конфига меняет шаблон Pod. Разово можно kubectl rollout restart.
Что хотят услышать: sha256sum в аннотации, разница между переменными окружения и смонтированным файлом (файл обновляется сам, но приложение должно его перечитать).
Красный флаг: «Kubernetes сам подхватит».
7. [middle] На helm install ошибка invalid ownership metadata. Что это и что делать?
Ответ
Объект с таким именем уже есть и создан не Helm: у него нет меток и аннотаций app.kubernetes.io/managed-by: Helm и meta.helm.sh/release-name. Helm не трогает чужое. Варианты: удалить объект и установить, либо усыновить, дописав метки и аннотации, если простой недопустим.
Что хотят услышать: аннотации meta.helm.sh/release-name и release-namespace, вывод про миграцию с kubectl apply на Helm без простоя.
Красный флаг: --force без понимания, что он пересоздаёт объекты.
8. [middle] В чарте пароль БД. Что не так и как правильно?
Ответ
Значение попадёт в git, в helm get values и в Secret релиза, доступный всем, кто читает Secret’ы в namespace. Правильно: чарт ссылается на существующий Secret (existingSecret), а сам Secret создаёт другой механизм: External Secrets, SOPS, Sealed Secrets (урок 9.2).
Что хотят услышать: existingSecret, сравнение с внешним хранилищем, причина: values это не секретное хранилище.
Красный флаг: «зашифрую base64».
9. [junior] [на скорость] В чём разница между version и appVersion в Chart.yaml?
Ответ
version версия чарта (шаблонов), appVersion версия приложения. Поменял шаблон: растёт version. Вышел новый образ: меняется appVersion.
Что хотят услышать: пример, где изменилась только одна из них; SemVer.
Красный флаг: «это одно и то же».
10. [middle] Helm или Kustomize: что выберешь для своего сервиса?
Ответ
Для чужих приложений и всего, что ставят пакетом, Helm. Для своих манифестов с небольшими различиями окружений хватает Kustomize без шаблонов. Часто вместе: Helm ставит чужое, Kustomize патчит своё или вывод Helm. Выбор зависит от того, нужны ли условия и циклы, и от версионирования пакета.
Что хотят услышать: оба подхода, критерии (шаблоны против патчей, история релизов), реальный опыт.
Красный флаг: «Helm всегда лучше» или «шаблоны никогда не нужны».
11. [middle] helm install падает с cannot re-use a name that is still in use. Что делаешь?
Ответ
Релиз с таким именем уже есть в этом namespace, часто в статусе failed после неудачной первой установки. Смотрю helm list -A --all, затем helm uninstall или helm upgrade --install. В CI всегда пишу upgrade --install.
Что хотят услышать: --all показывает failed и pending, релиз привязан к namespace.
Красный флаг: удаляет Secret релиза руками.
12. [middle] Что делают флаги –install, –wait и –atomic в helm upgrade?
Ответ
--install ставит релиз, если его ещё нет, поэтому одна команда подходит и для первого раза, и для обновления. --wait ждёт, пока ресурсы станут готовыми, и без него Helm может сообщить об успехе, когда поды ещё не поднялись. --atomic при неудаче автоматически откатывает релиз и включает ожидание. В Helm 4 этот флаг переименовали в --rollback-on-failure, точное имя сверяю по helm upgrade --help. Ещё задаю --timeout, чтобы CI не висел вечно.
Что хотят услышать: upgrade –install идемпотентен, wait ждёт готовности, atomic откатывает, timeout, в CI нужен осмысленный результат.
Красный флаг: Считать, что без –wait команда проверяет готовность подов.
13. [middle] [на скорость] Что такое Helm hooks и когда их применяют?
Ответ
Hook - это ресурс чарта (обычно Job) с аннотацией helm.sh/hook, который Helm запускает в определённый момент жизненного цикла: pre-install, pre-upgrade, post-install, pre-delete и другие. Типичный пример: миграция БД перед обновлением приложения как pre-upgrade Job. Нужно помнить, что ресурсы-хуки Helm не считает частью релиза и сам не удаляет после выполнения: для этого нужна helm.sh/hook-delete-policy (hook-succeeded/hook-failed) или TTL для Job. Без аннотации Helm лишь удаляет прежний ресурс перед повторным запуском хука. Если хук упал, обновление прерывается.
Что хотят услышать: аннотация helm.sh/hook, стадии, пример с миграцией, delete-policy, хук не входит в релиз.
Красный флаг: Запускать миграции отдельным скриптом вручную без привязки к релизу, не зная о хуках.
Проверено на версиях
Проверено запуском на этом Mac (без кластера):
- Helm v4.3.0:
helm lint,helm template(в том числе--debugи сообщения об ошибках сценариев 1, 2, 3),helm create; флаги--rollback-on-failure,--waitсверены поhelm upgrade --help,--atomicв этой версии выдаёт предупреждение об устаревании. - kubeconform v0.8.0:
helm template ... | kubeconform -strict -ignore-missing-schemas -summary. - Скрипт
break.sh:shellcheckбез замечаний, сценарии 1, 2, 3 иfix(по два запуска подряд) прогнаны на копии чарта.
Не прогонялось (кластер не запускался): helm install, upgrade, rollback, history, list, get values, kubectl из практики и их вывод, скачивание и установка Helm. Формат этого вывода взят из документации Helm и прежней редакции урока. Версии kind v0.33.0, kubectl 1.37.1, Gateway API v1.6.2, Envoy Gateway v1.9.2, образ приложения 0.4.1 взяты из прежних уроков темы и не перепроверялись.
Итог урока: ты умеешь
- умею объяснить, чем чарт, значения, релиз и ревизия отличаются друг от друга
- умею установить Helm с проверкой SHA256 и узнать его версию
- умею описать приложение как чарт:
Chart.yaml,values.yaml, шаблоны, хелперы - умею читать
{{ .Values... }},default,required,range,toYaml | nindentи посчитать нужный отступ - умею отличить
lint,template --debug,kubeconformиinstall, и проверить чарт до кластера - умею ставить и обновлять релиз командой
upgrade --install --rollback-on-failure - умею смотреть
history,get valuesи откатываться черезrollback - умею развести значения по окружениям через
-fи объяснить, почему--setхуже - умею объяснить, что входит в чарт, а что остаётся платформой, и почему секрет вне чарта
- умею читать ошибки
nil pointer,YAML parse error,cannot re-use a name,invalid ownership metadata
Дальше: Урок 5.10: Kustomize: окружения без шаблонов
Проверь себя
Короткий тест по уроку: 5 вопросов из банка в 30. Засчитывается только полностью правильный ответ, порог 60%. Каждая новая попытка даёт другие вопросы, пока банк не закончится. Ответы видны после проверки.
Тест работает с включённым JavaScript.