5.5 KiB
Развёртывание в Docker
Быстрый старт
docker compose up -d --build
docker compose logs -f api
API доступно на http://localhost:8000 (POST /v1/solve).
Без compose:
docker build -t robotex-api .
docker run -d --name robotex -p 8000:8000 --shm-size=2g -v robotex-extra:/app/extra robotex-api
Что внутри образа
| Компонент | Зачем |
|---|---|
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, uvicorn запускается как uvicorn main:app (импорты в проекте вида from api import ...).
Данные и volume
/app/extra вынесен в volume extra:
extra/user_data_dir/<session_id>— профили браузера (cookies, local storage, fingerprint) поsession_id;extra/geoip_cache.json— кэш геолокации прокси.
Профили со временем накапливаются — чистить вручную:
docker compose exec api sh -c 'rm -rf /app/extra/user_data_dir/*'
Настройки compose
shm_size: 2gb— Chrome активно использует/dev/shm, дефолтных 64 МБ не хватает (вкладки падают с «Aw, Snap»).API_PORT— порт на хосте (по умолчанию 8000):API_PORT=8001 docker compose up -d.API_CPUS/API_MEM_LIMIT— потолок ресурсов контейнера (по умолчанию 4 CPU / 8 ГБ, swap выключен). При переборе памяти OOM-killer убивает процесс Chrome внутри контейнера, хост не страдает.restart: unless-stopped..envподключается, если существует (шаблон —env.example).
Переменные окружения приложения
| Переменная | По умолчанию | Описание |
|---|---|---|
BROWSER_SLOTS |
10 |
сколько Chrome работает параллельно; сверх — 503 |
NO_SLOTS_RETRY_AFTER |
5 |
значение Retry-After (с) в ответе 503 |
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 ≈ число ядер,API_MEM_LIMIT ≈ 0,6 ГБ × BROWSER_SLOTS + 1 ГБ. Пример: сервер 4 ядра / 8 ГБ → 3–4 слота.BROWSER_SLOTSзадаётся в.env(по умолчанию 10 — на ~10–12 ядер). - Сверх
BROWSER_SLOTSзапросы сразу получают503сcode: no_slots, не ставятся в очередь.
Повторить замер:
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
}
Таймауты прокси должны быть не меньше timeout запроса (по умолчанию 120 с).