Локальная настройка Camunda для разработки¶
Инструкция по развёртыванию и отладке BPMN-процессов Planiqum на локальной машине.
Если что-то в этом документе противоречит актуальному состоянию кода, в первую очередь проверяйте
src/planiqum/camunda_workflow/service/run_listener.py,src/planiqum/camunda_workflow/launch_strategy/*.pyи переменные окруженияCAMUNDA_*/RABBIT_MQ_*в.env.
1. Необходимые сервисы¶
| Сервис | Контейнер | URL / адрес | Креды | Назначение |
|---|---|---|---|---|
| Camunda BPM Platform | camunda_app |
Web UI: http://localhost:8080/camunda · REST: http://localhost:8080/engine-rest |
demo / demo |
Оркестрация BPMN |
| Camunda DB (Postgres 12) | camunda_db |
localhost:5434 |
sa / sa, БД camunda |
Хранилище Camunda |
| RabbitMQ 3.12 + management | rabbitmq |
AMQP: localhost:5672 · UI: http://localhost:15672 |
admin / password |
Очереди message, run_workflow, close_task |
| Redis | planiqum_core_dev_redis |
localhost:6379 |
— | Celery + Planiqum кэш |
| Postgres основной | pg_container |
localhost:5432 |
из .env |
БД Planiqum |
Сервисы поднимаются через docker-compose-deb.yml:
cd <project_root>
docker compose -f docker-compose-deb.yml --env-file .env up -d camunda_db camunda rabbitmq
Проверка доступности:
curl -sS -o /dev/null -w "camunda rest: %{http_code}\n" http://localhost:8080/engine-rest/engine
curl -sS -o /dev/null -w "camunda ui : %{http_code}\n" http://localhost:8080/camunda/
curl -sS -o /dev/null -w "rabbit ui : %{http_code}\n" -u admin:password http://localhost:15672/api/overview
Все ответы должны быть 200.
2. Переменные окружения (.env)¶
CI_COMMIT_BRANCH=dev
CAMUNDA_HOST=http://localhost
CAMUNDA_PORT=8080
CAMUNDA_PORT_OUT=8080
CAMUNDA_DB_HOST=camunda_db
CAMUNDA_DB_PORT_IN=5432
CAMUNDA_DB_PORT_OUT=5434
CAMUNDA_DB_NAME=camunda
CAMUNDA_DB_USER=sa
CAMUNDA_DB_PASSWORD=sa
RABBIT_MQ_HOST=localhost
RABBIT_MQ_PORT=5672
RABBIT_MQ_USER=admin
RABBIT_MQ_PASSWORD=password
Флаг CAMUNDA_FRONTEND_SCRIPT_EXECUTION управляет тем, где исполняются скрипты,
запущенные через UI. Для BPMN-потока (Camunda → workflow-listener → RabbitMQ →
run_listener → run_worker) он не влияет.
3. Архитектура локального запуска¶
На локальной машине нет разделения «сервер приложений / сервер алгоритмов» (в проде оно есть, см. Workflow). Все компоненты запускаются на одном хосте в четырёх параллельных процессах:
# 0. Активация venv
cd <project_root>
source venv/bin/activate
# Терминал 1 — Django web
python manage.py runserver 0.0.0.0:8000
# Терминал 2 — Celery (если процесс или зависимые скрипты его используют)
celery -A planiqum.core worker -l info
# Терминал 3 — consumer RabbitMQ + спавнер run_worker (ОБЯЗАТЕЛЬНЫЙ)
python manage.py run_listener
# Терминал 4 — локальный listener Camunda External Tasks (ОБЯЗАТЕЛЬНЫЙ)
python infrastructure/scripts/local_workflow_listener.py
Терминалы 3 и 4 обязательны — без них Camunda будет крутиться вхолостую.
Что каждый процесс делает¶
-
workflow-listener (терминал 4) подписывается на топик
start_listener_process. Когда Camunda доходит до Service Task с этим топиком, скрипт скачивает BPMN-схему процесса, парсит её (parse_bpmn) и кладёт список задач в очередь RabbitMQrun_workflow. -
manage.py run_listener (терминал 3, код:
src/planiqum/camunda_workflow/service/run_listener.py) слушает очередиmessage,run_workflow,close_task. Получивrun_workflow, создаёт/обновляетWorkerControllerи для каждой External Task из списка спавнит отдельный subprocess:python manage.py run_worker <topic> --data <json> --not_run True --variant <variant>. -
manage.py run_worker через
ExecutionStrategyFactoryвыбирает стратегию (ComplexProcessдля флага--not_run=True), подписывается на топик своего Service Task, получает External Task, вызываетScript.execute(...)и закрывает задачу (task.complete()).
4. Контракт BPMN для Planiqum¶
Чтобы BPMN-процесс работал с существующим раннером, он должен соответствовать следующим требованиям.
4.1. Первый Service Task — входная точка¶
<bpmn:serviceTask id="ListenerEntry"
name="Start Listener Process"
camunda:type="external"
camunda:topic="start_listener_process"/>
Без этого таска процесс не попадёт в Planiqum — именно топик
start_listener_process слушает workflow-listener.
4.2. Алгоритмические Service Task¶
<bpmn:serviceTask id="MyTask"
name="My Algorithm"
camunda:type="external"
camunda:topic="my_algorithm_topic">
<bpmn:extensionElements>
<camunda:inputOutput>
<camunda:inputParameter name="script_natural_key">app_name__script_shortname</camunda:inputParameter>
<camunda:inputParameter name="variant">my_variant</camunda:inputParameter>
</camunda:inputOutput>
</bpmn:extensionElements>
</bpmn:serviceTask>
Имя топика = первый позиционный аргумент manage.py run_worker.
ComplexProcess подписывается на этот топик и внутри callback'а запускает
RunProcessSingle (одиночная задача) или RunThread (параллельная, если
is_parallel=true).
4.3. Переменные процесса¶
| Переменная | Кто читает | Назначение |
|---|---|---|
script_natural_key |
ComplexProcess.start_process_by_single |
Какой Script выполнять (формат: app_name__script_shortname). Проверить актуальные ключи: Script.objects.all().values('app_name', 'shortname'). |
variant |
ComplexProcess → Script.execute(variant=…) |
Ключ варианта скрипта. Может быть пустым или отсутствовать. |
is_test |
run_process(key, is_test=…) |
Тестовый прогон: ComplexProcess делает dry-run (закрывает таски без выполнения скриптов). |
group |
ComplexProcess.__get_group_ids |
Для параллельных задач — имя GroupTemplate, из которого берутся group_id для разветвления. |
4.4. Параллельный (multi-instance) subprocess¶
Для параллельного выполнения задач по группам используется subProcess с
multiInstanceLoopCharacteristics:
<bpmn:subProcess id="SubProcess_my_worker">
<bpmn:multiInstanceLoopCharacteristics
camunda:collection="ids" camunda:elementVariable="id" />
<bpmn:startEvent id="Event_start">
<bpmn:outgoing>Flow_1</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:serviceTask id="my_worker" name="My Worker"
camunda:type="external" camunda:topic="my_worker_topic">
<bpmn:extensionElements>
<camunda:inputOutput>
<camunda:inputParameter name="script_natural_key">app__script</camunda:inputParameter>
<camunda:inputParameter name="group">my_group_template</camunda:inputParameter>
</camunda:inputOutput>
</bpmn:extensionElements>
<bpmn:incoming>Flow_1</bpmn:incoming>
<bpmn:outgoing>Flow_2</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:endEvent id="Event_end">
<bpmn:incoming>Flow_2</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1" sourceRef="Event_start" targetRef="my_worker" />
<bpmn:sequenceFlow id="Flow_2" sourceRef="my_worker" targetRef="Event_end" />
</bpmn:subProcess>
group— имяGroupTemplate;ComplexProcess.__get_group_idsберёт из негоgroup_idдля каждой параллельной ветви.- Коллекция
idsформируется на предыдущем шаге черезtask.complete({'ids': groups})— флагis_next_parallelв парсере BPMN. WorkerParallelTaskавтоматически прокидывает вoptions:process_instance_id,group_id,filter_.
5. Деплой BPMN в Camunda¶
Автоматического деплоя из Planiqum нет. Доступные варианты:
A. Camunda Modeler¶
- Скачать Camunda Modeler.
- Открыть
.bpmn, в правом сайдбаре нажать Deploy current diagram → REST Endpointhttp://localhost:8080/engine-rest→ Deploy.
B. REST API¶
curl -X POST http://localhost:8080/engine-rest/deployment/create \
-F "deployment-name=my_process" \
-F "enable-duplicate-filtering=true" \
-F "deploy-changed-only=true" \
-F "data=@path/to/my_process.bpmn"
Проверить список процессов:
curl -s http://localhost:8080/engine-rest/process-definition | jq '.[] | {key, name, version}'
6. Запуск процесса¶
A. Из Django shell¶
from planiqum.camunda_workflow.service import CamundaFacade
CamundaFacade().start_process(
process_key="my_process_key", # = атрибут id у <bpmn:process>
variables={
"is_test": True, # dry-run
},
)
B. Через REST¶
curl -X POST http://localhost:8080/engine-rest/process-definition/key/my_process_key/start \
-H "Content-Type: application/json" \
-d '{"variables": {"is_test": {"value": true, "type": "Boolean"}}}'
C. Через run_process¶
from planiqum.camunda_workflow.tasks import run_process
run_process(key="my_process_key", is_test=True)
7. Отладка¶
7.1. Что смотреть в первую очередь¶
- Cockpit (
http://localhost:8080/camunda/app/cockpit/) — список running process instances, переменные, incident'ы. Если процесс «застрял», он висит на External Task, который никто не забрал. - External Tasks в Cockpit — видно, на каком топике лежит лок
(
start_listener_process→ не запущен workflow-listener; любой другой топик → не запущенmanage.py run_listenerилиrun_workerупал). - RabbitMQ UI (
http://localhost:15672) → вкладка Queues — должны бытьrun_workflow,message,close_task. Если в очереди висят необработанные сообщения — listener не читает (проверьте auth/host в.env). - Логи Docker:
docker logs -f camunda_app
docker logs -f rabbitmq
7.2. Частые ошибки¶
| Симптом | Причина | Решение |
|---|---|---|
External Task на start_listener_process висит бесконечно |
Не запущен workflow-listener | Запустить python infrastructure/scripts/local_workflow_listener.py |
Сообщения копятся в RabbitMQ run_workflow |
Не запущен manage.py run_listener |
Запустить его в отдельном терминале |
run_worker падает: Script.DoesNotExist |
Неверный script_natural_key в BPMN |
Проверить: Script.objects.all().values('app_name', 'shortname') |
run_worker падает без трейса в консоли |
Трейс в TaskResult.traceback |
TaskResult.objects.filter(task_id='<process_instance_id>').first().traceback |
| Multi-instance не разворачивается | Нет ids в переменных процесса |
Добавить fan-out Service Task перед subprocess (см. раздел 4.4) |
| Camunda очень медленная | Эмуляция amd64→arm64 на Apple Silicon | Ожидаемо для разработки; для нагрузки — собрать arm64-образ |
7.3. Сброс состояния¶
Очистить все instance'ы (не трогая deployments):
python manage.py clear_camunda
Удалить все deployments и instance'ы:
for d in $(curl -s http://localhost:8080/engine-rest/deployment | jq -r '.[].id'); do
curl -s -X DELETE "http://localhost:8080/engine-rest/deployment/$d?cascade=true"
done
Очистить очереди RabbitMQ:
curl -s -u admin:password -X DELETE http://localhost:15672/api/queues/%2F/run_workflow
curl -s -u admin:password -X DELETE http://localhost:15672/api/queues/%2F/message
curl -s -u admin:password -X DELETE http://localhost:15672/api/queues/%2F/close_task
8. Отличия локальной среды от прода¶
- Один хост vs два: в проде
manage.py run_listenerиrun_workerкрутятся на отдельном «сервере алгоритмов». Код тот же, разделение определяется тем, какая машина запускаетrun_listener. - Производительность: Camunda на amd64 через QEMU (Apple Silicon) нормальна для отладки, но не подходит для нагрузочных тестов.
- Креды RabbitMQ: docker-compose использует
admin/password, а не дефолтныеguest/guestизsettings.py. Значения должны быть в.env.
9. Полезные файлы¶
| Файл | Назначение |
|---|---|
src/planiqum/camunda_workflow/service/run_listener.py |
Consumer RabbitMQ, спавнер run_worker |
src/planiqum/camunda_workflow/service/run_process/parallel.py |
WorkerParallelTask, RunThread — параллельное выполнение |
src/planiqum/camunda_workflow/service/run_process/single.py |
RunProcessSingle — одиночный Service Task |
src/planiqum/camunda_workflow/launch_strategy/complex_process.py |
Стратегия для External Task с --not_run True |
src/planiqum/camunda_workflow/service/parser_bmp.py |
Парсер BPMN (camunda:topic, inputParameter, multiInstanceLoopCharacteristics) |
src/planiqum/camunda_workflow/service/camunda_facade.py |
Обёртка над engine-rest: start_process, fetch_and_lock, complete_task |
infrastructure/scripts/local_workflow_listener.py |
Локальная замена внешнего workflow-listener |
10. Чеклист перед запуском BPMN¶
- Docker-сервисы запущены (Camunda, RabbitMQ).
.envсодержитCAMUNDA_*иRABBIT_MQ_*(см. раздел 2).- Запущены:
manage.py run_listenerиlocal_workflow_listener.py. - BPMN задеплоен (см. раздел 5),
process_keyсовпадает сidв<bpmn:process>. - Первый Service Task —
camunda:topic="start_listener_process". - Каждый Service Task имеет
script_natural_keyвinputParameter. - Multi-instance subprocess имеет
camunda:collection="ids"и на предыдущем шаге выставляется коллекцияids.