Всем привет! Меня зовут Андрей Шилов, я инженер по DevOps-практикам. В «Экспресс 42» — подразделении «Фланта», которое консультирует компании по DevOps, — мы часто видим одну проблему: инфраструктурный код со временем превращается в хаос, даже небольшое изменение заставляет перелопачивать гору скриптов. Чтобы навести порядок, мы используем модель BSA (Base, Service, Application). В этой статье мы расскажем, как реализовали GitOps по этой модели с помощью Argo CD — результатом наших экспериментов и стал этот материал.

Если вы когда-нибудь страдали от merge-конфликтов между окружениями, копипаста в репозитории и беспорядка в Argo CD — добро пожаловать. Мы посмотрим, как можно перейти от App of Apps к ApplicationSet и попробуем несколько вариантов реализации последнего. Разберём преимущества и недостатки разных схем в поисках решения, которое:

  • не требует костылей в Argo CD;

  • работает только через Helm;

  • живёт в одном Git-репозитории;

  • легко масштабируется, включая временные окружения.

Ниже — коротко о модели BSA, критериях оценки для эксперимента и самих вариантах реализации.

Что такое модель BSA

Модель BSA (Base, Service, Application) предложил сооснователь и управляющий партнёр компании «Экспресс 42» Александр Титов. Она предлагает делить инфраструктуру на слои, как в Docker-файлах. Важно не смешивать их и не создавать лишних зависимостей между ними. Благодаря этому каждый слой остаётся независимым, а сама система — управляемой.

Нижний слой — базовый, или инфраструктурный (Base). Это то, как настраиваются операционная система, бэкапы и другие низкоуровневые вещи, например то, как развёртывается Kubernetes. В наших примерах роль последнего будет играть Deckhouse Kubernetes Platform.

Второй слой — сервисный (Service). Здесь находится весь SaaS для разработчиков: логирование как сервис, мониторинг как сервис, база данных как сервис, балансировщик как сервис, очередь как сервис, Continuous Delivery как сервис и так далее. Это всё необходимо описывать отдельными модулями в системе управления конфигурацией.

И верхний слой — приложение (Application). Здесь описывается то, как приложение будет развёртываться поверх предыдущих слоев.

Вместо дисклеймера

Прежде чем перейти к сути, давайте сразу очертим рамки, в которых мы будем сравнивать реализации. Это важно, чтобы правильно понимать, о чём пойдёт речь — и о чём не пойдёт.

  • Мы рассматриваем только два слоя модели BSA — Service и Application. Базовый слой (например, настройку Kubernetes) не затрагиваем, так как Argo CD уже находится внутри Kubernetes и управлять через него базовой инфраструктурой нецелесообразно.

  • Все приложения и окружения описываются только через Helm-чарты. Мы сознательно отказались от Kustomize и «голых» манифестов ради единообразия и предсказуемости, однако описываемые ниже подходы могут быть применены и для других способов описания конфигураций.

  • Все окружения живут в одном Git-репозитории. Это сделано для удобства разработчиков и минимизации конфликтов.

  • В качестве инструмента доставки конфигураций используем только Argo CD. Flux, werf и прочие альтернативы — за пределами нашего эксперимента.

  • Мы не модифицируем компоненты Argo CD и не делаем патчей CMP. Это нужно, чтобы решение было переносимым, то есть чтобы его можно было бы легко воспроизвести в другой инфраструктуре.

  • Стратегии деплоя вроде Blue/Green, Canary или Argo Rollouts не рассматриваем, хотя они технически совместимы с нашей схемой.

Начало: подход App of Apps и его ограничения

Основой для эксперимента стала система на базе классической схемы App of Apps в Argo CD. Суть её в том, что создаётся один родительский Application, который управляет всеми остальными — дочерними приложениями.

apiVersion: argoproj.io/v1alpha1
kind: Application 
metadata:
  name: cluster-authorization-rules
  namespace: argocd
