
CRM, регуляторные ограничения и автоматизация: как мы выстроили надёжный процесс релизов
Как 60+ модулей, 7 production-окружений и требования аудиторов привели нас к единому процессу доставки — и что из этого получилось.
Привет! Меня зовут Евгений, я DevOps-инженер в EXANTE. В этой статье расскажу, как мы перешли от ручных субботних релизов через Jenkins к единому автоматизированному процессу доставки, который сейчас работает на 30+ сервисах. Разберу, какие проблемы нас к этому подтолкнули, как мы учли требования аудиторов и что в итоге изменилось в повседневной работе команды.
Что такое Exante-CRM
У нас есть CRM-система — exante-crm. На ней завязано почти всё: работа с клиентами, документооборот, комплаенс, интеграции. Если CRM лежит — часть бизнес-операций блокируется. Когда CRM обновляется — все об этом знают, потому что нервничают.
Масштаб в цифрах:
60+ Django-приложений (apps) в одном репозитории — от модуля клиентских заявок до интеграции с HR и BI
3 базы PostgreSQL — основная CRM, бэкофис-данные и поисковый индекс
6 очередей Celery + Redis — письма, аудит-логи, экспорт, синхронизация, живые данные
10+ whitelabel-брендов — каждый со своим доменом, конфигурацией и локалью
15+ внешних интеграций — KYC-провайдеры, банковские сервисы, маркетинг, аналитика
7 production-окружений — разные юрисдикции, разные кластеры Kubernetes (GKE), плюс stage и load-testing
Связанные компоненты — после деплоя основной CRM нужно обновить crm-script (фоновые задачи), чтобы обе части были на одной версии
Архитектура CRM — один монолитный репозиторий, внутри модули с чёткими зонами. Продукт рос годами, а комбинация «куча модулей + куча окружений + связанные сервисы» сделала ручные релизы особенно муторными. Каждый релиз — не просто «код поехал на прод», а потенциальный вопрос аудитора: кто раскатил и почему.
Небольшое пояснение для тех, кто не сталкивался с аудитами в финтехе. EXANTE — лицензированный брокер, работающий в нескольких юрисдикциях. Регулятор (и нанятые им аудиторы) периодически проверяют, как компания управляет изменениями в production-системах: кто инициирова�� изменение, кто одобрил, какие проверки были выполнены, есть ли возможность откатить. Всё это называется change management, и для нас это не формальность — это требование, без выполнения которого лицензия под вопросом. Дальше в статье «аудит» и «аудиторы» — именно про это.
В этой статье я расскажу, как у нас получилось перевести релизы из ручного и хрупкого режима в предсказуемый и аудируемый. Никакой магии: просто последовательно всё описали и зашили в пайплайн. Этот же подход уже крутится на 30+ сервисах, не только на CRM.
Как было раньше
Раньше разработчик собирал тег, заполнял тикет в Jira в надежде ничего не забыть, а потом в субботу релизный инженер раскатывал всё через Jenkins. Код и пайплайны — в GitLab, деплой — из Jenkins. GitOps был разве что в названии.
Что было не так:
Разработчик создавал тикеты вручную. Каждый раз ему нужно было заполнять в Jira множество полей. Заполнять, перепроверять, обнаруживать, что половину заполнил неправильно. Иногда разработчик мог вообще забыть создать тикет.
Релизы происходили в определённые дни и были жёстко связаны с календарём. Почему суббота? Логика была такой: в выходные меньше пользователей в системе, а значит, меньше риск задеть кого-то при проблеме. Плюс есть буфер — целый день до понедельника, чтобы заметить и починить. Со временем это превратилось в традицию, которую никто не пересматривал. В итоге копился diff, по объёму релизы были огромными, а пятницы — нервными.
Информация о том, как правильно деплоить — порядок действий, нюансы окружений, конфигурации, — хранилась в головах. Нового человека нужно было месяцами вводить в курс дела, прежде чем доверить ему релиз.
Источник истины был в чате. Чтобы понять, что задеплоено на конкретном окружении, надо было лезть на кластер или писать в чат «А что у нас на проде?» и ждать ответа.
Нет автоматизации. После деплоя инженер вручную проходил по разделам: главная, вкладки, пара форм. Ключевые страницы отвечают — ок, поехали дальше.
Почему мы решили это менять
Со старым процессом жить можно было — мы так и делали. Релизы выходили, всё работало. Со временем «неудобно» превратилось в «рискованно».
Аудиторы стали задавать вопросы, на которые мы не могли быстро ответить
EXANTE — лицензированный брокер. С ростом компании и выходом в новые юрисдикции регуляторный контроль усилился. Внешние аудиторы — это специалисты, которых привлекает регулятор (или сама компания для подготовки к проверке). Их задача — убедиться, что изменения в production-системах происходят контролируемо: есть ответственный, есть одобрение, есть запись о том, что произошло. Раньше проверки были реже и менее детальными, но по мере роста масштаба и количества юрисдикций аудиторы начали копать глубже.
Они стали регулярно спрашивать: кто и когда раскатил версию X на окружение Y, почему он это сделал и кто ему это одобрил. При ручном процессе поиск ответа превращался в расследование:
Тикет в Jira есть, но заполнен кое-как.
Лог деплоя есть, но надо знать, где копать.
Кто нажал кнопку — помнит коллега, который в отпуске.
И мы не раз тратили часы на то, что должно было фиксироваться автоматически.
Масштаб перерос ручной процесс
При двух окружениях ручной деплой ещё терпим. При семи окружениях, каждое из которых со своим кластером, переменными и Jira-компонентами, процесс поехал. На ты коллега забыл обновить тикет, на четвёртом перепутал версию, а на пятом не обновил crm-script после CRM. Это не было разгильдяйством, просто у внимания есть предел при монотонной возне.
Сервисов при этом становилось больше. CRM — не единственный. Десятки проектов, каждый деплоится по-своему — единого процесса нет, новых людей не обучить, улучшать приходится в трёх десятках мест.
Люди устали от рутины
Те, кто знал, как правильно деплоить, коротали время на тикетах, скриптах, проверке «поехало ли», статусах, уведомлениях в Slack. То есть тратили время на рутину в ущерб задачам, где нужно заниматься инженерией и думать головой.
Фиксы ждали «окна», а не пользователей
Поскольку деплои были по расписанию, баг, найденный в понедельник, фиксился только в следующее окно. Hotfix формально был, но согласований и ручных шагов в нём было столько, что к нему прибегали только в случае пожара. Код копился между релизами, поэтому каждый деплой превращался в огромный пакет обновлений в котором любую найденную проблему не изолировать быстро.
Связанные компоненты расходились
CRM — это основное приложение (API, веб-интерфейс). Рядом с ним живёт crm-script — это тот же код CRM, но запущенный в режиме Celery workers: фоновые задачи (рассылки, синхронизация данных, экспорт, обработка очередей). Поскольку crm-script использует те же модели данных и ту же кодовую базу, что и основное приложение, их версии обязаны совпадать. Если основная CRM обновилась, а crm-script остался на старой версии, фоновые задачи начинают работать с устаревшими моделями — и через часы джобы начинают сыпаться. При ручном деплое crm-script частенько забывали обновить, и рассинхрон с API вылезал, когда уже было поздно.
Что мы переделали и как стало
Мы двигались пошагово.
Первую итерацию сделали на одном некритичном сервисе — в ней был минимальный набор: создание change-тикета в Jira через API, коммит нового тега в Flux-репозиторий и уведомление в Slack. Убедились, что базовая связка работает.
Потом добавили автоматические переходы статусов в Jira (Implementing → Released / Rolled back), верификацию после деплоя (проверки подов, health, логов) и Slack-треды вместо отдельных сообщений. Обкатали на пяти с лишним проектах, собрали фидбек, починили edge cases (конфликты коммитов в Flux, таймауты Jira API, Slack rate limits). CRM, одну из самых сложных и критичных систем, подключили уже после этого — когда были уверены, что шаблон выдерживает нагрузку.
Наша идея была простой: в момент деплоя человек делает одно ручное действие — нажимает кнопку на нужное окружение. Остальное ведёт пайплайн. При этом «один клик» — это именно про момент релиза. Code review, MR approvals, доступы, окна релизов (где они есть) — по-прежнему часть процесса и идут до деплоя. Мы автоматизировали рутину раскатки, а не убрали контроль.

