Миграция данных с docker-compose
Инструкция по переносу данных со старого сервера, развёрнутого из репозитория citeck-community (docker-compose), на новый сервер, развёрнутый через Citeck Launcher v2 в серверном режиме.
Рассчитана на администратора с root-доступом к обоим серверам. Все шаги выполняются вручную, отдельными командами в терминале.
Предупреждение
Если установка кастомизирована, команды и логины/пароли нужно будет подкорректировать под Ваше окружение.
Соглашения
На source команды используют синтаксис
docker compose(Compose v2, plugin). Если у вас установлен legacydocker-compose(v1) – заменяйтеdocker composeнаdocker-composeв каждой команде.Команды этапа 2 (выгрузка) и предусловий выполняются из каталога
citeck-community(там, где лежитdocker-compose.yaml), иначе compose не найдёт сервисы.На target launcher2 управляет контейнерами напрямую через Docker SDK (без compose) – там используется
docker execс реальными именами контейнеров (citeck_postgres_default,citeck_mongo_default,citeck_zookeeper_default).
Что мигрируется и что нет
Мигрируется:
PostgreSQL – пользовательские БД (схема + данные), точечно по mapping-таблице.
MongoDB – одна БД
ecos-process→citeck_eproc.Zookeeper – содержимое каталога
version-2/(snapshots + transaction log).
НЕ мигрируется (то есть выходит за рамки этой инструкции – технически данные ниже тоже можно перенести, если у кого-то такая задача возникнет, но пошаговую процедуру для них мы здесь не описываем):
БД
keycloak– на target создаётся самим launcher2. На source соответствующая БД называетсяecos_identity(сервисecos-identity-appв community – это Keycloak старой версии).Пользователи, роли, клиенты Keycloak источника (включая demo-аккаунт
admin/admin). Если они заводились вручную – пересоздать после миграции. Admin на target использует пароль, сгенерированный launcher’ом при первом старте (показывается в визарде один раз; перевыпустить черезciteck setup admin-password).RabbitMQ – очереди и in-flight сообщения.
Volumes proxy/nginx – логи, кеш сертификатов Let’s Encrypt.
Секреты (JWT, OIDC client secret, admin password) – launcher2 генерирует свои при первой установке.
Матрица совместимости
Компонент |
Source |
Target |
Тип миграции |
|---|---|---|---|
PostgreSQL |
12.7 |
17.5 |
логический dump ( |
MongoDB |
4.0 |
4.0.2 |
|
Zookeeper |
3.8.2 (Bitnami) |
3.9.4 (official) |
копия |
RabbitMQ |
– |
– |
пропускаем |
Keycloak БД |
– |
– |
пропускаем |
PostgreSQL переезжает с мажорным скачком версии (12 → 17). Физическое копирование data-каталога (volume) не сработает – нужен только логический dump.
Mapping БД и пользователей
В launcher2 действует соглашение: имя_БД == имя_пользователя == пароль. На target пароли пользовательских БД совпадают с именами этих БД.
PostgreSQL
Source DB |
Source owner |
Target DB |
Target owner |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Не мигрируются (на source игнорируем):
ecos_gateway– в launcher2 у gateway нет своей БД.ecos_identity– это БД сервисаecos-identity-app, который в community-сетапе является Keycloak (просто более старой версии). Соответствует решению «БДkeycloakне мигрируем» – пользователи и роли источника при миграции теряются (см. «НЕ мигрируется» во вступлении).keycloak– на target создаёт сам launcher2.
MongoDB
Source DB |
Target DB |
|---|---|
|
|
Zookeeper
Source путь (хост) |
Target путь (хост) |
|---|---|
|
|
Особенности:
Source (Bitnami) хранит и снапшоты, и transaction log в одном каталоге
version-2/.Target (official zookeeper) разделяет:
data/version-2/для снапшотов и служебных файлов (acceptedEpoch,currentEpoch),datalog/version-2/для transaction log. Если их не разделить, target падает сSnapDirContentCheckException.Хранилище в launcher2 – это bind-mount каталог на хосте (не named docker volume). Точный путь можно подтвердить через
docker inspect citeck_zookeeper_default --format '{{(index .Mounts 0).Source}}'.
Предусловия
0. Резервный backup source ДО любых действий
Это страховка на случай, если апгрейд до 2026.1 (предусловие 1) сломает старый сервер. Backup НЕ используется для миграции – только для отката source.
Простейший вариант (с остановкой сервера):
docker compose stop
tar -czf citeck-community-backup-$(date +%F).tar.gz \
services/ecos-community-demo-data/ \
services/backups/
docker compose start
Если останавливать сервер нельзя – pg_dumpall + mongodump + копия каталога zookeeper в горячем режиме на работающих контейнерах.
1. Source обновлён до релиза 2026.1
Это снимает риск рассинхронизации schema/changelog при заливке в launcher2 (тоже 2026.1). В каталоге citeck-community:
git pull
docker compose up -d
Дождаться, пока все микросервисы дойдут до RUNNING / healthy – Liquibase должен догнать changelog. Проверить можно так:
docker compose ps
2. Target подготовлен
Установлен citeck-launcher версии ≥ 2.x, bundle community:2026.1 или enterprise:2026.1 (в зависимости от лицензии). Подробности первого запуска – в этапе 1.
3. Свободное место
На source: ≥ объём данных × 1.5 (для дампов).
На target: то же.
4. Доступ к docker
docker ps без sudo либо через sudo на обоих серверах.
Этап 1. Подготовка target (launcher2)
Цель этапа: получить запущенный один раз namespace, чтобы launcher создал инфраструктуру (контейнеры + docker volumes), завёл пустые БД с пользователями и сгенерировал секреты.
1.1. Установить launcher
Если ещё не установлен:
curl -fsSL https://github.com/Citeck/citeck-launcher/releases/latest/download/install.sh | bash
В TUI-визарде выбрать bundle community:2026.1 или enterprise:2026.1 (под лицензию). Записать сгенерированный admin-пароль – он показывается один раз. Если пароль потерян – после миграции его можно перевыпустить через citeck setup admin-password.
1.2. Дождаться полного запуска
citeck status -w
Все приложения должны быть в статусе RUNNING. На сервере с 16 GB RAM это занимает 5–15 минут (для enterprise – дольше). Этот шаг критичен: если webapp’ы не успеют отработать Liquibase на пустых БД, на этапе 4 импорт пойдёт в БД с неполной схемой.
1.3. Остановить все webapp’ы, оставив инфраструктуру
Состав webapp в community и enterprise отличается, единого списка не приводим.
Посмотреть текущий список:
citeck statusОстановить всё, что не относится к инфраструктуре. Инфраструктурные контейнеры, которые остаются запущенными для импорта:
postgresиmongo(zookeeper тоже инфраструктура, но его остановим непосредственно перед заливкой данных в шаге 4.3).Перечислить остальные приложения списком (
gateway,eapps,emodel,uiserv,history,notifications,integrations,eproc,transformations,proxy, и так далее – что вернётciteck status):citeck stop <app1> <app2> <app3> ...
Команда
citeck stop <app>помечает приложение как detached: после очередногоciteck startбез аргументов оно автоматически не поднимется. Это нужно, чтобы пока идёт restore, никакой webapp не писал в БД.
1.4. Учётные данные для импорта
Искать вручную не нужно:
PostgreSQL:
postgres / postgres(захардкожено в launcher2).MongoDB: пароль root сгенерирован launcher’ом, читается через
docker exec citeck_mongo_default printenv MONGO_INITDB_ROOT_PASSWORD. Команды импорта в этапе 4 берут его именно так.
Этап 2. Выгрузка с source
Все команды этого этапа выполняются на старом сервере, из каталога citeck-community. Используются service-имена из docker-compose.yaml:
PostgreSQL –
ecos-microservices-postgresql-appMongoDB –
mongodb-appZookeeper –
zookeeper-app
Подход: запускаем pg_dump / mongodump через docker compose exec -T, перенаправляем stdout прямо в файл на хосте. Никаких промежуточных docker cp и временных файлов внутри контейнера.
Рабочий каталог для дампов в инструкции – ~/citeck-migration/. Создаём подкаталоги заранее:
mkdir -p \
~/citeck-migration/postgres \
~/citeck-migration/mongo \
~/citeck-migration/zookeeper
2.1. PostgreSQL
Параметры pg_dump:
-F c– формат custom (компактный, поддерживает параллельный restore).--no-owner --no-acl– выкидываемOWNER TO/GRANT(на target пользователи называются иначе, ownership проставитpg_restore --role).--clean --if-existsНЕ используем – целевую БД чистим отдельно командойDROP SCHEMA.Без
-f /tmp/...–pg_dumpпишет в stdout, мы перенаправляем>в файл на хосте.
Примечание
Если какая-то из БД отсутствует на вашем source (например, в community-only deployment нет ecos_edi) – pg_dump упадёт с database "<name>" does not exist. Просто пропустите соответствующий блок: соответствующая target-БД на launcher2 либо отсутствует, либо останется пустой и Liquibase webapp’а наполнит её при первом старте.
Команды для каждой БД из mapping-таблицы (выполнять блоками, по одному):
ecos_apps:
docker compose exec -T \
-e PGPASSWORD=postgresstorngpassword \
ecos-microservices-postgresql-app \
pg_dump -U postgres -F c --no-owner --no-acl ecos_apps \
> ~/citeck-migration/postgres/ecos_apps.dump
ecos_uiserv:
docker compose exec -T \
-e PGPASSWORD=postgresstorngpassword \
ecos-microservices-postgresql-app \
pg_dump -U postgres -F c --no-owner --no-acl ecos_uiserv \
> ~/citeck-migration/postgres/ecos_uiserv.dump
ecos_integrations:
docker compose exec -T \
-e PGPASSWORD=postgresstorngpassword \
ecos-microservices-postgresql-app \
pg_dump -U postgres -F c --no-owner --no-acl ecos_integrations \
> ~/citeck-migration/postgres/ecos_integrations.dump
ecos_model:
docker compose exec -T \
-e PGPASSWORD=postgresstorngpassword \
ecos-microservices-postgresql-app \
pg_dump -U postgres -F c --no-owner --no-acl ecos_model \
> ~/citeck-migration/postgres/ecos_model.dump
ecos_notifications:
docker compose exec -T \
-e PGPASSWORD=postgresstorngpassword \
ecos-microservices-postgresql-app \
pg_dump -U postgres -F c --no-owner --no-acl ecos_notifications \
> ~/citeck-migration/postgres/ecos_notifications.dump
ecos_history:
docker compose exec -T \
-e PGPASSWORD=postgresstorngpassword \
ecos-microservices-postgresql-app \
pg_dump -U postgres -F c --no-owner --no-acl ecos_history \
> ~/citeck-migration/postgres/ecos_history.dump
ecos_process:
docker compose exec -T \
-e PGPASSWORD=postgresstorngpassword \
ecos-microservices-postgresql-app \
pg_dump -U postgres -F c --no-owner --no-acl ecos_process \
> ~/citeck-migration/postgres/ecos_process.dump
ecos_camunda:
docker compose exec -T \
-e PGPASSWORD=postgresstorngpassword \
ecos-microservices-postgresql-app \
pg_dump -U postgres -F c --no-owner --no-acl ecos_camunda \
> ~/citeck-migration/postgres/ecos_camunda.dump
ecos_edi:
docker compose exec -T \
-e PGPASSWORD=postgresstorngpassword \
ecos-microservices-postgresql-app \
pg_dump -U postgres -F c --no-owner --no-acl ecos_edi \
> ~/citeck-migration/postgres/ecos_edi.dump
После выполнения в ~/citeck-migration/postgres/ должно быть 9 файлов:
ls ~/citeck-migration/postgres/
# ecos_apps.dump ecos_camunda.dump ecos_edi.dump ecos_history.dump
# ecos_integrations.dump ecos_model.dump ecos_notifications.dump
# ecos_process.dump ecos_uiserv.dump
Примечание
Если пароль postgres был изменён (вместо postgresstorngpassword из services/environments/ecos-microservices-postgresql-app.env) – заменить значение PGPASSWORD=... во всех командах.
2.2. MongoDB
Дамп одной БД ecos-process. Логин/пароль root – из services/environments/mongodb-app.env. Если они менялись – заменить в команде. --archive без значения = вывод в stdout.
docker compose exec -T mongodb-app mongodump \
--username root_user --password root_user_password --authenticationDatabase admin \
--db ecos-process --archive --gzip \
> ~/citeck-migration/mongo/ecos-process.archive
После выполнения в ~/citeck-migration/mongo/ должен быть один файл ecos-process.archive.
2.3. Zookeeper
Делаем на остановленном контейнере, чтобы не получить частично записанный snapshot. Bind-mount каталог принадлежит uid Bitnami zookeeper (1001), а не вашему пользователю – поэтому tar запускаем во временном контейнере, чтобы не зависеть от sudo:
docker compose stop zookeeper-app
docker run --rm \
-v "$(pwd)/services/ecos-community-demo-data/zookeeper-app/data:/src:ro" \
-v "$HOME/citeck-migration/zookeeper:/dst" \
alpine sh -c "
tar -czf /dst/version-2.tar.gz -C /src version-2 && \
chown $(id -u):$(id -g) /dst/version-2.tar.gz
"
docker compose start zookeeper-app
Путь services/ecos-community-demo-data/zookeeper-app/data – это host-путь bind-mount из services/zookeeper-app.yaml. Compose разрешает относительные пути относительно файла сервиса (т. е. services/...), а не корня репо. Если в вашем развёртывании layout другой – указать свой путь.
2.4. Итоговая структура каталога
~/citeck-migration/
├── postgres/ # 9 .dump файлов
├── mongo/ # 1 .archive
└── zookeeper/ # version-2.tar.gz
Этап 3. Передача файлов
Передайте каталог ~/citeck-migration/ (PostgreSQL-дампы + Mongo-архив + Zookeeper-tarball) на целевой сервер любым удобным способом: rsync, scp, S3, переносной носитель – на ваше усмотрение.
На целевом сервере положите его по тому же пути – ~/citeck-migration/. Важно сохранить структуру с тремя подкаталогами (postgres/, mongo/, zookeeper/) – команды этапа 4 ссылаются на пути вида ~/citeck-migration/postgres/<имя>.dump. Если файлы скопированы плоско в одну папку, импорт сломается.
Этап 4. Загрузка на target
Все команды – на новом сервере. Имена контейнеров launcher2 в серверном режиме (namespace всегда default):
PostgreSQL –
citeck_postgres_defaultMongoDB –
citeck_mongo_defaultZookeeper –
citeck_zookeeper_default
4.1. PostgreSQL
На каждую пару БД из mapping-таблицы – три действия:
Скопировать дамп в контейнер.
Очистить все пользовательские схемы целевой БД (Liquibase на первом старте создал в ней служебные объекты; их надо снести, иначе restore конфликтует по именам).
Выполнить
pg_restoreс указанием--role=<target_user>, чтобы ownership объектов навешивался корректно.
Расширения (pg_trgm, uuid-ossp и др.) восстанавливаются от имени postgres – --role на CREATE EXTENSION не влияет.
Блок команд для каждой пары (повторить 9 раз):
ecos_apps → citeck_eapps:
docker cp ~/citeck-migration/postgres/ecos_apps.dump citeck_postgres_default:/tmp/
docker exec -i \
-e PGPASSWORD=postgres \
citeck_postgres_default \
psql -U postgres -d citeck_eapps <<'SQL'
DO $$ DECLARE r RECORD; BEGIN
FOR r IN SELECT schema_name FROM information_schema.schemata
WHERE schema_name NOT IN ('pg_catalog','pg_toast','information_schema')
LOOP EXECUTE 'DROP SCHEMA IF EXISTS '||quote_ident(r.schema_name)||' CASCADE'; END LOOP;
END $$;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO citeck_eapps;
SQL
docker exec \
-e PGPASSWORD=postgres \
citeck_postgres_default \
pg_restore -U postgres \
-d citeck_eapps \
--no-owner --no-acl --role=citeck_eapps \
-j 4 /tmp/ecos_apps.dump
docker exec citeck_postgres_default rm /tmp/ecos_apps.dump
ecos_uiserv → citeck_uiserv:
docker cp ~/citeck-migration/postgres/ecos_uiserv.dump citeck_postgres_default:/tmp/
docker exec -i \
-e PGPASSWORD=postgres \
citeck_postgres_default \
psql -U postgres -d citeck_uiserv <<'SQL'
DO $$ DECLARE r RECORD; BEGIN
FOR r IN SELECT schema_name FROM information_schema.schemata
WHERE schema_name NOT IN ('pg_catalog','pg_toast','information_schema')
LOOP EXECUTE 'DROP SCHEMA IF EXISTS '||quote_ident(r.schema_name)||' CASCADE'; END LOOP;
END $$;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO citeck_uiserv;
SQL
docker exec \
-e PGPASSWORD=postgres \
citeck_postgres_default \
pg_restore -U postgres \
-d citeck_uiserv \
--no-owner --no-acl --role=citeck_uiserv \
-j 4 /tmp/ecos_uiserv.dump
docker exec citeck_postgres_default rm /tmp/ecos_uiserv.dump
ecos_integrations → citeck_integrations:
docker cp ~/citeck-migration/postgres/ecos_integrations.dump citeck_postgres_default:/tmp/
docker exec -i \
-e PGPASSWORD=postgres \
citeck_postgres_default \
psql -U postgres -d citeck_integrations <<'SQL'
DO $$ DECLARE r RECORD; BEGIN
FOR r IN SELECT schema_name FROM information_schema.schemata
WHERE schema_name NOT IN ('pg_catalog','pg_toast','information_schema')
LOOP EXECUTE 'DROP SCHEMA IF EXISTS '||quote_ident(r.schema_name)||' CASCADE'; END LOOP;
END $$;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO citeck_integrations;
SQL
docker exec \
-e PGPASSWORD=postgres \
citeck_postgres_default \
pg_restore -U postgres \
-d citeck_integrations \
--no-owner --no-acl --role=citeck_integrations \
-j 4 /tmp/ecos_integrations.dump
docker exec citeck_postgres_default rm /tmp/ecos_integrations.dump
ecos_model → citeck_emodel:
docker cp ~/citeck-migration/postgres/ecos_model.dump citeck_postgres_default:/tmp/
docker exec -i \
-e PGPASSWORD=postgres \
citeck_postgres_default \
psql -U postgres -d citeck_emodel <<'SQL'
DO $$ DECLARE r RECORD; BEGIN
FOR r IN SELECT schema_name FROM information_schema.schemata
WHERE schema_name NOT IN ('pg_catalog','pg_toast','information_schema')
LOOP EXECUTE 'DROP SCHEMA IF EXISTS '||quote_ident(r.schema_name)||' CASCADE'; END LOOP;
END $$;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO citeck_emodel;
SQL
docker exec \
-e PGPASSWORD=postgres \
citeck_postgres_default \
pg_restore -U postgres \
-d citeck_emodel \
--no-owner --no-acl --role=citeck_emodel \
-j 4 /tmp/ecos_model.dump
docker exec citeck_postgres_default rm /tmp/ecos_model.dump
ecos_notifications → citeck_notifications:
docker cp ~/citeck-migration/postgres/ecos_notifications.dump citeck_postgres_default:/tmp/
docker exec -i \
-e PGPASSWORD=postgres \
citeck_postgres_default \
psql -U postgres -d citeck_notifications <<'SQL'
DO $$ DECLARE r RECORD; BEGIN
FOR r IN SELECT schema_name FROM information_schema.schemata
WHERE schema_name NOT IN ('pg_catalog','pg_toast','information_schema')
LOOP EXECUTE 'DROP SCHEMA IF EXISTS '||quote_ident(r.schema_name)||' CASCADE'; END LOOP;
END $$;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO citeck_notifications;
SQL
docker exec \
-e PGPASSWORD=postgres \
citeck_postgres_default \
pg_restore -U postgres \
-d citeck_notifications \
--no-owner --no-acl --role=citeck_notifications \
-j 4 /tmp/ecos_notifications.dump
docker exec citeck_postgres_default rm /tmp/ecos_notifications.dump
ecos_history → citeck_history:
docker cp ~/citeck-migration/postgres/ecos_history.dump citeck_postgres_default:/tmp/
docker exec -i \
-e PGPASSWORD=postgres \
citeck_postgres_default \
psql -U postgres -d citeck_history <<'SQL'
DO $$ DECLARE r RECORD; BEGIN
FOR r IN SELECT schema_name FROM information_schema.schemata
WHERE schema_name NOT IN ('pg_catalog','pg_toast','information_schema')
LOOP EXECUTE 'DROP SCHEMA IF EXISTS '||quote_ident(r.schema_name)||' CASCADE'; END LOOP;
END $$;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO citeck_history;
SQL
docker exec \
-e PGPASSWORD=postgres \
citeck_postgres_default \
pg_restore -U postgres \
-d citeck_history \
--no-owner --no-acl --role=citeck_history \
-j 4 /tmp/ecos_history.dump
docker exec citeck_postgres_default rm /tmp/ecos_history.dump
ecos_process → citeck_eproc:
docker cp ~/citeck-migration/postgres/ecos_process.dump citeck_postgres_default:/tmp/
docker exec -i \
-e PGPASSWORD=postgres \
citeck_postgres_default \
psql -U postgres -d citeck_eproc <<'SQL'
DO $$ DECLARE r RECORD; BEGIN
FOR r IN SELECT schema_name FROM information_schema.schemata
WHERE schema_name NOT IN ('pg_catalog','pg_toast','information_schema')
LOOP EXECUTE 'DROP SCHEMA IF EXISTS '||quote_ident(r.schema_name)||' CASCADE'; END LOOP;
END $$;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO citeck_eproc;
SQL
docker exec \
-e PGPASSWORD=postgres \
citeck_postgres_default \
pg_restore -U postgres \
-d citeck_eproc \
--no-owner --no-acl --role=citeck_eproc \
-j 4 /tmp/ecos_process.dump
docker exec citeck_postgres_default rm /tmp/ecos_process.dump
ecos_camunda → citeck_camunda:
docker cp ~/citeck-migration/postgres/ecos_camunda.dump citeck_postgres_default:/tmp/
docker exec -i \
-e PGPASSWORD=postgres \
citeck_postgres_default \
psql -U postgres -d citeck_camunda <<'SQL'
DO $$ DECLARE r RECORD; BEGIN
FOR r IN SELECT schema_name FROM information_schema.schemata
WHERE schema_name NOT IN ('pg_catalog','pg_toast','information_schema')
LOOP EXECUTE 'DROP SCHEMA IF EXISTS '||quote_ident(r.schema_name)||' CASCADE'; END LOOP;
END $$;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO citeck_camunda;
SQL
docker exec \
-e PGPASSWORD=postgres \
citeck_postgres_default \
pg_restore -U postgres \
-d citeck_camunda \
--no-owner --no-acl --role=citeck_camunda \
-j 4 /tmp/ecos_camunda.dump
docker exec citeck_postgres_default rm /tmp/ecos_camunda.dump
ecos_edi → citeck_edi:
docker cp ~/citeck-migration/postgres/ecos_edi.dump citeck_postgres_default:/tmp/
docker exec -i \
-e PGPASSWORD=postgres \
citeck_postgres_default \
psql -U postgres -d citeck_edi <<'SQL'
DO $$ DECLARE r RECORD; BEGIN
FOR r IN SELECT schema_name FROM information_schema.schemata
WHERE schema_name NOT IN ('pg_catalog','pg_toast','information_schema')
LOOP EXECUTE 'DROP SCHEMA IF EXISTS '||quote_ident(r.schema_name)||' CASCADE'; END LOOP;
END $$;
CREATE SCHEMA public;
GRANT ALL ON SCHEMA public TO citeck_edi;
SQL
docker exec \
-e PGPASSWORD=postgres \
citeck_postgres_default \
pg_restore -U postgres \
-d citeck_edi \
--no-owner --no-acl --role=citeck_edi \
-j 4 /tmp/ecos_edi.dump
docker exec citeck_postgres_default rm /tmp/ecos_edi.dump
4.2. MongoDB
docker cp ~/citeck-migration/mongo/ecos-process.archive citeck_mongo_default:/tmp/
docker exec citeck_mongo_default mongorestore \
--username "$(docker exec citeck_mongo_default printenv MONGO_INITDB_ROOT_USERNAME)" \
--password "$(docker exec citeck_mongo_default printenv MONGO_INITDB_ROOT_PASSWORD)" \
--authenticationDatabase admin \
--nsFrom 'ecos-process.*' --nsTo 'citeck_eproc.*' \
--archive=/tmp/ecos-process.archive --gzip --drop
docker exec citeck_mongo_default rm /tmp/ecos-process.archive
Опции:
--nsFrom 'ecos-process.*' --nsTo 'citeck_eproc.*'– переименование namespace при restore.--drop– удаляет коллекции, оставшиеся от первого старта webappeproc.Username и пароль root читаются из ENV контейнера через
printenv, чтобы не зависеть от того, что launcher сгенерировал.
4.3. Zookeeper
Останавливаем zookeeper через citeck stop, а не docker stop. Это критично: launcher2 имеет reconciler, который через ~1 минуту перезапустит контейнер, остановленный «снаружи», прямо во время того, как мы пишем в данные. citeck stop помечает приложение как detached и reconciler оставляет его в покое.
Хранилище zookeeper в launcher2 – это bind-mount каталог на хосте (не named docker volume): /opt/citeck/data/runtime/default/volumes/zookeeper2/. Внутри него – подкаталог data/, в котором zookeeper держит snapshots, и datalog/, в котором держит transaction log. Bitnami же на source складывал и snapshots, и log в один каталог data/version-2/. При импорте нужно разделить файлы: log.* идут в datalog/version-2/, а всё остальное (snapshot.*, acceptedEpoch, currentEpoch и любые другие служебные файлы) – в data/version-2/.
ZK_DIR=/opt/citeck/data/runtime/default/volumes/zookeeper2
citeck stop zookeeper
# Очистить старые данные (они созданы launcher'ом при первом старте)
rm -rf $ZK_DIR/data/version-2 $ZK_DIR/datalog/version-2
mkdir -p $ZK_DIR/data/version-2 $ZK_DIR/datalog/version-2
# Распаковать дамп во временный каталог
TMP=$(mktemp -d)
tar -xzf ~/citeck-migration/zookeeper/version-2.tar.gz -C $TMP
# Сначала переносим log'и в datalog/, потом всё остальное в data/
mv $TMP/version-2/log.* $ZK_DIR/datalog/version-2/ 2>/dev/null || true
mv $TMP/version-2/* $ZK_DIR/data/version-2/
rm -rf $TMP
# Выставить владельца -- uid/gid пользователя zookeeper в official образе
chown -R 1001:1001 $ZK_DIR/data/version-2 $ZK_DIR/datalog/version-2
citeck start zookeeper
Замечания:
Точный путь bind-mount проверить через
docker inspect citeck_zookeeper_default --format '{{(index .Mounts 0).Source}}'.chown 1001:1001– uid/gid пользователяzookeeperв официальном образе. Без него запуск падает сpermission denied.Если файлы не разделены – official zookeeper падает с
SnapDirContentCheckException: Snapshot directory has log files.citeck start zookeeperre-attaches приложение, после чего reconciler снова им управляет.Проверить старт:
docker logs --tail 100 citeck_zookeeper_default
В логах должно быть
Snapshot loadedиSnapshotting:без ошибок.
4.4. Поднять webapp’ы обратно
Тот же список приложений, что был остановлен в шаге 1.3. Важно: в отличие от citeck stop (принимает любое число аргументов), citeck start принимает только один app за раз, поэтому запускаем циклом – без --detach, чтобы каждый старт дожидался RUNNING перед стартом следующего:
for app in <app1> <app2> <app3> ...; do
citeck start "$app"
done
Если запускать с --detach, возникает race condition: proxy запускается раньше, чем onlyoffice (или другая зависимость) успевает стать DNS-резолвимым, и падает с host not found in upstream "onlyoffice". Sequential-старт (без --detach) использует waitForDeps launcher2 и поднимает приложения в правильном порядке.
Цикл занимает 10–20 минут (каждый Java-webapp стартует 1–3 минуты). Прогресс параллельно можно смотреть через citeck status -w в другой сессии.
Liquibase каждого webapp при старте увидит актуальный changelog (source перед миграцией обновлён до 2026.1) – изменений schema не будет.
Этап 5. Проверка
5.1. Что должно работать
Логин в Web UI под
admin / <admin-пароль из шага 1.1>. Неadmin / adminисточника – пароль теперь сгенерирован launcher’ом.Списки записей в
eapps,eproc,uiservпоказывают данные источника.BPMN-процессы из
eprocзапускаются (mongo + postgres согласованы).В Keycloak присутствуют только пользователь
adminи сервисный аккаунтciteck.
5.2. Проверочные команды
citeck status
citeck health
docker exec -e PGPASSWORD=postgres citeck_postgres_default \
psql -U postgres -d citeck_eapps -c "\dn"
docker exec -e PGPASSWORD=postgres citeck_postgres_default \
psql -U postgres -d citeck_eapps -c "\dt+ *.*" | head
docker exec citeck_mongo_default mongo \
-u "$(docker exec citeck_mongo_default printenv MONGO_INITDB_ROOT_USERNAME)" \
-p "$(docker exec citeck_mongo_default printenv MONGO_INITDB_ROOT_PASSWORD)" \
--authenticationDatabase admin \
citeck_eproc --eval "db.getCollectionNames()"
docker exec citeck_zookeeper_default \
bash -c 'echo "ls /" | zkCli.sh -server localhost:2181'
citeck status должна показать все приложения в статусе RUNNING. citeck health – exit-код 0.
5.3. Что заведомо обнулилось
Пользователи и роли Keycloak источника.
In-flight сообщения RabbitMQ.
Логи nginx/proxy.
Кеш сертификатов Let’s Encrypt – будет перевыпущен при первом запросе.
Откат
Сбой на target до доставки данных
Переустановить с нуля:
citeck uninstall --delete-data
Затем пройти установку и подготовку target заново (этап 1).
Сбой после импорта
Повторить с нуля:
citeck uninstall --delete-data
Затем установка → подготовка target → импорт. Source уже на 2026.1, его трогать не нужно – дампы из ~/citeck-migration/ ещё валидны.
Сбой апгрейда source до 2026.1 (предусловие 1)
Восстановить из backup’а из шага 0:
Если backup делался через
tar–docker compose down, распаковатьtar,docker compose up -d.Если делался через
pg_dumpall+mongodump– следовать стандартной процедуре restore PostgreSQL/MongoDB.
Дампы из ~/citeck-migration/ сохранять минимум 1–2 недели после миграции – на случай поздно обнаруженных проблем.