spec:
  destination:
    server: 'https://kubernetes.default.svc'
    namespace: e42-service-layer
  project: service-layer 
  source:
    repoURL: 'git@gitlab.com:team/express42/internal-infrastructure/layers/service.git' 
    path: 'deckhouse/services/cluster-authorization-rules/test'
    targetRevision: develop 
    plugin:
      name: argocd-vault-plugin
  syncPolicy:
    automated:
      prune: true 
      selfHeal: true

Для эксперимента мы организовали репозиторий с разбиением по окружениям: в корне — папки production, test, а в каждой из них — дублирующиеся манифесты одних и тех же сервисов. В результате начали всплывать проблемы:

  • Слияние веток стало болью. Поскольку конфигурации дублировались между окружениями, merge из test в production превращался в лотерею с высоким риском конфликта или багов.

  • Временные окружения? Забудьте! Поддержка динамических стендов была слишком громоздкой и требовала ручных правок.

  • DRY? Не слышали. Один и тот же код приходилось копировать в несколько мест и адаптировать. И, как водится, рано или поздно что-то где-то забывали обновить.

  • Изоляция окружений тоже могла принести проблемы. При коммите напрямую в production всегда есть риск человеческой ошибки: что-то улетит не в ту папку и мы получим некорректную модификацию.

Добавление нового приложения в подходе App of Apps выглядит примерно так: 

  1. Создать манифест Application. 

  2. Завести директорию под сервис, внутри неё — подпапки под production, test, возможно dev.

  3. Везде прописать нужные значения. 

Казалось бы, всего-то несколько шагов, но это рутина с высокой вероятностью ошибиться.

Доработка: ApplicationSet вместо App of Apps

Чтобы избавиться от дублирования и боли с временными окружениями, мы перешли на ApplicationSet — контроллер Argo CD, который сам генерирует нужные Application-ресурсы на основе шаблона и входных данных (генераторов).

Вот какие плюсы получились:

  • Больше никакой копипасты. Шаблон один, а Application’ы под каждое окружение или сервис генерируются автоматически.

  • Динамические окружения — на раз-два. Нужно временное окружение? Просто добавьте ветку или директорию — всё подхватится само.

  • Управление стало централизованным. Один ApplicationSet рулит всем, никаких десятков отдельных Application.

  • Гибкость генераторов. Хотите формировать приложения по списку кластеров? Или по структуре Git-репозитория? Или по сочетанию параметров? Пожалуйста: генераторы Git, Cluster, Matrix всё это позволяют.

Переход на ApplicationSet сокращает количество ручной работы, снижает риск ошибок и открывает путь к более масштабируемому GitOps-процессу.

Почему мы выбрали Helm для описания приложений

Мы выбрали Helm как единый способ описания приложений — и вот почему:

  • Один стиль для всех. Helm помогает привести сервисы к общему формату — шаблоны, values.yaml, единая структура.

  • Гибкость через шаблоны. Конфиги меняются без переписывания манифестов — достаточно передать нужные значения.

  • Отличный dev-опыт. Helm поддерживает локальное тестирование, helm lint, проверки best practices и всё, что нужно для комфортной работы.

  • Сообщество и поддержка. Если что-то пошло не так — ответ почти наверняка уже есть на GitHub или в Stack Overflow.

Единственное «но» — старые YAML-манифесты придётся переписать под Helm. Но это разовая плата за порядок и удобство.

Критерии оценки реализации ApplicationSet

Поскольку ApplicationSet можно реализовать по-разному, нам нужны были критерии для оценки вариантов в ходе эксперимента. Их получилось семь.

Первый — изоляция окружений. Каждое окружение должно быть представлено отдельной веткой или директорией, потому что так проще управлять изменениями и контролировать их. 

Дрейф конфигураций возникает из-за конфликтов слияния и может приводить к расхождениям конфигураций в разных окружениях. Использование веток Git снижает риск дрейфа, позволяя синхронизировать изменения между окружениями с помощью механизмов слияния.

