devops-курс Все курсы

✻ Урок 5.9 · Тема 5: Kubernetes и Helm

Helm: упаковываем «Заметки» в чарт

⏱ 4 ч

Зачем это нужно

К этому уроку приложение «Заметки» живёт в четырёх отдельных файлах-манифестах: 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 переезжает в чарт, а Secret notes-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 делает четыре вещи:

  1. Шаблонизация: подставляет значения в шаблоны.
  2. Упаковка: собирает шаблоны и значения по умолчанию в один каталог-чарт, который можно положить в реестр и поставить в другом кластере.
  3. Учёт: запоминает, что и с какими значениями установлено (релиз и его ревизии).
  4. Откат: возвращает состояние любой прошлой ревизии.

Хост приложения 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.yaml replicaCount: 3, в values-dev.yaml 2, а в командной строке --set replicaCount=5. Сколько реплик получишь?

Пять: --set сильнее файлов, а файл -f сильнее умолчаний. Без -f файл values-dev.yaml вообще не читается.

Осторожно: не думай, что --set удобен и его можно использовать везде. Разовую правку он делает быстро, но потом никто не помнит, с каким --set ставили релиз. Постоянные отличия записывай в файл значений и клади в git. Ещё одна ловушка: --set разбирает запись сам, поэтому --set config.PORT=8080 даст число, а не строку, что для переменной окружения важно (ниже разберём quote).

Проверь понимание: в values.yaml стоит replicaCount: 3, в values-dev.yaml replicaCount: 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.yaml image.tag: "". Какой тег получит образ и откуда он взят?

Тег 0.4.1 из .Chart.AppVersion: пустая строка считается «нет значения», и default берёт запасное.

Осторожно: не думай, что пропуск {{ }} можно поставить где угодно. Подставляется текст, поэтому после подстановки YAML обязан оставаться правильным. Слова вроде yes и on без кавычек некоторые разборщики YAML читают как булево значение, а 8080 без кавычек станет числом. Для переменных окружения и меток строки лучше пропускать через quote.

Проверь понимание: в values.yaml image.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) им не диктует.

Команда проходит шаги по порядку:

  1. Helm читает чарт и значения (values.yaml, -f, --set) и делает рендер в итоговый YAML.
  2. Достаёт из кластера манифест текущей ревизии (из Secret sh.helm.release...) и сравнивает с новым.
  3. Отправляет в API-сервер только те объекты, которые изменились или появились.
  4. Записывает новую ревизию в новый Secret, а предыдущей ставит статус superseded.
  5. Если указан --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 копирует программу в каталог, где система ищет команды, и ставит право на запуск.

Шаги:

  1. Скачай релиз 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
    
  2. Посмотри, что генерирует 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 не появляется нигде.

Шаги:

  1. Создай helm/notes/Chart.yaml:

    apiVersion: v2
    name: notes
    description: Приложение Заметки (без БД, Gateway и секретов)
    type: application
    version: 0.1.0
    appVersion: "0.4.1"
    
  2. Создай 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
    
  3. Создай helm/notes/values-dev.yaml:

    replicaCount: 2
    config:
      LOG_LEVEL: debug
    
  4. Создай 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 }}
    
  5. Создай 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 }}
    
  6. Создай 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 }}
    
  7. Создай 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
    
  8. Создай 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.
  9. Проверь чарт и отрисуй его. Разбор: helm lint helm/notes проверяет чарт статически; в helm template notes helm/notes -n notes -f ... первое notes это имя релиза, второе путь к чарту, -n namespace, -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) не завершается, а печатает изменения по мере появления.

Шаги:

  1. Удали 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
    
  2. Установи релиз и дождись готовности. --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
    
  3. Поменяй уровень логов через --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 и ждёт готовности.

Шаги:

  1. Выкати несуществующий тег:

    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
    
  2. Откатись на прошлую ревизию и посмотри историю:

    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 сам создал и записал в манифест релиза.

Шаги:

  1. Проверь чарт на схему 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
    
  2. Проверь состав 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>).

Исправление

Разбор всех сценариев
  1. Из 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.

  2. Вместо 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 }}.

  3. В 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.

тема 5 урок 5.9 4 ч курс 0/0 ← → уроки