Семнадцать лет я писал код в Конфигураторе, хранил его в хранилище 1С, а деплоил руками. Открыть Конфигуратор на сервере, загрузить из хранилища, обновить базу данных, закрыть, проверить. Иногда ночью. Иногда в выходные. Иногда — когда бухгалтерия ждёт и звонит каждые пять минут.
Потом я попробовал Git. Потом — GitHub Actions. Потом провёл два дня в отладке дедлока, который сам же создал. И ещё день — в попытках заставить hot deploy работать с BSL-модулями. Это история про то, как CI/CD для 1С из красивой идеи превратился в рабочий пайплайн. С граблями, которые я аккуратно собрал и разложил по порядку.
Зачем уходить из хранилища
Хранилище 1С — штука рабочая. Для команды из двух-трёх человек, работающих с одной базой, оно справляется. Но у него есть фундаментальное ограничение: оно живёт в парадигме «захватил объект — поработал — поместил обратно». Это пессимистичная блокировка. Если я захватил документ «РеализацияТоваров», никто другой его трогать не может. Даже если я правлю одну процедуру из сорока.
Git работает иначе. Оптимистичные блокировки, ветки, мерж. Два разработчика могут одновременно править один модуль — и система сама разберётся, если изменения не конфликтуют. А если конфликтуют — покажет конфликт явно, а не молча перезапишет чужую работу.
Но главное — автоматизация. С хранилищем деплой — всегда ручная операция. С Git и GitHub Actions деплой — это коммит. Я пушу изменения, CI-пайплайн конвертирует EDT-проект в XML, загружает конфигурацию в базу, обновляет структуру. Без Конфигуратора на моём рабочем столе. Без звонков в два часа ночи.
Звучит просто. На бумаге — три шага. На практике — неделя открытий.
Дедлок: когда HTTP-сервис блокирует сам себя
Первая задача в любом CI/CD для 1С — управление сеансами. Перед обновлением структуры базы нужно завершить активные сеансы и заблокировать новые подключения. Стандартная процедура, описана в документации.
У базы уже был HTTP-сервис — для интеграции с внешними системами. Токен авторизации настроен, эндпоинт работает. Логичная мысль: добавить метод для управления блокировкой. Пара строк кода, POST-запрос с телом {"action": "lock"}, и CI-скрипт через curl блокирует базу перед деплоем.
Написал. Протестировал блокировку — работает. Протестировал разблокировку — работает. Собрал пайплайн целиком. Запустил.
Сеансы заблокировались. Регламентные задания остановились. И тут пайплайн завис. Навсегда.
Минута. Две. Пять. Таймаут GitHub Actions — шесть часов. Я отменил вручную через три минуты, когда понял, что база не отвечает. Ни на HTTP-запросы, ни на подключение через Конфигуратор. Мёртвая.
Дедлок в чистом виде. HTTP-сервис 1С живёт внутри рабочего процесса rphost. Когда блокировка базы останавливает планировщик заданий, она останавливает и обработку HTTP-запросов. Мой скрипт отправляет POST /unlock, но rphost уже не принимает запросы — он заблокирован тем самым вызовом /lock, который я отправил минуту назад.
Классическая ситуация: процесс заблокировал ресурс и ждёт ответа от этого же ресурса, чтобы его разблокировать. Замкнутый круг.
Решение: rac.exe и внешнее управление кластером
Выход нашёлся в утилите rac.exe — Remote Administration Console. Она обращается к кластеру 1С через порт RAS (по умолчанию 1545), минуя прикладной уровень информационной базы. Это принципиально другой канал связи.
rac.exe session --cluster=<cluster-id> list --infobase=<ib-id>
rac.exe session --cluster=<cluster-id> terminate --session=<session-id>
rac.exe infobase --cluster=<cluster-id> update --infobase=<ib-id> --sessions-deny=on
Блокировка снаружи, не изнутри. rac.exe не зависит от состояния HTTP-сервиса базы. Даже если база полностью заблокирована, RAS-порт кластера доступен. Дедлок невозможен.
Один вечер рефакторинга — и все операции с сеансами переехали на rac.exe. HTTP-сервис остался для бизнес-интеграций, где ему место.
Мораль простая: не управляйте инфраструктурой через прикладной слой. Это как тушить пожар изнутри горящего дома.
Hot deploy: два дня, которые не были потрачены зря
Следующая цель — обновление BSL-кода без остановки пользователей. В документации платформы есть команда /UpdateDBCfg -Dynamic+, которая применяет изменения конфигурации «на лету». Метаданные не трогаются, структура таблиц не меняется, пользователи продолжают работать.
Звучало как мечта. Схема казалась очевидной:
EDT (src/) --> экспорт в XML --> LoadConfigFromFiles --> /UpdateDBCfg -Dynamic+
Написал скрипт. Запустил. Изменил метаданные — применились. Изменил код в модуле формы — добавил новую функцию, запушил, дождался деплоя. Открыл базу. Функции нет.
Перепроверил. Ещё раз изменил BSL-код, запустил пайплайн. Метаданные обновляются. BSL — нет. Как будто платформа игнорирует текстовые файлы модулей.
Тупик за тупиком
Два дня я пробовал альтернативные подходы. Четыре скрипта, больше десяти итераций.
Попытка через временную базу. Идея: создать пустую файловую базу, загрузить в неё EDT через LoadConfigFromFiles, экспортировать обратно через DumpConfigToFiles, получить «чистый» XML. Команда DumpCfg отвечала «неопределена информационная база» при любом варианте синтаксиса строки подключения. Файловая база, серверная — без разницы. Ошибка стабильная и непробиваемая.
Попытка через 1cedtcli build. Утилита EDT может собрать конфигурацию в бинарный формат. Но формат выходного файла документально не описан. Что с ним делать дальше — неясно. LoadCfg его не принимает. Прямая загрузка в базу — не предусмотрена.
Статьи на Infostart. Нашёл подробный материал про CI/CD стек для EDT. Описывает архитектуру, объясняет зачем. Но конкретные команды и параметры — за кадром. «Делайте так» без «вот так конкретно».
Корень проблемы
Тупик оказался не в концепции hot deploy, а в источнике XML. Мой первый скрипт строил цепочку: EDT → временная база → LoadConfigFromFiles → DumpConfigToFiles → XML. Этот XML был «пропущен через базу». И именно на этапе загрузки во временную базу BSL-модули терялись. LoadConfigFromFiles загружала структуру метаданных, но игнорировала текстовые файлы модулей. Круг замкнулся ещё до UpdateDBCfg.
Рабочий путь оказался короче. Утилита 1cedtcli export --configuration-files берёт BSL-файлы прямо из EDT-проекта, без промежуточной базы. Никакой конвертации «туда-обратно». Чистый экспорт из исходников в XML-формат, который LoadConfigFromFiles принимает корректно. /UpdateDBCfg -Dynamic+ после этого применяет и метаданные, и BSL-код.
Два дня исследований ради одного ключа командной строки. Это не провал. Это цена входа в территорию, где документация заканчивается раньше, чем начинаются реальные сценарии.
Оптимизация деплоя: с двадцати минут до трёх
Пайплайн заработал. Коммит → конвертация → загрузка → обновление. Полный цикл. Одна проблема: пятнадцать-двадцать минут простоя на каждый деплой.
Почему так долго? Потому что весь пайплайн работал в монопольном режиме. Завершить сеансы, заблокировать базу, загрузить XML через LoadConfigFromFiles, обновить структуру через UpdateDBCfg, разблокировать. Два шага требуют Конфигуратор в пакетном режиме, и оба запускались при заблокированной базе.
Но так ли это необходимо?
Первый уровень: разделение этапов
LoadConfigFromFiles загружает XML в конфигурацию базы. Она меняет метаданные в хранилище конфигурации, но не трогает структуру таблиц. Пользователи могут работать — они используют текущую конфигурацию базы данных, а не конфигурацию поставщика. Монопольный доступ LoadConfigFromFiles не нужен.
UpdateDBCfg — другое дело. Реструктуризация таблиц, изменение индексов, миграция данных. Тут без эксклюзивного доступа никак.
Я перенёс LoadConfigFromFiles в начало пайплайна, сразу после конвертации EDT в XML. Пока загружается конфигурация — пользователи работают. Блокировка включается только для UpdateDBCfg.
Результат: реальный простой сократился с пятнадцати-двадцати минут до трёх-пяти. LoadConfigFromFiles занимает основное время, а UpdateDBCfg при типичных изменениях укладывается в пару минут.
Второй уровень: точечная терминация сеансов
LoadConfigFromFiles не конфликтует с Enterprise-сеансами, но требует эксклюзивного доступа к конфигурации. Если кто-то из разработчиков оставил открытый Конфигуратор — загрузка зависнет. Молча. Без сообщений. Без таймаута. Просто висит и ждёт.
Первый рефлекс: завершить все сеансы перед загрузкой. Но «все» — это и бухгалтер, которая проводит платёжку. И менеджер, который оформляет заказ клиенту. Грубая сила работает, но побочные эффекты неприемлемы.
Через rac session list можно получить список сеансов с типом приложения. app-id: Designer — это Конфигуратор. app-id: 1CV8C — тонкий клиент. Фильтруем, завершаем только Designer-сеансы. Бухгалтер с платёжкой не заметит ничего.
# Получить список сеансов Конфигуратора
rac session list --cluster=$CLUSTER --infobase=$IB | grep -B5 "app-id.*Designer"
# Завершить каждый с сообщением
rac session terminate --cluster=$CLUSTER --session=$SESSION_ID \
--error-message="CI/CD: LoadConfigFromFiles requires exclusive config access"
Третий уровень: изоляция окружений
Этот урок обошёлся дороже остальных. Задеплоил доработку на dev-базу. Через минуту обнаружил, что prod заблокирован.
Причина: concurrency group в GitHub Actions была общей для dev и prod. Физически это один раннер на одном сервере. Dev-деплой занял раннер на двадцать минут — prod-задачи встали в очередь. Продуктив парализован ради обновления тестовой среды.
Паника. Ручная отмена всех workflow runs. Проверка, что сервер отвечает. Проверка, что база не осталась в заблокированном состоянии. Всё обошлось — но адреналина хватило на неделю.
Решение — отдельные concurrency groups для каждого окружения:
# Было:
concurrency:
group: deploy-1c
# Стало:
concurrency:
group: deploy-1c-${{ github.event.inputs.environment || 'prod' }}
Теперь dev-деплой не блокирует prod. Каждое окружение живёт в своей очереди.
Почему CI/CD для 1С — редкость (и почему это стоит менять)
В мире Java, Python, JavaScript CI/CD — гигиенический минимум. В мире 1С — экзотика. Почему?
Во-первых, порог входа. Платформа 1С не проектировалась для командной строки. Пакетный режим Конфигуратора — это надстройка, а не фундамент. Документация по пакетным командам скудная. Утилиты вроде 1cedtcli появились недавно и описаны фрагментарно. Чтобы собрать рабочий пайплайн, нужно экспериментировать — а эксперименты на продуктивной базе никто не одобрит.
Во-вторых, культура. 1С-разработка исторически — это Конфигуратор, хранилище, ручной деплой. Инструменты формируют привычки, привычки формируют культуру. Когда вся экосистема построена вокруг GUI, переход на CLI и скрипты воспринимается как блажь, а не как необходимость.
В-третьих, экономика. Типичный 1С-проект — один-два разработчика, одна-две базы. Для такого масштаба ручной деплой раз в неделю — терпимо. Затраты на CI/CD не окупаются.
Но есть проекты, где базы три, пять, десять. Где деплой нужен каждый день. Где ошибка в ручном обновлении стоит рабочего дня бухгалтерии. Для таких проектов CI/CD — не роскошь. Это страховка.
Мой пайплайн сейчас выглядит так: коммит в Git запускает GitHub Actions, раннер на сервере конвертирует EDT в XML, загружает конфигурацию без остановки пользователей, завершает только Designer-сеансы, блокирует базу на три-пять минут для обновления структуры, разблокирует. Полный цикл — автоматический, предсказуемый, воспроизводимый.
Каждый шаг этого пайплайна — результат конкретной ошибки. Дедлок научил использовать rac.exe вместо HTTP-сервиса. Два дня с hot deploy научили не доверять промежуточным конвертациям. Двадцать минут простоя научили разделять этапы. Заблокированный прод научил изолировать окружения.
Грабли — лучшая документация. Потому что документация рассказывает, как должно работать. А грабли — как работает на самом деле.


