Для тех кто впервые
Если в кластере живёт больше 5 Helm-релизов, рано или поздно появляется скрипт.
Сначала это десять строк с helm upgrade --install, потом в нём заводятся sleep 30 между базой и приложением, потом --set image.tag=$CI_COMMIT_SHA, потом ветка if [ "$ENV" = prod ], потом sops -d values.yml | helm ... -f -.
В какой-то момент скрипт становится главным артефактом инфраструктуры, и никто не может сказать, что именно он раскатит на проде до того, как он это сделает.
https://github.com/helmwave/helmwave — попытка заменить этот скрипт одним декларативным манифестом. Он описывает много релизов сразу, строит между ними граф зависимостей, резолвит values из произвольных источников, и применяет всё.
Nelmwave vs helmwave?
Nelmwave это брат близнец helmwave, но с новой схемой и другим движком под капотом.
Helmwave использует Helm. А Nelmwave – Nelm. Подробнее про Nelm https://habr.com/ru/companies/flant/articles/970498/
Идея Nelmwave — проверить новый движок. Несмотря на схожую функциональность, публичный API Nelm отличается от API Helm, поэтому внедрение обоих движков или замена одного на другой может оказаться довольно затратной.
В Nelmwave реализованы концепции и идеи, которые изначально разрабатывались для Helmwave. Однако из-за накопившейся кодовой базы проверить эти идеи непосредственно в Helmwave сейчас довольно сложно.
Нужно ли с helmwave переезжать на nelmwave?
Nelmwave — экспериментальный проект. Его стоит рассматривать как площадку для проверки новых идей и возможный Release Candidate для Helmwave, но на отдельном движке.
Мы хотим собрать ваш опыт использования Nelmwave и понять, какие возможности действительно нужны индустрии. В дальнейшем это поможет нам выпустить Helmwave v1.0.0.
Большая часть экспериментальных возможностей в итоге появится в Helmwave. Поэтому Nelmwave и Helmwave v1.0.0 будут во многом похожи. Основные отличия могут быть связаны со специфичными для Nelm и Helm флагами. Stores, Values и остальная обвязка будут идентичными.
Иными словами, мы стремимся к тому, чтобы конфигурация, запускаемая командой
helmwave -f nelmwave.yml.tpl
переносилась между проектами примерно на 90%.
Переходить на Nelmwave прямо сейчас не обязательно. Если вам нужны стабильность и предсказуемость, продолжайте использовать Helmwave. Nelmwave стоит попробовать тем, кто готов тестировать новый движок и делиться обратной связью.
Что нового в Nelmwave?
Самое вкусное! Некоторые могут слегка показаться вам знакомыми с helmwave.
build отделён от apply
У nelmwave четыре команды: build, up, down, diff. Рендерит шаблоны и ходит в датасорсы только build. Он не требует кластера и не делает ни одного запроса к API Kubernetes. Всё, что он делает — превращает манифест в самодостаточную директорию:
.nelmwave/ planfile.yml разрешённый план: релизы, рёбра графа, артефакты values/<uniqname>/... values-файлы, в порядке слияния stores/<uniqname>/... сопутствующие файлы из stores: charts/<chart>/... сами чарты — с --download-charts
up, down и diff читают этот план и никогда не рендерят заново. Следствие простое: то, что вы посмотрели глазами в review-джобе, — ровно то, что уедет в кластер. Между diff и up манифест не может «передумать», потому что на этом этапе манифеста уже нет, есть только план.
Планфайл детерминирован (ключи map отсортированы), так что он нормально диффится между сборками, и его не стыдно читать на ревью.
~/demo $ ENV=prod API_TAG=1.5.0 nelmwave build --download-charts --output .nelmwave-prod~/demo $ diff .nelmwave/planfile.yml .nelmwave-prod/planfile.yml6c6< env: stg---> env: prod16c16< image.tag: 1.4.2---> image.tag: 1.5.028c28< env: stg---> env: prod44c44< env: stg---> env: prod
Идентичность релиза
Релизы — это map, ключ которой называется uniqname и имеет вид name[@namespace[@kubecontext]]:
releases: api: # текущий контекст, его дефолтный namespace api@app: # namespace app api@app@staging: # namespace app, kube-context staging
Внутри тела релиза нет полей name и namespace. Это сознательный выбор: имя релиза — не атрибут, а идентичность, и дублировать её в двух местах — способ однажды получить два разных релиза с одинаковым именем. Эквивалентные написания ключей нормализуются и схлопываются, коллизии — ошибка сборки. Заодно нормализуются и записи в needs, чтобы ссылка на зависимость не разъезжалась с её объявлением.
Namespace и kube-context необязательны: если их нет, берётся текущий контекст и его дефолтный namespace, причём резолвится это в момент применения, а не сборки.
Зависимости между релизами
Для этого используется директива needs. Она позволяет указать завимость как от имени релиза. Так и от лейблов. При этом можно задать k8s-like expressions
releases: api@app: # Пример со всеми Needs needs: releases: postgres@data: {} # required redis@data: optional: true matchLabels: tier: db matchLabelsExpressions: - { key: tier, operator: In, values: [backend, secrets] } - { key: app, operator: NotIn, values: [worker] }
Явно названная зависимость по умолчанию обязательна: выбрать зависимый релиз без неё — ошибка, ничего не применяется. optional: true понижает это до предупреждения и просто выкидывает ребро.
Labels для работы с релизами
В Helmwave v0.44.4 релизы выбираются с помощью тегов:
helmwave build -t prod
По мере роста конфигурации имена тегов становятся всё длиннее. Из-за самой концепции тегов команда может превратиться, например, в такую:
helmwave build -t prod-bla-bla
Поэтому в Nelmwave используются лейблы.
Лейблы позволяют описывать релизы по нескольким независимым признакам: окружению, проекту, команде, региону и другим параметрам. Благодаря этому не нужно создавать длинные составные теги для каждой комбинации.
nelmwave up -l 'tier=backend' nelmwave diff -l 'env=prod,!track'nelmwave up -l 'env!=prod'nelmwave up -l 'app=api,env in (prod,stg),tier!=db'
Независимые релизы применяются параллельно,
--concurrencyограничивает одновременность. Падение останавливает свою ветку графа: зависимые пропускаются, несвязанные ветки продолжают работать.
Флаг --include-needs расширяет выборку по графу — в направлении, в котором движется команда, и оно разное:
|
Команда |
Подтягивает |
Пример |
|---|---|---|
|
|
то, от чего зависит выборка |
|
|
|
то же, что |
|
|
|
то, что зависит от выборки |
|
Инверсия для down – намеренная. Снос, который тянул бы за собой зависимости, удалил бы больше, чем вы выбрали, и оставил бы выживших сломанными. А подтягивание зависимых убирает ровно то, что иначе осталось бы смотреть в пустоту.
Каждый запуск логирует итоговую выборку до того, как что-то тронуть.
И отдельно кричит про всё, чего селектор не называл:
INFO uninstall selection {"count": 3, "releases": ["api@app", "cache@app", "postgres@data"]}WARN pulled in by --include-needs {"count": 2, "releases": ["api@app", "cache@app"]}
Tracking Resources / Workloads
Тут все просто – за это отвечает Nelm. Тут смотрите документацию Nelm.
Sets
Когда заводить целый values – избыточно.
Ключи — точечные пути, как у helm --set, но значения сохраняют YAML-тип: до nelm они доезжают типизированным JSON, так что 3 остаётся числом, а false — булевым. Все, кто ловил --set replicas=3, превратившееся в строку внутри чарта, оценят.
releases: api@app: values: - values/api.yml.tpl sets: replicaCount: 3 image.tag: [[ getenv "API_TAG" "1.4.2" ]]
Динамический выбор рендера
В src можно писать любую схему датасорса gomplate: env:, http(s)://, s3://, git://, vault://. А вот что с источником сделают, решает расширение, а не схема:
|
Расширение |
Поведение |
|---|---|
|
|
копируется как есть |
|
|
рендерится через gomplate ( |
|
|
расшифровывается |
|
|
расшифровывается, потом рендерится |
.sops-источники расшифровываются в процессе, бинарь sops не нужен. Ключи берутся из окружения ровно так же, как их берёт CLI: SOPS_AGE_KEY_FILE/SOPS_AGE_KEY, GnuPG, облачные KMS. Никакого своего хранилища ключей nelmwave не заводит.
Расшифрованное попадает в
.nelmwave/в открытом виде — это build-артефакт. Это опасно. О чемbuildговорит об этом в WARN, если за прогон что-то расшифровал:
WARN decrypted secrets written in cleartext {"sources": 1, "dir": ".nelmwave", "hint": "treat this directory as sensitive: do not publish it as a build artifact"}
Gomplate V5
Полностью отказались от Sprig. Чтобы не путать людей.
Для рендера Nelmwave’ом используются только [[ ]] . А Helm-овские {{ }} доезжают до артефакта не тронутыми по умолчанию.
Stores Files
Store – позволяет вам указывать любой Datasource (http, vault, aws…) и можете использовать его при рендеринге helm-values.
Также Store умеет ссылаться на другой Store.
Отличия с helmwave v0.43.0?
Store можно было задать только на уровне релиза. Нет Датасорсов. Нельзя ссылаться на другой Store.
В helmwave из-за этого приходится заниматься менеджментом Store самому пользователю, покружаясь в сложности сравнения с сливания объектов с помощью gomplate.
Values Files
Values теперь можно достать также как из любого Datasource.
values: - { src: values/base.yml, name: base.yml } - { src: values/app.yml.tpl, name: app.yml } # видит base.yml
[[ (ds "values/base.yml").image.registry ]][[ include "stores/netpol.yml" ]]
ds даёт распарсенный объект, include — сырое содержимое. Резолвинг строго «назад»: элемент видит только то, что разрешилось до него. Это позволяет держать один источник правды по сайзингу или тегу образа в base.yml и не размазывать его по шаблонам.
Имена артефактам стоит задавать явно: тогда ключ датасорса не поедет при переупорядочивании списка.
Разница с helmwave v0.43.0
Values могли быть только локальными или http. С помощью Датасорсов есть весь спектр. Также Валуес могут ссылаться нативно через датасорс на другой values.
Для обхода этого ограчения у helmwave появились дополнительные функции для рендера – getValues().
HELM DRIVER
Где живёт состояние релиза
releases: api@app: labels: { app: api } chart: { name: ../charts/stub } driverURL: kubernetes://secrets # the default, spelled out
Один URL вместо флага и набора параметров к нему:
|
URL |
Состояние в |
|---|---|
|
|
Secret на ревизию, в неймспейсе релиза. По умолчанию. |
|
|
ConfigMap на ревизию. |
|
|
PostgreSQL. |
PostgreSQL имеет смысл, когда релизы перерастают ~1 МБ на объект (большие CRD это умеют), когда история должна пережить удаление неймспейс.
пароль в URL писать НЕЖЕЛАТЕЛЬНО, потому что
buildкопирует манифест вplanfile.yml, и он окажется открытым текстом на диске и в артефактах CI. Задавайте пароль черезPGPASSWORD.buildпредупредит, если найдёт встроенный пароль в URL.
Managed Namespace
namespace: не «какой», а «как»
Блок namespace — не про то, в каком неймспейсе живёт релиз (это часть ключа), а про то, создавать ли его и какие метаданные на нём держать:
releases: api@production: chart: { name: repo/api } namespace: create: true delete: true labels: pod-security.kubernetes.io/enforce: restricted istio-injection: enabled annotations: owner: platform-team
Метаданные применяются до релиза, а не после. Лейбл вроде
istio-injectionилиpod-security.kubernetes.io/enforceвлияет только на поды, созданные уже после того, как он встал. Собственный API nelm создаёт неймспейс с одним лишь именем, поэтому метаданные пишет самnelmwave— и делает это до того, как передать управление движку. Лейблы и аннотации при этом мержатся: то, чемnelmwaveне управляет, он не трогает, так что он мирно сосуществует с чем угодно ещё, что управляет этим неймспейсом.
delete — не зеркало create. create по умолчанию true, delete — false. Неймспейс не принадлежит релизу: удалив его, вы снесёте всё, что там живёт, включая чужие релизы, секреты и PVC, а не только то, что положил туда nelmwave. Поэтому симметрии здесь нет намеренно, а down пишет предупреждение по каждому релизу, у которого выставлен delete.
namespace: productionстрокой — это ошибка, а не тихий no-op. Имя живёт в ключе релиза, и молча проглотить попытку задать его в другом месте было бы худшим из вариантов.
Defaults
Сделали отдельную библиотеку для жадного конфигуратора – https://github.com/helmwave/confijer (читается как конфижьор)
Release: labels: { team: platform, env: prod }releases: api@app: labels: { team: data } # -> {team: data, env: prod}
Верхнеуровневый блок Release: — это type default конфиг-загрузчика: он применяется к каждому значению Go-типа Release, то есть к каждой записи под releases:
Так работает со всеми go-типами. Release – самый наглядный.
Map’ы сливаются вглубь, собственный ключ релиза выигрывает. Списки заменяются, а не мержатся — это семантика YAML, и она сохранена сознательно. Так что релиз со своим
values:не унаследует общий; общий базовый файл перечисляется явно.
Политики обращения с ресурсами
|
Поле |
По умолчанию |
Что делает |
|---|---|---|
|
|
|
Забрать ресурс, который через |
|
|
|
Отобрать поля, добавленные руками через |
|
|
|
Ставить CRD из директории |
|
|
|
Стратегия удаления: |
|
|
|
Сколько ревизий хранить. |
forceAdoption — инструмент для миграций и переименований: переименование делает релиз новым владельцем существующих ресурсов. Оставлять его включённым навсегда означает, что следующая коллизия имён молча украдёт чужие ресурсы вместо того, чтобы упасть.
removeManualChanges действует и на diff тоже — чтобы превью совпадало с тем, что реально сделает применение.
QuickStart
Как попробовать?
# macOS и Linuxbrew install helmwave/tap/nelmwave# контейнерdocker run --rm -v "$PWD:/workspace" ghcr.io/helmwave/nelmwave:latest build# из исходниковgo install github.com/helmwave/nelmwave/cmd/nelmwave@latest
Начать проще всего с примеров: в репозитории лежит по проекту на каждую фичу — зависимости, источники чартов, неймспейсы, политики ресурсов, хранилище состояния, sops, перекрёстные датасорсы, работа в CI. Все они собираются без кластера, реестра и сети, потому что чарты указывают на локальную заглушку, а make examples собирает их разом — заодно это способ держать примеры честными при изменении схемы.
Не забудьте автодополнение: оно подсказывает не только имена, но и значения — ключи лейблов и их значения после -l берутся из собранного плана, контексты — из kubeconfig.
Код, примеры и подробный референс схемы: github.com/helmwave/nelmwave. Issues и обсуждения приветствуются — особенно про то, какой части схемы не хватает под ваш пайплайн.

ссылка на оригинал статьи https://habr.com/ru/articles/1068644/