Перейти к содержимому
falang.ruEN

Локальный запуск

Workflow-движок распространяется как open source, и его можно полностью запустить локально — это самый быстрый способ его попробовать. Понадобятся установленные Docker и kind (Kubernetes in Docker): каждый запущенный сценарий выполняется в собственном поде локального k8s-кластера — тем же механизмом, что и в боевом окружении.

  1. Создайте локальный kind-кластер:

    ./scripts/kind-dev-setup.sh
    Скрипт идемпотентен — его можно перезапускать повторно, например после изменений в коде раннера, чтобы пересобрать и перезагрузить его образ в кластер.

  2. Скопируйте .env.example в .env в корне репозитория. Для чистого локального запуска можно ничего не менять — у каждого секрета (JWT_SECRET, CREDENTIALS_ENCRYPTION_KEY и т.д.) есть значение по умолчанию для разработки (dev-compose также задаёт SEED_DEFAULT_ADMIN=true; сам backend по умолчанию использует false). При NODE_ENV=production backend не запустится, пока JWT_SECRET, DB_PASSWORD и CREDENTIALS_ENCRYPTION_KEY не заданы нестандартными значениями длиной не менее 32 символов.

  3. Поднимите стек:

    docker compose -f docker-compose.workflow.yml up –build

  4. Откройте http://localhost:5175 и войдите как admin/admin (учётная запись создаётся автоматически при первом запуске backend’а) либо зарегистрируйте новый аккаунт со страницы входа.

  5. Создайте проект, затем в нём — папку и документ типа «Функция»: задайте имя и тело функции.
  6. Нажмите «Build & Run» на панели инструментов. Это скомпилирует все функции проекта и создаст под в kind-кластере для их выполнения — под очередью задач вида workflow-dev-<projectId> (её имя отображается на панели инструментов рядом с индикатором статуса, а также командой kubectl -n workflow get pods).

  7. Откройте функцию и выберите Тестовый стенд → Запустить функцию на панели инструментов (если у функции есть параметры, их спросят): иконка, на которой идёт выполнение, подсвечивается на холсте, а боковая панель показывает статус и историю. Запустить выполнение можно и через CLI администратора Temporal:

    docker compose -f docker-compose.workflow.yml exec temporal-admin-tools \
    temporal workflow start --task-queue workflow-dev-<projectId> --type <functionName> \
    --workflow-id demo-1 --input '{}'

    Ход выполнения можно наблюдать в Temporal Web UI: http://localhost:8080.

  8. Кнопка «Publish» переводит текущий код в стабильную версию (общую очередь задач workflow-<projectId>) — в модальном окне «Версии» доступны откат («Активировать») и остановка более старых версий («Остановить раннер»). Версия с меткой «Текущая» — та, на которую идут новые запуски и триггеры и которую поднимает «Запустить»; откат сохраняется после Стоп/Запуск и пока простаивающий раннер спит.

