Иерархия values: откуда берутся итоговые значения
Итоговые значения собираются из нескольких источников по строгому приоритету — разбираем, кто кого перебивает.
Финальный объект
.Values— это результат глубокого слияния слоёв. Чем «ближе» к команде установки источник, тем выше его приоритет.
Порядок приоритета (от низшего к высшему)
| Приоритет | Источник |
| 1 (низший) | values.yaml чарта (дефолты) |
| 2 | значения родительского чарта для subchart |
| 3 | файлы -f a.yaml -f b.yaml (в порядке указания; последний перебивает) |
| 4 (высший) | флаги --set / --set-string / --set-file |
Иными словами: --set бьёт -f, последний -f бьёт предыдущий, а любой пользовательский источник бьёт дефолты чарта.
Зачем вообще такой порядок, а не, скажем, «кто первый, тот и прав»? Логика приоритета повторяет то, как мы рассуждаем при деплое. Дефолты в чарте — это разумные значения «по умолчанию», которые автор положил, чтобы чарт хоть как-то заработал из коробки. Файлы окружения уточняют их под конкретный стенд. А --set в командной строке — это самое «свежее» и самое адресное намерение оператора: «прямо сейчас, для этого запуска, поставь вот так». Естественно, что наиболее конкретное и позднее намерение должно побеждать более общее и раннее. Поэтому правило «ближе к команде установки — выше приоритет» не случайность, а отражение того, что более специфичный источник почти всегда важнее.
Где именно проявляется каждый слой
Дефолты чарта живут в values.yaml рядом с шаблонами и едут вместе с чартом — их видит каждый, кто его устанавливает. Значения родителя для subchart — это секция в values.yaml верхнего чарта под ключом-именем зависимости: так родитель «дотягивается» внутрь чужого чарта, не редактируя его. Файлы -f обычно лежат в вашем deploy-репозитории и версионируются вместе с инфраструктурой. А --set чаще всего рождается прямо в CI-пайплайне из переменных окружения — например, из хэша коммита. Понимание, откуда физически приходит каждый слой, помогает не искать «потерянное» значение не в том месте.
Пример слияния
Дефолт чарта:
# values.yaml чарта
replicaCount: 1
image:
repository: nginx
tag: "1.25.0"
resources:
requests:
cpu: 100m
Файл окружения:
# prod.yaml
replicaCount: 5
image:
tag: "1.25.3"
Команда: helm upgrade --install web ./chart -f prod.yaml --set replicaCount=8. Итог:
replicaCount: 8 # из --set (высший приоритет)
image:
repository: nginx # из дефолта (prod.yaml не трогал)
tag: "1.25.3" # из prod.yaml (перебил дефолт)
resources:
requests:
cpu: 100m # из дефолта
Карты сливаются, списки заменяются
Ключевое правило слияния повторим ещё раз, потому что на нём спотыкаются все: карты (maps) сливаются рекурсивно — в примере выше image.repository уцелел, хотя prod.yaml переопределил только image.tag. А списки (arrays) заменяются целиком: если в дефолте ports: [80, 443], а вы задали ports: [8080], итог — [8080], а не [80, 443, 8080].
Почему Helm не объединяет списки автоматически? Потому что у списка нет надёжного «ключа», по которому можно понять, дополняете вы его или заменяете элемент. Возьмём env с переменными окружения: если бы Helm складывал списки, то добавив одну переменную в prod.yaml, вы бы получили её в довесок к дефолтным — и никогда не смогли бы убрать ненужную. Замена целиком даёт предсказуемость: то, что вы написали в файле окружения, — ровно то, что будет в итоге, без сюрпризов из дефолтов. Цена этой предсказуемости — необходимость повторять весь список, даже если меняете один элемент.
Практическое следствие для списков
Раз списки не сливаются, продумывайте структуру значений так, чтобы переопределять приходилось как можно реже именно списки. Частый приём: вместо списка extraEnv: [...], который пришлось бы дублировать целиком, заводят карту env: { LOG_LEVEL: warn, ... }, где каждый ключ переопределяется точечно и сливается рекурсивно. Когда видите в чарте список, который окружения постоянно «перезатирают целиком ради одной строки», — это сигнал, что данные стоило бы смоделировать картой.
Проверяем итог, не угадываем
Никогда не держите слияние в голове — спросите Helm:
# что получится после слияния (без применения)
helm template web ./chart -f prod.yaml --set replicaCount=8 | head
# итоговые values уже установленного релиза
helm get values web -n prod --all
--set и его варианты
--set replicaCount=8 # число/булев авто-определяется
--set-string image.tag=01 # принудительно строка "01"
--set-file config=./app.conf # значение из файла
--set 'ingress.hosts[0].host=a.io' # элемент списка по индексу
--set 'nodeSelector.disk=ssd' # вложенный ключ
Помните про типы: --set port=8080 даст число, что обычно правильно, но --set tag=1.0 может стать 1 — для версий и тегов берите --set-string.
Как слияние реализовано под капотом
Helm строит карту значений снизу вверх: загружает дефолты чарта, затем по очереди накладывает каждый источник функцией глубокого слияния (аналог mergeOverwrite). Для subchart значения родителя кладутся под ключом с именем subchart-а. Результат фиксируется до рендера и одинаков для всех шаблонов одного релиза. Поскольку слияние идёт по ключам словаря, важен не порядок ключей, а порядок источников: правый/поздний источник затирает левый/ранний. Это детерминированно — один и тот же набор флагов всегда даёт один и тот же .Values.
Важная деталь про null: в YAML явное key: null в файле верхнего приоритета не «удаляет» унаследованный ключ так, как многие ожидают — поведение зависит от версии и контекста, и полагаться на «занулил, значит убрал» рискованно. Если нужно гарантированно отключить какую-то секцию, чаще закладывают в чарт явный флаг вроде ingress.enabled: false, а шаблон оборачивают в условие. Это надёжнее, чем рассчитывать, что переопределение значением null аккуратно вырежет ветку из итогового .Values.
Subchart: один важный нюанс приоритета
Когда ваш чарт зависит от subchart, значения для него вы пишете в своём values.yaml под ключом-именем зависимости — и они перебивают дефолты самого subchart. Но если пользователь вашего чарта передаст -f или --set по тому же пути, он окажется выше и вашего родительского значения. То есть для значений subchart выстраивается ровно та же лестница приоритетов, просто с дополнительной ступенькой «родитель» между дефолтами subchart и пользовательскими файлами. Помнить про эту ступеньку важно: «почему моё значение для subchart не применилось» нередко оказывается тем, что родитель уже задал его, а вы переопределили не тот уровень.
Частые ошибки
- Ждать объединения списков. Список из
-fзаменяет дефолтный целиком. - Порядок
-f.-f base.yaml -f prod.yaml≠-f prod.yaml -f base.yaml: последний перебивает. - Тип в
--set. Тег1.0через--setстанет числом; используйте--set-string.
Итог
- Приоритет: дефолты чарта < родитель для subchart <
-f(поздний бьёт ранний) <--set. - Карты сливаются рекурсивно, списки заменяются целиком.
- Проверяйте итог через
helm templateиhelm get values --all, а не на глаз.