Третий критерий — поддержка временных или динамических окружений. Хотелось бы иметь возможность автоматически создавать окружения (например, по имени ветки или PR) без дополнительной настройки в Argo CD.

Не менее полезно и минимальное взаимодействие с Argo CD для добавления новых приложений. Хотелось бы, чтобы новые приложения или окружения могли добавляться исключительно через Git, без необходимости вручную создавать Application или ApplicationSet.

Пятый критерий — соответствие принципу DRY. Вся повторяющаяся логика должна быть вынесена в шаблоны, чтобы уменьшить дублирование конфигураций и упростить сопровождение.

Важна и самодостаточность ApplicationSet без внешней шаблонизации. Здорово, если вся генерация манифестов и логика параметризации выполняются средствами Argo CD, а сторонняя шаблонизация не нужна.

И последнее — устойчивость к масштабированию. Некоторые реализации ApplicationSet при 100+ приложениях могут резко «просесть» по производительности. Поэтому полезно оценивать подходы с учётом реального количества приложений.

Критерии обсудили, самое время сравнить возможные варианты реализации. 

Варианты реализации ApplicationSet

У нас получились четыре варианта реализации. Рассмотрим их.

1. «В лоб»: по ApplicationSet на каждую ветку

Каждая ветка (например, dev и prod) содержит свой собственный ApplicationSet, который через Git-генератор итерирует по директориям с Helm-чартами и создаёт приложения в Argo CD.

Слева на скриншоте — ApplicationSet, справа — структура репозитория

Плюсы

Минусы

- Полная изоляция окружений

- Самодостаточный ApplicationSet без внешней шаблонизации

- Устойчивость к масштабированию

- Ветки мёржить практически невозможно: конфигурации расходятся

- Нет поддержки временных окружений

- DRY нарушен: шаблон приходится дублировать в каждой ветке

- Добавление нового приложения — всё ещё ручная работа

2. Несколько ApplicationSet в одной ветке

В одной ветке создаются папки под окружения (dev, prod), в каждой из которых лежит свой ApplicationSet, с жёстко заданными путями к values.yaml.

Плюсы

Минусы

- Полная изоляция окружений

- Самодостаточный ApplicationSet без внешней шаблонизации

- Можно мёржить изменения между dev- и prod-окружениями , то есть нет дрейфа конфигураций

- Устойчивость к масштабированию

- Нет поддержки временных окружений

- Каждый ApplicationSet всё ещё создаётся вручную

- DRY-подход по-прежнему не соблюдён

3. Рендеринг ApplicationSet во внешнем CI

Здесь используется шаблон ApplicationSet с плейсхолдерами (например, {{ ENV }}), который в CI (например, GitLab) превращается в готовый YAML. Его потом kubectl apply накатывает в кластер.

Плюсы

Минусы

- Изоляция окружений

- DRY соблюдён

- Устранение дрейфа конфигураций

- Устойчивость к масштабированию

- Минимальное взаимодействие с Argo CD для добавления новых приложений

- Поддержка динамических окружений

- Нет самодостаточного ApplicationSet без внешней шаблонизации

- Шаблонизируем шаблон — архитектура становится сложнее

4. Матричный генератор (Matrix generator)

Матричный генератор — это механизм, который перебирает все возможные сочетания значений из двух (или более) генераторов. Для каждого элемента из первого итерируются все элементы второго. В результате получается декартово произведение множеств.

В нашем варианте реализации используются два генератора: один проходит по приложениям (чартам), другой — по окружениям (значениям). Получается декартово произведение: на каждый чарт — по конфигурации для каждого окружения.

Плюсы

Минусы

- Соответствие принципу DRY

- Самодостаточность ApplicationSet без внешней шаблонизации

- Устранение дрейфа конфигураций

- Гибкость и поддержка динамических окружений

- Минимальное взаимодействие с Argo CD для добавления новых приложений

- Риск для продакшена из-за недостаточной изоляции окружений. Без защиты (например, CODEOWNERS) изменения в ветке могут затронуть критичные окружения