Давайте разберём по шагам, что происходит
Сначала разработчик создаёт Git-тег — по нему стартует пайплайн. Дальше в ход вступает автоматизация: сборка образа, линтеры, тесты, сканирование зависимостей. Если что-то сломалось, процесс дальше не идёт.
В GitLab для каждого окружения висят кнопки деплоя. Разработчик выбирает нужное окружение, нажимает кнопку и может идти дальше пить кофе или смотреть лог (кому как удобнее).
Затем управление берёт автоматика. Стартует дочерний пайплайн с цепочкой этапов:
Prepare — создаёт тикет в Jira через Service Desk API и открывает тред в Slack. Или пропускает, если приложение не в CMDB.
Deploy — добавляеткоммит в Flux-репозиторий (обновление тега образа), Jira-тикет переводится в «Implementing».
Verify — ждёт раскатку, потом гоняет проверки. Подробнее — в разделе ниже.
Finish — при успехе: Jira → «Released», тред обновлён. При провале: «Rolled back» и оповещения.
Post-release — если есть связанные компоненты, например crm-script, пайплайн обновляет и их, чтобы версии не разъехались.
Рассмотрим пример ключевого фрагмента конфигурации. Подключение к единому шаблону упрощённо выглядит так:
.gitlab-ci.yml проекта
stages:
- lint
- test
- build
- deploy
- verify
include:
Подключаем единый шаблон auto-change deploy
- project: 'ops/devops/cicd'
ref: '$CI_DEFAULT_BRANCH' # default branch репозитория шаблонов
file: 'base-templates/jira-change/multi-env-projects.yml'
Референс шаблона зафиксирован (тег или коммит) — общие обновления не ломают релизы сюрпризом, команда подтягивает версию сама, когда готова.
Одна строка include — и проект получает всю автоматику деплоя, если окружения и параметры есть в общем реестре и права на деплой выданы. Конфигурация приложения — в едином YAML-реестре.
Что входит в наш шаблон для деплоя
В статье я рассказываю в основном о CRM, но шаблон деплоя общий. Его используют 30+ сервисов: бэкенды, фронтенды и утилиты. CRM подключили не первой, зато на ней шаблон прошёл проверку по-настоящему тяжёлыми сценариями.
Что мы стандартизировали и что важно не упустить, если делаете нечто похожее:
Создание одиного триггера для всех проектов: тег → кнопка → автоматический флоу. Разные способы для разных проектов — это верный путь к хаосу.
Автоматическое создание change-тикетов. При регуляторных требованиях тикеты должны создаваться системой, потому что люди забывают поля, путают компоненты и пропускают окружения.
Один деплой = один тред в Slack. Иначе канал превращается в кашу и историю деплоя не собрать.
GitOps-коммиты должны быть в едином формате: в сообщении коммита нужна ссылка на тикет и окружение. Именно это аудитор смотрит в Git.
Post-deploy верифицируется одним набором проверок для всех. Иначе для одного сервиса вы проверяете, для другого забываете — проверено на собственной практике.
Создана единая статусная модель: Created → Implementing → Verifying → Released / Rolled back. Аудитор фильтрует по статусу и сразу видит, что где.
Добавлены Post-release хуки для связанных компонентов (типа нашего crm-script) — обновление только автоматическое. Если при создании тикета, в ручную указывать "Не забыть обновить еще и...", баги будут возникать постоянно.
Retry при параллельных деплоях: когда два сервиса коммитят в один Flux-репо, возникает конфликт. Без retry с backoff, флоу ломался бы постоянно.
Унификация конфигурации окружений в одном реестре: Jira-ключи, кластеры, namespace'ы, health-пути. Чтобы создать новое окружение или сервис — понадобится всего пара строк в YAML.
Ошибки на каждом этапе не глотаем: уведомление уходит в любом случае, тикет получает корректный статус. «Тихий» сбой для нас хуже явного.
Уточню одну деталь про масштаб. На двух-трёх проектах пользоваться единым шаблоном удобно. На тридцати с лишним — уже необходимо: без него не гарантировать одинаковый compliance, не обучить нового инженера за час, не починить процесс в одной точке вместо тридцати. Мы поняли это, когда проектов стало много.
Verify: что это и чего это НЕ делает
Verify — это автоматический этап пайплайна, который отвечает на вопрос: «деплой прошёл, приложение живое?». Это не отдельный сервис и не внешний инструмент, а job в GitLab CI, который запускается после того, как Flux применил изменения на кластере.
Verify отвечает за проверку следующего:
HelmRelease перешел в статус Ready.
Deployment rollout завершён.
Поды запущены и готовы (Running/Ready).
Тег образа соответствует ожидаемому.
Нет аномальных рестартов контейнеров.
Service endpoints доступны.
Internal health check (health-эндпоинт приложения).
External health check (через ingress).
Анализ свежих логов: он позволяет найти критичные уровни (ERROR, FATAL) и аномальные всплески ошибок в первые минуты после раскатки.
Если все проверки пройдены, Verify фиксирует DEPLOY_RESULT=SUCCESS, и пайплайн переходит к этапу Released. Если хотя бы одна критичная проверка провалилась, результат — FAILURE, и пайплайн переходит к Rolled back.
Чего Verify не делает. Verify ловит проблемы деплоя и доступности: упавшие поды, зависший rollout, мёртвый health, не тот образ. Это санитарный минимум. Функциональные тесты Verify не заменяет. Бизнес-логика, формы, интеграции — за это отвечает следующий уровень, автосмоки.
Автосмоки: уже в работе
Если Verify замечает проблемы уровня «приложение не запустилось». Но есть класс ошибок, которые он не отслеживает: сломанная авторизация, недоступная интеграция, ошибка в критичной форме.
Автосмоки — это естественное продолжение текущего Verify. Если Verify отвечает на вопрос «приложение живое?», то смоки отвечают на «приложение работает?»
Именно поэтому мы уже занимаемся внедрением автоматических smoke-тестов:
Когда запускаются: после деплоя и успешного Verify. Это третий уровень проверки: Build → Verify → Smoke.
Что проверяют: базовые критичные сценарии — доступность ключевых страниц, возможность авторизации, корректность ответов основных API-эндпоинтов.
Что дают: сокращение ручных post-deploy проверок. Вместо «пойду потыкаю руками» — автоматический отчёт, который сразу показывает, что сломалось.
Audit trail: результаты автосмоков также попадают в артефакты пайплайна и привязываются к Jira-тикету. Аудитор сможет увидеть не только «деплой прошёл», но и «базовые сценарии проверены».
А что с rollback?
С Flux CD авто-rollback уже есть: если HelmRelease не уходит в Ready, Flux откатывается на предыдущую ревизию и шлёт алерты. Предыдущий тег образа лежит в артефактах, поэтому при необходимости можно сделать ручной откат за минуты.
На части окружений с жёсткой регуляторикой откат требует подтверждения, поэтому откаты мы делаем только с подтверждения. Для нас это фича: «само откатилось» в такой среде иногда хуже, чем «откатили за пять минут и зафиксировали».
GitOps + Git Flow, но по-взрослому
В учебнике GitOps красив: всё в Git, декларативно, автоматически. В реальности с несколькими юрисдикциями и аудиторами, GitOps процессы могут применяться только с кастомизацией под потребности продуктов.
Что от GitOps теории мы реально используем в работе:
Git как источник истины: состояние каждого окружения описано в Flux-репозитории. Что в Git, то и на кластере.
Декларативность: изменения — это коммиты в единый репозиторий, а не kubectl patch или ручной helm upgrade.
Аудируемость: Git сохраняет полную историю для нужд аудиторов: можно легко найти кто, когда, какой тег, с каким Jira-тикетом и тд.
А что пришлось адаптировать под регуляторные потребности:
Manual trigger: осознанное нажатие кнопки, которое фиксируется как решение о релизе, чтобы деплой не происходил автоматически по коммиту.
Change ticket: каждый деплой порождает тикет с полным контекстом: версия, окружение, кто запустил, статус.
Approval gates: code review и MR approvals происходят до создания тега, как часть основного workflow проекта.
Разделение окружений: каждое production-окружение — отдельная кнопка, отдельный тикет, отдельная верификация. Нет кнопки «раскатить на всё сразу».
В итоге у нас получился GitOps с учетом нашей специфики. Не «нарушаем», а «подстраиваем под регуляторку и контроль».
Чем помогает Git Flow
В итоге кастомизированная версия Git Flow даёт нам:
Чёткую точку релиза — тег на определённом коммите. Это не «вроде бы последний мастер», а конкретная, неизменная версия.
Историю решений — merge commits, PR reviews, approvals — всё в Git-хранилище данных.
Возможность cherry-pick / hotfix — когда нужно срочно починить одно окружение, не ломая остальные.
Git Flow сам по себе не даёт compliance. Нужны дополнительные вещи: branch protection, обязательные ревью, при необходимости подписанные коммиты, плюс связка Git-артефактов с change-тикетами.
9. Артефакты для аудитов
Когда аудитор просит историю изменений за квартал, вот что мы ему показываем.
Артефакт | Что содержит | Где хранится |
Jira-тикет | Версия, окружение, приложение, кто запустил, статусные переходы | Jira |
GitLab-пайплайн | Полный лог сборки, тестов, деплоя, верификации. Связь с тегом и коммитом | GitLab |
Flux-коммит | Кто, когда, какой тег, на какое окружение. Сообщение коммита содержит Jira-ключ | Git-репозиторий |
Slack-тред | Хронология деплоя в реальном времени: создание тикета, начало, верификация, результат | Slack |
Job-артефакты | Переменные окружения, предыдущий тег, результаты верификации, ошибки | GitLab |
Docker-образ | Конкретный immutable-образ с тегом, привязанным к Git-коммиту | Container Registry |
Про retention (сроки хранения)
Зачем нужен retention? Аудитор может прийти через полгода и попросить полную историю изменений за последние кварталы. Если к этому моменту job-артефакты в GitLab уже протухли, Docker-образ удалён по политике очистки, а Slack-тред архивирован — восстановить цепочку «кто → когда → что → с каким результатом» будет невозможно. Без нормального retention compliance-стратегия дырявая: артефакты есть, пока они свежие, и исчезают, когда нужны больше всего.
Поэтому мы настраиваем retention по типам артефактов:
Jira и Git — постоянное хранение (пока живёт система).
GitLab job-артефакты — ограниченный срок; для compliance-критичных данных нужно настраивать увеличенный retention или экспорт.
Docker-образы — политика очистки должна учитывать, что production-образы могут понадобиться для аудита или rollback.
Артефакты связаны в цепочку: по Jira-тикету — пайплайн, по пайплайну — Flux-коммит, по коммиту — тег и образ. Аудитору как раз и нужна такая трассируемость: от решения до результата без белых пятен.
Что нам дала автоматизация деплоя
Мы не замеряли каждый деплой секундомером, поэтому точных цифр «до/после» у меня нет. Но вот что изменилось ощутимо:
Для большинства окружений больше нет привязки к конкретному дню. Теперь мы деплоим, когда это нужно, а не когда есть «окно». Благодаря этому код меньше копится между релизами, и в каждом деплое меньше «бомб замедленного действия».
Теперь мы всегда готовы к приходу аудиторов. По одному запросу в Jira мы предоставляем ему всю историю за квартал. Мы экономим часы подготовки к проверкам и нам больше не надо ничего собирать в авральном режиме. Аудитор приходит — мы готовы. Не надо ничего собирать в авральном режиме. Один запрос в Jira — вся история за квартал. Это реально сэкономило нам часы подготовки к проверкам
Инженеры занимаются инженерией. Им больше не нужно тратить время на заполнение тикетов, писать в Slack «деплою на прод», руками проверять «а поехало ли». На каждом деплое это занимало минуты, которые складывались в часы при семи окружения.
В чате больше не всплывает вопрос «А что у нас на проде?». Каждый разработчик в любой момент может узнать информацию в Jira, Slack-тред или GitLab. Чат перестал быть источником истины, и это, честно, одно из самых приятных изменений.
У нас появился один шаблон для 30+ сервисов. Если мы улучшаем его в одном месте, это распространяется на все проекты. Разработчикам не нужно ходить по 30 репозиториям и чинить одно и то же.
Чего это не решает
Было бы нечестно написать «мы всё автоматизировали» и поставить точку. Вот где у нас остаются открытые вопросы:
Verify — это не тестирование. Он проверяет, что приложение запустилось и отвечает, но не проверяет, правильно ли работает бизнес-логика.Сейчас мы внедряем автоматические smoke-тесты, которые закроют этот разрыв (подробнее — в следующем разделе).
Единый шаблон настроен, исходя из разных параметров проектов,и покрывает подавляющее большинство сценариев. Но у ряда сервисов есть особенности — нестандартные health-пути, каскадные зависимости, специфичная логика окружений. Для них нужна дополнительная конфигурация.
Внешние зависимости. Если упала Jira, не создастся тикет. Если лёг Slack, уведомления не придут. Деплой при этом продолжится в «Flux-only» режиме: коммит в Flux-репо пройдёт, Verify отработает, приложение обновится. Но без тикета в Jira возникнет дыра в audit trail — аудитору нечего будет показать по этому деплою. Без Slack-уведомлений команда не узнает о деплое в реальном времени. Формально деплой пройдёт, но часть автоматики, которая нужна для контроля и прозрачности, деградирует.
Выводы, которые мы сделали
Главная ценность — предсказуемость, а не скорость. Да, деплоить стало быстрее. Но по-настоящему важно, что каждый раз — одинаково. Без «а на этом окружении мы делаем чуть по-другому».
Аудиторам нравится автоматика. «Мы всегда делаем вот так, вот артефакты» — это сильнее, чем «мы стараемся не забывать». Автоматический процесс доказывает сам себя.
Шаблон окупается. Первый сервис подключали долго. Второй — быстрее. Тридцатый — за пару строк в конфиге. Инвестиция в хороший шаблон возвращается многократно.
GitOps — подход, а не религия. Берите принципы (Git как source of truth, декларативность) и адаптируйте. «Чистый GitOps» в регулируемой среде не живёт, а GitOps-подход — вполне.
Честность про ограничения повышает доверие. Мы не говорим «всё автоматизировано» — мы говорим «вот что автоматизировано, вот что нет, вот куда идём дальше». И это работает лучше.
Что в итоге
Мы начали с ситуации, где каждый деплой был событием: он проходил в конкретный день, за него отвечали конкретные люди, они заводили ручные тикеты и надеялись, что ничего не сломается. И теперь мы пришли к тому, что деплой стал рутиной. Разработчику достаточно нажать кнопку, чтобы система сделала все остальное, а аудитор был доволен.
Никакой магии - мы просто связывали привычные интсрументы (GitLab CI, Flux, Jira API, Slack) в один сценарий и проверяли его работоспособность постепенно корректируя процесс под потребности продукта и требования регулятора. Так, сервис за сервисом, нам удалось реализовать автоматический флоу и упростить поиск и хранение данных для 30+ сервисов.
Если у вас похожая ситуация — CRM или другая бизнес-критичная система, у которой есть регуляторные требования и ручные процессы — надеемся, наш опыт покажет, что это решаемо. Не мгновенно, не идеально, но вполне реально.
Спасибо за внимание!
P.S. Статья подготовлена на основе реального опыта. Названия систем, доменов и внутренних проектов изменены или обобщены. Все цифры — порядки величин.
