Files
robotex/docs/Deployment.md
T
protokeyandClaude Opus 5.5 7b511d5d38 feature: архитектура v2 — api + очередь + worker, удержание окна браузера (#78)
- Сервис api (FastAPI, без Chrome): POST /v2/tasks, GET /v2/tasks/{id} (доступ по cookie
  сессии), DELETE /v2/sessions/current; /v1/solve — прежний контракт поверх очереди.
- Сервис worker (dramatiq + RabbitMQ): слоты браузера, окна сессий между задачами
  (browser_idle_timeout 0–120 мин, простой — TTL ключа в Redis), heartbeat.
- Маршрутизация задач сессии в персональную очередь воркера с открытым окном.
- Redis: задачи, результаты (RESULT_TTL_HOURS), окна, воркеры. Прокси и ключ 2captcha
  в Redis не сохраняются.
- Движок: AsyncStealthySession.run() — действия на текущей странице без навигации.
- Два образа (robotex-api, robotex-worker), compose с rabbitmq и redis,
  make up/down/logs/smoke, релиз собирает все образы.
- Тесты: api + worker в одном процессе (fakeredis), настоящий воркер dramatiq на
  StubBroker, браузерные тесты на новой схеме, smoke для запущенного стенда.
- Документация: README, API (v2), Deployment, Usage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:13:52 +04:00

8.8 KiB
Raw Blame History

Развёртывание в Docker

Быстрый старт

make up       # собрать образы и запустить api, worker, rabbitmq, redis
make smoke    # проверить: /health, /v1/solve, /v2/tasks, удержание окна
make logs     # логи api и worker
make down

API доступно на http://localhost:8000.

Сервисы

клиент ─▶ api ──▶ RabbitMQ ──▶ worker (Chrome + Xvfb) ──▶ Redis ◀── api
Сервис Образ Что делает
api robotex-api (Dockerfile.api, без Chrome) принимает задачи, ставит в очередь, отдаёт результаты из Redis; /v1/solve ждёт результат сам
worker robotex-worker (Dockerfile) dramatiq worker.actors: выполняет задачи в Chrome, держит окна сессий (browser_idle_timeout)
rabbitmq rabbitmq:4.1-alpine очереди: общая robotex и персональная robotex.worker.<id> у каждого воркера
redis redis:8.2-alpine задачи и результаты (TTL RESULT_TTL_HOURS), открытые окна (TTL = простой), heartbeat воркеров

Наружу открыт только порт api. Прокси и ключ 2captcha из запроса передаются только в сообщении очереди и в Redis не сохраняются.

Воркеров можно запустить несколько (docker compose up -d --scale worker=2): каждый берёт задачи из общей очереди, а задачи сессии с открытым окном api направляет в персональную очередь воркера, где это окно. Профили сессий лежат в volume воркера — воркеры на одном хосте делят его.

Что внутри образа worker

Компонент Зачем
python:3.12-slim-bookworm базовый образ, Python той же версии, что и в .venv
requirements.txt ставится из PyPI (папка wheels/ в образ не попадает — она под Ubuntu Noble и неполная)
Google Chrome stable (patchright install --with-deps chrome) сессия запускается с real_chrome=True → channel chrome
Xvfb (xvfb-run) браузер работает в headful-режиме (headless=False), нужен виртуальный дисплей 1920x1080x24
tini (ENTRYPOINT, PID 1) пробрасывает сигналы и подбирает осиротевшие процессы Chrome/Xvfb — без него копятся зомби-процессы
пользователь app (uid 1000) Chrome не запускается от root без --no-sandbox

Рабочая директория — /app/src. Воркер — один процесс dramatiq (--processes 1: открытые окна живут в его памяти), потоков WORKER_THREADS (по умолчанию 2 × BROWSER_SLOTS).

Данные и volume

/app/extra воркера вынесен в volume (extra локально, api-data в релизе):

  • extra/user_data_dir/<session_id> — профили браузера (cookies, local storage, fingerprint) по session_id;
  • extra/geoip_cache.json — кэш геолокации прокси.

Профили со временем накапливаются — чистить вручную:

docker compose exec worker sh -c 'rm -rf /app/extra/user_data_dir/*'

Настройки compose

  • shm_size: 2gb у worker — Chrome активно использует /dev/shm, дефолтных 64 МБ не хватает (вкладки падают с «Aw, Snap»).
  • API_PORT — порт api на хосте (по умолчанию 8000): API_PORT=8001 docker compose up -d.
  • WORKER_CPUS / WORKER_MEM_LIMIT — потолок ресурсов воркера (по умолчанию 4 CPU / 8 ГБ, swap выключен). При переборе памяти OOM-killer убивает процесс Chrome внутри контейнера, хост не страдает.
  • RABBITMQ_PASSWORD — пароль пользователя robotex в RabbitMQ (по умолчанию robotex; порт наружу не открыт). RabbitMQ запускается с consumer_timeout 4 ч: задача может выполняться дольше 30 минут по умолчанию.
  • stop_grace_period: 60s у worker — при остановке он доделывает текущие задачи и закрывает окна.
  • restart: unless-stopped.
  • .env подключается, если существует (шаблон — env.example).

Переменные окружения приложения

Переменная По умолчанию Описание
BROWSER_SLOTS 4 worker: сколько окон Chrome открыто одновременно — и выполняющих задачу, и удерживаемых. Задача без свободного слота ждёт в очереди
WORKER_THREADS 2 × BROWSER_SLOTS worker: потоков dramatiq
WORKER_ID hostname worker: имя воркера и его персональной очереди
RESULT_TTL_HOURS 24 сколько хранится результат задачи
V1_QUEUE_WAIT 30 api: сколько /v1/solve ждёт, пока воркер возьмёт задачу; дальше 503
NO_SLOTS_RETRY_AFTER 5 api: значение Retry-After (с) в ответе 503
RABBITMQ_URL amqp://guest:guest@localhost:5672/ api, worker; в compose задаётся сам
REDIS_URL redis://localhost:6379/0 api, worker; в compose задаётся сам
LOG_LEVEL INFO DEBUG — полный дамп запроса/ответа (секреты клиента маскируются)

Нагрузка

Замер: freelancehunt.com/projects за Cloudflare Turnstile, captcha_type: cloudflare, ожидание списка проектов, каждый запрос — новый профиль. Прокси — gost из профиля loadtest. Хост: 16 ядер, 31 ГБ.

Лимит Параллельно Успешно Медиана, с Пик CPU Пик RAM
4 CPU 1 1/1 5 3,5 ядра 0,5 ГБ
4 CPU 5 3/5 68 4,1 2,8 ГБ
4 CPU 10 4/10 229 4,2 6,0 ГБ
4 CPU 15 3/15 261 4,2 6,1 ГБ
12 CPU 3 3/3 8 7,2 1,4 ГБ
12 CPU 5 4/5 8 12,1 2,4 ГБ
12 CPU 10 9/10 23 12,3 4,7 ГБ

Выводы:

  • Узкое место — CPU. Один Chrome на прохождении Turnstile съедает 2–3,5 ядра. Когда процессора не хватает, время растёт в десятки раз, а Cloudflare перестаёт пропускать (на 4 CPU и 10 параллельных — 6 из 10 застряли на челлендже).
  • Память — около 0,5–0,6 ГБ на браузер.
  • Правило подбора: BROWSER_SLOTS ≈ число ядер, WORKER_MEM_LIMIT ≈ 0,6 ГБ × BROWSER_SLOTS + 1 ГБ. Пример: сервер 4 ядра / 8 ГБ → 3–4 слота. BROWSER_SLOTS задаётся в .env.
  • Удерживаемые окна (browser_idle_timeout) занимают слоты, но почти не тратят CPU.
  • Сверх BROWSER_SLOTS задачи v2 ждут в очереди, а /v1/solve сразу получает 503 с code: no_slots.

Замеры выше сделаны на v1 (один сервис, Chrome в api); схема нагрузки на Chrome с очередью та же.

Повторить замер:

API_PORT=8001 docker compose --profile loadtest up -d
python tests/loadtest.py --api http://127.0.0.1:8001 --levels 1 5 10

Параметры скрипта: --url, --selector, --captcha-type, --proxy, --levels. Каждый запрос создаёт новый профиль в extra/user_data_dir — после прогона их стоит почистить.

Reverse proxy

В проде контейнер ставится за Caddy (TLS). Пример Caddyfile:

api.example.com {
	reverse_proxy localhost:8000
}

Таймауты прокси для /v1/solve должны быть не меньше timeout запроса (по умолчанию 120 с); запросы v2 короткие.