- Недостаточная устойчивость к масштабированию из-за нагрузки на ApplicationSet. При каждом синке он генерирует все конфигурации, а потом фильтрует по кластеру — на масштабах это может тормозить

И ещё пара продвинутых трюков

  • Cluster generator: выбирает кластеры по лейблам. Можно делать гибкие конфигурации для мультикластерных деплоев.

  • Сабчарты: создать метачарт, куда включены все приложения.

  • List generator: вручную задать список окружений и приложений в YAML — удобно, если конфигурация стабильна.

  • CMP (Config Management Plugin): кастомный плагин, который рендерит всё по своим правилам. Самый гибкий, но и самый сложный способ.

Заключение

Мы прошли путь от простой, но ограниченной схемы App of Apps к более гибким и масштабируемым подходам с использованием ApplicationSet. Каждый вариант — от «в лоб» до матричного генератора и CI-шаблонизации — имеет свои плюсы и ограничения. Выбор зависит от ваших целей: стабильность, гибкость, масштаб, независимость от CI. Ниже — удобная таблица для сравнения.

А какой подход используете вы? Расскажите в комментариях, что сработало, а что нет. Особенно интересно, если вы ушли от ApplicationSet — и к чему именно.

P. S.

Читайте также в нашем блоге:

Комментарии (4)


  1. RieSet
    21.10.2025 16:06

    Ветки - кажутся не семантическим решением для этой задачи

    Сейчас экспериментирую с Helm поверх Application. Вместо ApplicationSet или CI/CD генерации - вся мощь Helm


    1. zlyoha
      21.10.2025 16:06

      Сейчас экспериментирую с Helm поверх Application

      А есть возможность сниппеты показать как это выглядит? Интересно для какого кейса оно себя хорошо показывает.


      1. RieSet
        21.10.2025 16:06

        Мне кажется, я не очень понял, что именно показать. Могу в личке ответить на вопросы. Этот способ нивелирует некоторые минусы из приведенных в статье, но у меня пока нет длительного опыта использования.

        Через ArgoCD менеджерим микросервисы самого приложения, а не инфру для него. И настраиваем сами микросервисы через Helm.

        App -> (App Helm dev, stage, prod) -> Argo Application -> Argo Helm (dev_1_cluster, dev_2_cluster, dev_3_cluster)

        ArgoCD Application — это CRD. В большинстве случаев ставим один раз и часто изменяем через web интерфейс. Поэтому не проблема обернуть его в чарт.

        Конфигурируем через файлик values_еще одно окружение.yaml. А Helm создает манифесты: apiVersion: argoproj.io/v1alpha1;kind: Application:

        Определяем:

        • в каком кластере разврачиваем,

        • с какими префиксами,

        • в каких неймспейсах,

        • какой вариант values файла использовать из самого микросервиса (dev, stage, test...).

        • набор микросервисов

        • можно откатить если что-то наредактировали в web интерфейсе

        • можно независимо версионировать запускаемый набор микросервисов

        Можно и вручную развернуть для отладки и дебага, можно и встроить в какой либо процесс депоя через hook. И все площадки приложения оказываются в файлах, которые легко можно сравнить в IDE:

        • values.yaml,

        • values-dev.yaml,

        • values-stage.yaml,

        • values-....yaml,

        • values-test.yaml.

        Получается, что есть чарты для запуска микросервисов и есть чарт для запуска их всех в каком-то кластере через ArgoCD

        Еще думал туда попробовать job добавить для проверки наличия в кластере всех операторов перед запуском в нем микросервисов. И не нравится, что ArgoCD плохо за собой прибирается, может, сборщик мусора удастся впихнуть.


  1. xitriy87
    21.10.2025 16:06

    А зачем вы используете GoTemplates, если судя по скринам, не используются никакие фишки, ну кроме missingkey? Можно было бы и обычные templates использовать, что бы не усложнять синтаксис.