Несколько окружений: values-dev, values-prod

Главный практический паттерн Helm: один чарт и компактные файлы значений на каждое окружение.

Идея проста: шаблон один, а различия окружений собраны в маленьких файлах values-<env>.yaml. Это прямое решение «боли голых манифестов» из первого урока.

Зачем вообще разделять окружения

Окружения существуют, чтобы изменения проходили проверку до того, как доберутся до пользователей. Dev — место, где ломать не страшно: туда выкатывают каждый коммит, ресурсы экономят, логи делают подробными. Staging максимально похож на прод, чтобы поймать то, что воспроизводится только «как в бою»: реальные домены, настоящие интеграции, близкие к проду лимиты. Prod — то, что видят пользователи: с запасом по ресурсам, автоскейлингом, сдержанным уровнем логов и строгими гарантиями отката. Если описывать каждое из этих окружений отдельным набором YAML-манифестов, вы получаете три почти одинаковые копии, которые неизбежно разъезжаются: правку в одном забыли перенести в другой, и прод начинает вести себя не так, как протестированный staging. Паттерн «один чарт — много values» придуман ровно против этого расхождения.

Структура репозитория

webapp/                 # сам чарт (шаблоны общие для всех окружений)
├── Chart.yaml
├── values.yaml         # дефолты (как правило = dev-подобные)
└── templates/
deploy/
└── values/
    ├── base.yaml       # общее для всех окружений
    ├── dev.yaml        # отличия dev
    ├── staging.yaml    # отличия staging
    └── prod.yaml       # отличия prod

Что кладут в файлы окружений

Только то, что отличается. Образ, секреты и общая логика — не здесь.

# values/dev.yaml — дёшево, удобно дебажить
replicaCount: 1
resources:
  requests: { cpu: 50m, memory: 64Mi }
ingress:
  enabled: true
  host: app.dev.example.com
env:
  LOG_LEVEL: debug
autoscaling:
  enabled: false
# values/prod.yaml — надёжно, с запасом
replicaCount: 5
resources:
  requests: { cpu: 500m, memory: 512Mi }
  limits:   { cpu: "2",  memory: 1Gi }
ingress:
  enabled: true
  host: app.example.com
env:
  LOG_LEVEL: warn
autoscaling:
  enabled: true
  minReplicas: 5
  maxReplicas: 20

Команды деплоя по окружениям

# dev
helm upgrade --install web ./webapp -n dev --create-namespace   -f deploy/values/base.yaml -f deploy/values/dev.yaml   --set image.tag="$CI_SHA"

# prod
helm upgrade --install web ./webapp -n prod --create-namespace   -f deploy/values/base.yaml -f deploy/values/prod.yaml   --set image.tag="$RELEASE_TAG" --atomic --timeout 5m

Везде один и тот же чарт ./webapp. Различие — лишь файл окружения. Образ задаётся динамически из CI.

Обратите внимание на пары флагов, которые отличают прод от dev. --atomic в проде означает: если релиз не стал здоровым в отведённый --timeout, Helm сам откатит его к предыдущей рабочей ревизии — пользователи не увидят полусломанного состояния. В dev этим часто жертвуют ради скорости и наглядности: пусть упавший под висит, чтобы было что отлаживать. --create-namespace уместен на эфемерных стендах, где namespace может ещё не существовать. Так одни и те же два файла значений обслуживают разную операционную политику окружений, и эта политика тоже становится частью воспроизводимой команды деплоя, а не устной договорённости.

Слой base: что общее

В base.yaml выносят то, что одинаково везде, но отличается от дефолтов чарта: имя сервисного аккаунта, общие аннотации, метки команды, политика рестартов. Это убирает дублирование между dev/staging/prod.

