- Postgres в compose; пакет db (SQLAlchemy async): clients, windows, requests. - Все эндпоинты, кроме /health, — только с ключом в X-API-Key, иначе 401; без выпущенных ключей доступа нет. В базе — только sha256 ключа. - Задача и окно сессии привязаны к клиенту: чужой ключ их не видит. - Воркер пишет журнал: окно (открыто, последнее обращение, закрыто) и задачу (в очереди, начало, конец, итог, данные без секретов). Сбой записи не роняет задачу. - python -m db.cli create|list|revoke|stats; make key-create/key-list/key-revoke/stats. - smoke и loadtest с ключом. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9dc2c0b619
commit
1a4bd8d672
34 files changed
+857
-81
No files matched your search
+7
-1
@@ -14,7 +14,13 @@ HTML и скриншот. Задачи выполняет сервис worker и
|
||||
живых воркеров; `workers` — по каждому `worker_id`, `browser_slots`, `free_slots`, `windows` (открытые окна),
|
||||
`running` (выполняемые задачи).
|
||||
|
||||
> `X-API-Key` пока не проверяется ([#4](https://redmine.sokolab.xyz/issues/4)).
|
||||
## Авторизация
|
||||
|
||||
Все эндпоинты, кроме `/health`, требуют ключ клиента в заголовке `X-API-Key`. Без ключа, с неверным или отозванным
|
||||
ключом — `401`. Ключи выпускает администратор (`make key-create name=<клиент>`, см. [Deployment.md](Deployment.md#клиенты-и-ключи)).
|
||||
|
||||
Задача и окно сессии видны только клиенту, который их создал: с другим ключом `GET /v2/tasks/{id}` даёт `404`, а
|
||||
чужое открытое окно не используется.
|
||||
|
||||
## Запрос
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ API доступно на `http://localhost:8000`.
|
||||
| `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 воркеров |
|
||||
| `postgres` | `postgres:17-alpine` | клиенты и API-ключи, журнал окон браузера и запросов |
|
||||
|
||||
Наружу открыт только порт `api`. Прокси и ключ 2captcha из запроса передаются только в сообщении очереди и в Redis
|
||||
не сохраняются.
|
||||
@@ -31,6 +32,37 @@ API доступно на `http://localhost:8000`.
|
||||
а задачи сессии с открытым окном api направляет в персональную очередь воркера, где это окно. Профили сессий лежат
|
||||
в volume воркера — воркеры на одном хосте делят его.
|
||||
|
||||
## Клиенты и ключи
|
||||
|
||||
Без ключей API никого не пускает. Ключ показывается один раз при выпуске, в базе хранится только sha256.
|
||||
|
||||
```bash
|
||||
make key-create name=acme # выпустить ключ клиенту
|
||||
make key-list # клиенты: начало ключа, активен или отозван, последнее обращение
|
||||
make key-revoke id=3 # отозвать
|
||||
```
|
||||
|
||||
На сервере без make: `docker compose exec api python -m db.cli create acme` (и `list`, `revoke <id>`, `stats`).
|
||||
|
||||
## Статистика использования
|
||||
|
||||
Воркер пишет в Postgres:
|
||||
|
||||
- `windows` — окно браузера: клиент, задача, открывшая окно, время открытия, последнего обращения и закрытия;
|
||||
- `requests` — задача: клиент, `v1`/`v2`, время постановки в очередь, начала и конца, итог (`status`, `code`,
|
||||
`duration_ms`), данные задачи без секретов (без логина и пароля прокси, ключа 2captcha, вводимых значений и значений
|
||||
cookies);
|
||||
- `clients` — время создания клиента и последнего обращения.
|
||||
|
||||
Сводка по клиентам — число запросов, успешные, среднее и p95 время, окна и часы работы браузера, ошибки по `code`:
|
||||
|
||||
```bash
|
||||
make stats days=7
|
||||
```
|
||||
|
||||
Ошибка записи в журнал не роняет задачу — только предупреждение в логе воркера. Схема создаётся при старте api и
|
||||
воркера.
|
||||
|
||||
## Что внутри образа worker
|
||||
|
||||
| Компонент | Зачем |
|
||||
@@ -67,6 +99,7 @@ docker compose exec worker sh -c 'rm -rf /app/extra/user_data_dir/*'
|
||||
- `WORKER_CPUS` / `WORKER_MEM_LIMIT` — потолок ресурсов воркера (по умолчанию 4 CPU / 8 ГБ, swap выключен).
|
||||
При переборе памяти OOM-killer убивает процесс Chrome внутри контейнера, хост не страдает.
|
||||
- `RABBITMQ_PASSWORD` — пароль пользователя `robotex` в RabbitMQ (по умолчанию `robotex`; порт наружу не открыт).
|
||||
- `POSTGRES_PASSWORD` — пароль пользователя `robotex` в Postgres (по умолчанию `robotex`; порт наружу не открыт).
|
||||
RabbitMQ запускается с `consumer_timeout` 4 ч: задача может выполняться дольше 30 минут по умолчанию.
|
||||
- `stop_grace_period: 60s` у worker — при остановке он доделывает текущие задачи и закрывает окна.
|
||||
- `restart: unless-stopped`.
|
||||
@@ -85,6 +118,7 @@ docker compose exec worker sh -c 'rm -rf /app/extra/user_data_dir/*'
|
||||
| `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 задаётся сам |
|
||||
| `DATABASE_URL` | `postgresql+asyncpg://robotex:robotex@localhost:5432/robotex` | api, worker; в compose задаётся сам |
|
||||
| `LOG_LEVEL` | `INFO` | `DEBUG` — полный дамп запроса/ответа (секреты клиента маскируются) |
|
||||
|
||||
## Нагрузка
|
||||
|
||||
+2
-1
@@ -80,6 +80,7 @@ UA в ответе может измениться, когда на сервер
|
||||
| `challenge_failed` | антибот не пройден за отведённое время: повторить в той же сессии; при повторных неудачах — сменить прокси (и сессию) |
|
||||
| `action_failed` | упало действие: смотрите `error` и `actions[].result`; для отладки повторите с `"screenshot": true` |
|
||||
| `request_failed` | навигация не удалась: обычно прокси недоступен или сайт не отвечает |
|
||||
| `401` | нет ключа в `X-API-Key`, ключ неверный или отозван |
|
||||
| `422` | ошибка в запросе: описание в `detail` |
|
||||
|
||||
Успех — строго `status == "ok"`.
|
||||
@@ -97,7 +98,7 @@ import httpx
|
||||
ROBOTEX = "https://app.robotex.sokolab.xyz"
|
||||
PROXY = "http://user:pass@host:port"
|
||||
|
||||
robotex = httpx.Client(base_url=ROBOTEX, timeout=300)
|
||||
robotex = httpx.Client(base_url=ROBOTEX, timeout=300, headers={"X-API-Key": "rbx_..."})
|
||||
|
||||
|
||||
def solve(url: str) -> dict:
|
||||
|
||||
Reference in new issue
Block a user