Эфемерные раннеры
Эфемерный раннер в Mockarty — это процесс раннера, жизненный цикл которого ограничен одной CI-задачей (или фиксированным окном простоя). Раннер регистрируется, берёт работу, потом завершается — и координатор сразу удаляет строку раннера из таблицы вместо того, чтобы оставлять её в статусе offline до следующей фоновой проверки.
Используйте эфемерные раннеры, когда:
- Раннер живёт внутри CI-контейнера (GitLab job, GitHub Actions container job, Jenkins agent), который исчезает по окончании пайплайна.
- Вы поднимаете раннеры по требованию в Kubernetes (HPA, Job, KEDA) и не хотите, чтобы в admin UI копились «мёртвые» строки после завершения подов.
- Хотите, чтобы CI-шаг чисто завершался через N секунд простоя — пусть раннер сам уйдёт, когда у него нет работы.
Долгоживущие раннеры (поднял один раз и забыл) сохраняют поведение по умолчанию — никакой опции включать не нужно.
Быстрый старт
Передайте EPHEMERAL=true при запуске раннера. По желанию добавьте IDLE_TIMEOUT, чтобы процесс завершился, когда работы нет.
Перед примерами задайте shell- или CI-переменную MOCKARTY_RUNNER_IMAGE точной неизменяемой ссылкой repository@sha256:... на образ раннера из проверенного release set. Это переменная примера, а не настройка сервера Mockarty. В air-gapped установке сначала перенесите образ в приватный registry; не подставляйте изменяемый тег latest.
Docker run
docker run --rm \
-e API_TOKEN=mki_your_integration_token \
-e COORDINATOR_ADDR=mockarty.example.com:5773 \
-e RUNNER_NAME=ci-runner-${CI_JOB_ID} \
-e LABELS=ci,gitlab,linux \
-e EPHEMERAL=true \
-e IDLE_TIMEOUT=120s \
"$MOCKARTY_RUNNER_IMAGE"
Когда раннер закончит свои задачи (нет активных задач в течение 120 секунд), он чисто разрегистрируется и контейнер завершится. Строка раннера исчезнет из списка при следующем обновлении.
Docker Compose (одноразовый CI-помощник)
services:
runner:
image: ${MOCKARTY_RUNNER_IMAGE:?задайте неизменяемый repository@sha256 digest}
restart: "no"
environment:
API_TOKEN: ${MOCKARTY_INTEGRATION_TOKEN}
COORDINATOR_ADDR: ${MOCKARTY_COORDINATOR_ADDR}
RUNNER_NAME: ci-${CI_PIPELINE_ID}-${CI_JOB_ID}
LABELS: ci,gitlab,${CI_RUNNER_TAGS}
EPHEMERAL: "true"
IDLE_TIMEOUT: 90s
GitLab .gitlab-ci.yml
Зеркалируйте и зафиксируйте Docker CLI и DinD service в том же проверенном CI release set; замените оба digest placeholder в примере ниже.
performance-test:
image: <PRIVATE_REGISTRY>/docker-cli@sha256:<DOCKER_CLI_IMAGE_DIGEST>
services:
- name: <PRIVATE_REGISTRY>/docker-dind@sha256:<DOCKER_DIND_IMAGE_DIGEST>
variables:
MOCKARTY_INTEGRATION_TOKEN: $MOCKARTY_RUNNER_TOKEN
script:
- |
docker run --rm \
-e API_TOKEN=$MOCKARTY_INTEGRATION_TOKEN \
-e COORDINATOR_ADDR=mockarty.example.com:5773 \
-e RUNNER_NAME=gitlab-$CI_PROJECT_NAME-$CI_JOB_ID \
-e LABELS=ci,gitlab \
-e EPHEMERAL=true \
-e IDLE_TIMEOUT=300s \
"$MOCKARTY_RUNNER_IMAGE"
Kubernetes Job
apiVersion: batch/v1
kind: Job
metadata:
generateName: mockarty-runner-
spec:
ttlSecondsAfterFinished: 60
template:
spec:
restartPolicy: Never
containers:
- name: runner
image: <PRIVATE_REGISTRY>/mockarty-runner@sha256:<RUNNER_IMAGE_DIGEST>
env:
- name: API_TOKEN
valueFrom:
secretKeyRef: { name: mockarty-runner, key: token }
- name: COORDINATOR_ADDR
value: mockarty.example.com:5773
- name: EPHEMERAL
value: "true"
- name: IDLE_TIMEOUT
value: "180s"
- name: LABELS
value: "ci,k8s,linux"
Режимы «одна задача» и «слив» (--once / --drain)
Помимо окна ожидания у общего раннера (mockarty-runner) есть два явных режима CI-жизненного цикла. Их передают флагами командной строки (флаги главнее переменных окружения) или задают переменными:
| Режим | Флаг | Переменные (каноническая / устаревшая) | Что делает раннер |
|---|---|---|---|
| Одна задача | --once |
MOCKARTY_RUNNER_ONCE=true / RUNNER_ONCE=true |
Берёт ровно одну задачу, дожидается её завершения, разрегистрируется, выходит с кодом 0. |
| Слив | --drain |
MOCKARTY_RUNNER_DRAIN=true / RUNNER_DRAIN=true |
Не берёт ни одной новой задачи, дожидается всего, что уже исполняется в этом процессе, разрегистрируется, выходит с кодом 0. |
# CI-контейнер, который выполняет ровно одну задачу и исчезает:
mockarty-runner --once
# Мягкая остановка, при которой раннер не должен взять ещё одну задачу:
mockarty-runner --drain
Режимы взаимоисключающие: --once --drain (или обе переменные сразу) — это ошибка конфигурации при старте, а не молчаливый выбор одного из них.
Сочетайте --once с CLAIM_TOKENS, когда CI-триггер поднимает раннер под один конкретный запуск: раннер заберёт ровно задачу с этим токеном, выполнит её и завершится.
Security-раннер (mockarty-redteam-runner) поддерживает те же контракты: --once (эквивалент --max-tasks=1) и --drain (отказывает новым задачам по dispatch/lease и дожидается своих текущих).
Что происходит при разрегистрации
Когда EPHEMERAL=true:
- Раннер вызывает DeregisterRunner при graceful shutdown (SIGTERM, истечение
IDLE_TIMEOUTили штатное завершениеDRAIN_TIMEOUT). - Координатор удаляет строку из
runner_agents(вместо перевода вstatus=offline). - Список раннеров в admin UI больше не показывает этот раннер — никаких ручных чисток, никаких «призраков».
Если разрегистрация не удалась (координатор недоступен, сетевая ошибка), раннер пытается ещё до трёх раз с задержкой в одну секунду, прежде чем сдаться. Оставшиеся «осиротевшие» строки подберёт leader-only sweeper, который работает каждые 60 секунд на админ-ноде и удаляет эфемерные строки, чей последний heartbeat старше трёх минут. То есть даже жёсткое падение (kill -9, OOM, отказ узла) рано или поздно приведёт к очистке.
Долгоживущий (не ephemeral) раннер списан, но всё ещё виден в списке.
- Удалите его явно:
DELETE /api/v1/runners/<id>переводит строку в offline (повторное
подключение с тем же токеном оживит её), аDELETE /api/v1/runners/<id>?purge=true
удаляет строку целиком. Нужно право integrations write. Живой раннер с активным
heartbeat просто появится снова — purge предназначен для раннеров, которых больше нет.
Переменные окружения
| Переменная | По умолчанию | Описание |
|---|---|---|
EPHEMERAL |
false |
Если true, помечает раннер как эфемерный. Разрегистрация становится hard-delete; leader-only sweeper подбирает упущенные строки через три минуты. |
IDLE_TIMEOUT |
0s |
Если > 0 и EPHEMERAL=true, раннер чисто завершится после указанного времени простоя (нет активных задач). Удобно для одноразовых CI-задач. Принимает Go duration (30s, 2m, 1h) или просто число — секунды. |
MOCKARTY_EPHEMERAL_SWEEP_INTERVAL |
60s |
(на стороне админ-ноды) Как часто leader-only sweeper проверяет устаревшие эфемерные строки. Уменьшайте только в тестах; в проде менять не нужно. |
MOCKARTY_EPHEMERAL_SWEEP_STALE_AFTER |
3m |
(на стороне админ-ноды) Сколько должен «молчать» heartbeat, чтобы sweeper счёл строку устаревшей. Увеличивайте только если у вас реально большой джиттер сети. |
Что нужно знать
- Заголовок
X-Mockarty-Runner-Instanceобязателен на каждом запросе от эфемерного раннера. Клиент Mockarty-раннера ставит его автоматически (UUID на процесс) — оператору ничего делать не нужно. - Метки остаются полезными и для эфемерных раннеров — комбинируйте
EPHEMERAL=trueсLABELS=ci,gitlab, и ваши CI-пайплайны смогут целиться в нужные label-выражения (например,ci & gitlab & !staging) без загрязнения долгоживущего флота.
Pull-режим — раннеры на машинах команды (только исходящие)
Раннеру не нужен входящий сетевой доступ. По умолчанию он работает в pull-режиме: сам дозванивается до админ-ноды, длинным опросом забирает задачу, выполняет её и отправляет результат обратно — каждое соединение идёт раннер → админ. Это именно то, что нужно на ноутбуке разработчика или CI-воркере за NAT/файрволом: открыт только исходящий доступ к админу, ничего входящего. Член команды может поднять раннер и взять на себя браузерные тесты, шарды нагрузки, мобильные тесты или сканы безопасности, не открывая ни одного порта.
Единственные две обязательные настройки — URL админа и токен раннера:
docker run --rm \
-e MOCKARTY_ADMIN_URL=https://mockarty.example.com \
-e MOCKARTY_RUNNER_TOKEN=mki_ваш_интеграционный_токен \
-e MOCKARTY_RUNNER_NAMESPACES=team-a \
-e MOCKARTY_RUNNER_LABELS=region=eu,env=dev \
"$MOCKARTY_RUNNER_IMAGE"
Этого достаточно — pull-режим стоит по умолчанию, флаг транспорта указывать не нужно. Токен выпускается в Админ → Интеграции: создайте интеграцию типа Тестовый раннер и скопируйте её токен.
Общие настройки (одни и те же имена работают для всех типов раннеров):
| Переменная | По умолчанию | Описание |
|---|---|---|
MOCKARTY_ADMIN_URL |
— | Базовый URL админа, к которому дозванивается раннер (https). Только исходящие. |
MOCKARTY_RUNNER_TOKEN |
— | Интеграционный токен; одновременно идентичность раннера. |
MOCKARTY_RUNNER_MODE |
pull |
pull (исходящий long-poll, по умолчанию) — указывайте, только если осознанно нужен другой транспорт. |
MOCKARTY_RUNNER_INSTANCE |
на процесс | Различает несколько раннеров с одним токеном. |
MOCKARTY_RUNNER_NAMESPACES |
— | Пространства имён через запятую, которые обслуживает раннер. |
MOCKARTY_RUNNER_LABELS |
— | Метки-селекторы ключ=значение через запятую (region=eu,env=dev). |
MOCKARTY_RUNNER_MAX_CONCURRENT |
по раннеру | Потолок параллелизма. |
Каждый тип раннера — та же картина со своим образом и парой специфичных ручек:
- Обычный раннер (API/функциональные/нагрузка) — неизменяемый образ из
MOCKARTY_RUNNER_IMAGEв примере выше. - Браузерный UI-раннер — образ browser-runner; capability
ui-testобъявляется автоматически. - Мобильный раннер — образ mobile-runner; добавьте
RUNNER_PLATFORM=androidи подключите устройство/эмулятор. Живой стрим экрана используетMOCKARTY_STREAM_MODE=relay(по умолчанию) — кадры идут через админ, поэтому стрим виден даже без входящей доступности раннера (WebRTC/TURN не нужны).MOCKARTY_STREAM_MODE=webrtcставьте только в сети, где p2p-медиа способно соединиться. - Раннеры безопасности (сканеры) — образы redteam/exploit-runner; pull по умолчанию, callback-URL не нужен.
Сетевое требование: разрешите раннеру исходящие к админу (HTTPS). Входящее правило к раннеру в pull-режиме не требуется никогда.
Что видно в admin UI
- Раннер с бейджем
ephemeralв списке, пока он жив. - Строка исчезает, как только раннер разрегистрируется (или sweeper подберёт её через три минуты после последнего heartbeat).
- История задач этого раннера остаётся в истории прогонов (Test Plans, Performance Tests) — результаты задач не зависят от строки раннера.
Решение проблем
Строка раннера не исчезает после завершения контейнера.
- Посмотрите логи координатора на предупреждения
Ephemeral runner DELETE failed. Самая частая причина — рестарт координатора между последней попыткой раннера разрегистрироваться и следующим тиком sweeper’а. - Подождите три минуты. Sweeper работает каждые 60 секунд и убирает устаревшие эфемерные строки, чей heartbeat старше порога
MOCKARTY_EPHEMERAL_SWEEP_STALE_AFTER(по умолчанию 3 минуты). - Убедитесь, что
EPHEMERAL=trueдействительно применился. Стартовая запись лога раннера содержитephemeral=true, когда флаг включён.
Раннер завершается раньше, чем закончил работу.
- Уменьшайте
IDLE_TIMEOUTтолько если знаете, что раннер получит хотя бы одну задачу за это время. Типичный паттерн:IDLE_TIMEOUT=300s(5 минут) — достаточно, чтобы предыдущий шаг CI-пайплайна положил задачу в очередь, и достаточно мало, чтобы зависшие контейнеры исчезали. IDLE_TIMEOUT=0s(по умолчанию) полностью отключает idle-watcher — раннер работает, пока его не завершит SIGTERM или родительский процесс.
Разрегистрация прошла, но строка остаётся ещё 3 минуты.
- Это путь «страховка» (ошибка разрегистрации провалилась к sweeper’у). Во время этого окна строка безвредна — в in-memory registry раннера уже нет, и задачи на него не назначаются. Окно можно сократить через
MOCKARTY_EPHEMERAL_SWEEP_STALE_AFTER, если ваши дашборды требуют чистого состояния.
Раннер подключился, но не берёт UI- или мобильные задачи.
-
Запустите встроенную проверку готовности на этой машине — ей не нужно подключение к админ-ноде, и она ничего не меняет:
mockarty-runner doctorОна печатает честный отчёт о том, что ИМЕННО эта машина умеет обслуживать:
✓ ready— работает сейчас,◐ auto— доустановится при первом использовании (лёгкий движок браузера ставится автоматически),✗ needs action— называет, чего не хватает (например,adb not found). -
Capabilities включаются сами. Имея лишь токен и URL админ-ноды, раннер зондирует хост и рекламирует каждую capability, которую реально может обслужить — руками ничего поддерживать не нужно. В частности:
- Мобильный (Android) авто-включается, когда подключено устройство по adb (считаются только авторизованные устройства). Переопределите через
RUNNER_MOBILE=true, чтобы эфемерный раннер ждал устройство, илиRUNNER_MOBILE=false, чтобы отключить на CI-машине без телефона. RUNNER_BROWSERS=trueзапрашивает настоящие браузеры: раннер сканирует уже установленные браузеры Playwright и предлагает каждый из них рядом с всегда доступным лёгким движком (он скачивается при первом использовании). Точный контроль остаётся черезRUNNER_UI_ENGINES.
- Мобильный (Android) авто-включается, когда подключено устройство по adb (считаются только авторизованные устройства). Переопределите через