Полезно держать в голове критерий, что считать «общим». Значение едет в base, если при изменении его захочется поменять сразу во всех окружениях — например, метку владельца команды или общий префикс имён. Если же значение по своей природе разное на каждом стенде (хост, число реплик, уровень логов), ему место в файле окружения, и тащить его в base не нужно, даже если сейчас оно случайно совпадает в двух средах. Ошибка в обе стороны вредна: слишком толстый base заставляет окружения переопределять его обратно (а это снова дублирование, только наизнанку), а слишком тонкий — размазывает по env-файлам то, что должно жить в одном месте.

А нужен ли base вообще

Для маленького проекта с двумя окружениями слой base может оказаться лишней церемонией: если общего почти нет, проще держать всё в самих env-файлах и в дефолтах чарта. base окупается, когда окружений становится три и больше или когда заметная часть настроек реально одинакова. Не вводите base «на всякий случай» — вводите, когда видите конкретное дублирование, которое он устранит.

Диаграмма наложения

values.yaml (дефолты чарта)
        +  base.yaml      (общее для окружений)
        +  prod.yaml      (специфика прода)
        +  --set image.tag (динамика сборки)
        =  итоговые .Values для релиза в prod

Как это масштабируется под капотом

Поскольку Helm детерминированно сливает источники, добавление нового окружения — это просто новый маленький файл values/qa.yaml и новая строка деплоя; шаблоны не трогаются вовсе. Это и есть выигрыш над голыми манифестами: сложность окружений вынесена в данные, а не в копии YAML. Различия окружений становятся обозримыми — достаточно открыть два файла и сравнить. Когда файлов окружений много и хочется управлять ими как единым набором (несколько релизов, общий шаблон команды), на сцену выходит Helmfile — отдельный инструмент, которому посвящён урок дальше.

Обозримость различий — недооценённое преимущество. Когда вся разница между staging и prod умещается в diff двух коротких файлов, ревью релиза превращается в осмысленное чтение: видно, что прод отличается ровно бо́льшим числом реплик и другим хостом, и ничем больше. Сравните это с попыткой глазами сверить два набора полноразмерных манифестов на сотни строк — там критичное отличие легко проскользнёт незамеченным. Маленькие env-файлы делают саму систему аудируемой.

Один и тот же чарт во всех окружениях — это и есть гарантия

Главная причина не плодить «отдельный чарт для прода» в том, что именно одинаковость шаблонов даёт смысл тестированию на staging. Если staging и prod рендерятся из одного чарта и отличаются только значениями, то «прошло на staging» что-то значит: вы проверили ту же логику шаблонов, что поедет в прод. Как только для прода заводится свой чарт, эта гарантия рушится — вы тестируете один код, а выкатываете другой, и расхождение проявится в самый неподходящий момент. Дисциплина «чарт ровно один» — не эстетика, а условие, при котором конвейер dev → staging → prod вообще имеет доказательную силу.

Частые ошибки

  • Дублировать общее в каждом файле окружения. Общее — в base.yaml, в env-файлах только различия.
  • Хранить секреты в env-файлах под git. Прод-пароли — через защищённые механизмы, не в открытом values.
  • Разные чарты для окружений. Соблазн «для прода свой чарт» возвращает копипасту; чарт должен быть один.

Итог

  • Один чарт + компактные values-<env>.yaml — каноничное решение мультиокружения.
  • В env-файлах только различия; общее — в base.yaml; образ — динамически из CI.
  • Новое окружение = новый файл значений, без правки шаблонов.
Проверьте себя
1. Что следует класть в файл values/prod.yaml?
AПолную копию всех значений
BТолько то, что отличается от дефолтов и base
CШаблоны манифестов
DСам образ Docker
2. Сколько чартов нужно для трёх окружений dev/staging/prod?
AТри разных чарта
BОдин общий чарт, различия — в файлах значений
CПо чарту на namespace
DЗависит от Helm
3. Как добавить новое окружение qa при этом паттерне?
AСкопировать весь чарт
BДобавить файл values/qa.yaml и строку деплоя, не трогая шаблоны
CПереписать templates/
DСоздать новый репозиторий