docker-compose.workflow.yml поднимает:

  • самостоятельно размещённый сервер Temporal (версия 1.32, с собственной базой Postgres, разовыми заданиями создания схемы и namespace default), его админ-CLI и веб-интерфейс;
  • отдельную базу Postgres для backend’а (проекты, папки, документы, пользователи);
  • backend — сервис, который хранит проекты, компилирует функции и управляет запуском/остановкой подов выполнения через Kubernetes API;

  • client — веб-редактор схем (приложение администратора открывается по адресу /admin);
  • MinIO — S3-совместимое объектное хранилище для файлов (консоль — http://localhost:9001);

  • media — сервис на ffmpeg для интеграции Медиа;
  • activepieces — вспомогательный сервис для части интеграций (см. Интеграции).

Каждый опубликованный сценарий выполняется не отдельным процессом внутри контейнера backend’а, а собственным подом в Kubernetes — именно поэтому нужен локальный kind-кластер.

По умолчанию (TEMPORAL_TENANT_ISOLATION=shared) все workflow проектов выполняются в единственном namespace default Temporal без авторизации — этого достаточно для локальной разработки и для экземпляра, где все проекты принадлежат доверенным людям. Runner-под исполняет код самого проекта, поэтому на экземпляре с несколькими пользователями включите режим per-project: у каждого проекта свой namespace (falang-<projectId>), runner-поды аутентифицируются короткоживущим JWT, действительным только для этого namespace, а межнеймспейсные команды на сервере выключены. Запуск (ключ нужно держать одним и тем же между запусками):

export TEMPORAL_JWT_PRIVATE_KEY="$(openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048)"
export PROJECT_TOKEN_SECRET="$(openssl rand -hex 32)"
TEMPORAL_SERVER_CONFIG=server.yaml TEMPORAL_TENANT_ISOLATION=per-project \
docker compose -f docker-compose.workflow.yml up --build

Публичный ключ Temporal берёт у backend на внутреннем порту (/internal/temporal/jwks.json), поэтому для смены ключа достаточно перезапустить backend. В этом режиме веб-интерфейс Temporal на порту 8080 войти не сможет; используйте админ-CLI — он ходит во внутренний frontend (порт 7236, на хост не публикуется):

docker compose -f docker-compose.workflow.yml exec temporal-admin-tools \
temporal workflow list --namespace falang-<projectId>

Переменные backend’а, на которых держится этот режим:

  • TEMPORAL_TENANT_ISOLATION — shared (по умолчанию) или per-project. В режиме per-project backend не запустится без TEMPORAL_JWT_PRIVATE_KEY, а при старте проверит, что Temporal действительно отклоняет запросы без токена и с токеном, который он не может проверить (если нет — backend откажется стартовать: такая изоляция ничего не стоит).

  • TEMPORAL_JWT_PRIVATE_KEY — приватный RSA-ключ в формате PKCS#8 PEM (подпись RS256). Необязательные: TEMPORAL_JWT_KEY_ID (по умолчанию — отпечаток ключа), TEMPORAL_JWT_PREVIOUS_PUBLIC_KEY (прежний публичный ключ, публикуется рядом с новым на время ротации), TEMPORAL_JWT_TTL_SECONDS (срок жизни токена пода, по умолчанию 3600; поды обновляют его сами).

  • PROJECT_TOKEN_SECRET — ключ, из которого выводятся токены проектов, с которыми runner-поды ходят в backend (в production — не короче 32 символов и не значение по умолчанию; на всех репликах backend’а одно и то же).

  • TEMPORAL_NAMESPACE_RETENTION_DAYS — сколько хранятся завершённые выполнения в namespace проекта (по умолчанию 7). TEMPORAL_TLS=true — если frontend Temporal работает по TLS.

  • RUN_JOURNAL_RETENTION_DAYS_DEV / RUN_JOURNAL_RETENTION_DAYS_PROD — сколько дней журнал запусков хранит записи тестовых и опубликованных запусков (по умолчанию 14; журнал живёт дольше собственной истории запусков движка).

Namespace удалённого проекта backend удаляет примерно через сутки после удаления проекта. Переключение существующего экземпляра с shared на per-project не переносит старые выполнения из default; расписания, оставшиеся там, backend ставит на паузу, а переопубликованный проект начинает работать в своём namespace.

При самом первом запуске docker compose up после (пере)создания сети falang-workflow-network контейнер backend’а иногда не успевает подключиться ко второй сети (kind) — в его логах будут постоянные ошибки вида getaddrinfo EAI_AGAIN app-postgres. Лечится одной командой:

docker compose -f docker-compose.workflow.yml restart backend

Повторно эта проблема не возникает, пока обе сети не пересоздаются заново.

Backend слушает два порта. Публичный (PORT, в compose-файлах 4000) отдаёт API приложения — только его нужно публиковать через reverse proxy или ingress. Внутренний (INTERNAL_PORT, по умолчанию 3001) отдаёт только /internal/* — API для runner-подов, сервиса ActivePieces и медиа-сервиса; на публичном порту эти пути отвечают 404. Не открывайте внутренний порт в интернет: укажите его в BACKEND_INTERNAL_URL этих сервисов (внутри кластера — через ClusterIP Service).

LOG_FORMAT=json включает вывод логов backend по одному JSON-объекту на строку (уровень, время, контекст, сообщение и — внутри HTTP-запроса — requestId, то же значение приходит в ответе в заголовке x-request-id; входящий x-request-id до 128 безопасных символов используется как есть). По умолчанию — читаемый текст. LOG_LEVEL необязательно фильтрует: один уровень (warn — это warn и выше) или список через запятую. GET /internal/metrics на внутреннем порту отдаёт метрики Prometheus (процесс, Node.js, HTTP-запросы по шаблону маршрута, сборки, runner-поды, вызовы AI-агента); аутентификации нет, так что держите этот порт закрытым.