api и worker компилируются Nuitka (--mode=standalone) в builder-стадии; в финальном образе — только скомпилированный бинарник и системные библиотеки, без исходников, pip и build-инструментов. api/runtime — debian:bookworm-slim без Python вообще; worker/runtime — python:3.12-slim-bookworm, но Python там нужен только на шаг установки Chrome. db.cli объединён с сервером в один бинарник (entry_api.bin dbcli ...). Заодно убрано 51 неиспользуемый пакет из requirements.txt (camoufox, PySide6, moz*- стек и др., подтверждено графом зависимостей) — он давал больше веса, чем сама компиляция. make build offline=1 ставит python-зависимости из wheels/ без PyPI. make test-browser адаптирован под новую сборку: pytest не может подключиться к internals скомпилированного бинарника, поэтому гоняется против отдельной стадии test (Chrome из runtime + исходники как обычный интерпретируемый Python). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
99 lines
6.1 KiB
Docker
99 lines
6.1 KiB
Docker
# Сервис worker (#78): выполняет задачи из очереди в Google Chrome под Xvfb.
|
||
#
|
||
# builder: компилирует src/ (через entry_worker.py) в самодостаточный standalone-бинарник Nuitka —
|
||
# в финальный образ не попадают ни исходники .py, ни gcc/pip, ни сам requirements.txt.
|
||
FROM python:3.12-slim-bookworm AS builder
|
||
|
||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||
PIP_NO_CACHE_DIR=1 \
|
||
PIP_DISABLE_PIP_VERSION_CHECK=1
|
||
|
||
# build-essential (gcc + libc6-dev — заголовки Python.h тянут stdlib.h/limits.h) — компилятор для
|
||
# сгенерированного Nuitka C-кода; patchelf — правит RPATH бинарника в standalone-режиме;
|
||
# ccache — ускоряет повторные сборки при пересборке образа
|
||
RUN apt-get update \
|
||
&& apt-get install -y --no-install-recommends build-essential patchelf ccache \
|
||
&& rm -rf /var/lib/apt/lists/*
|
||
|
||
# OFFLINE=1: ставить зависимости только из wheels/ (make wheels), без обращения к PyPI — см. docs/Deployment.md
|
||
ARG OFFLINE=0
|
||
WORKDIR /build
|
||
COPY requirements.txt requirements-build.txt ./
|
||
COPY wheels/ ./wheels/
|
||
RUN set -e; \
|
||
if [ "$OFFLINE" = "1" ]; then PIP_OPTS="--no-index --find-links=./wheels"; else PIP_OPTS=""; fi; \
|
||
pip install $PIP_OPTS -r requirements.txt -r requirements-build.txt
|
||
|
||
COPY src/ ./src/
|
||
|
||
# standalone: entry_worker.dist/ — изолированная папка со своим Python-рантаймом и всеми пакетами, которые
|
||
# реально нужны воркеру (patchright, playwright, browserforge, dramatiq, db, tasks, engine, antibot, captcha,
|
||
# полный api/ — см. worker/windows.py). worker.actors попадает в сборку только из-за явного import в
|
||
# entry_worker.py: dramatiq грузит его по строке "worker.actors" динамически, статика Nuitka это не видит
|
||
# --include-package-data: Nuitka копирует только код, а patchright/browserforge везут с собой
|
||
# небольшой код + большие данные не-.py (драйвер patchright — Node.js бинарник и JS, browserforge и
|
||
# apify_fingerprint_datapoints — zip/json датасеты отпечатков), без них падают с FileNotFoundError в runtime
|
||
RUN python -m nuitka --mode=standalone \
|
||
--include-package=worker --include-package=engine --include-package=antibot --include-package=captcha \
|
||
--include-package=db --include-package=tasks --include-package=api \
|
||
--include-package-data=patchright --include-package-data=browserforge \
|
||
--include-package-data=apify_fingerprint_datapoints \
|
||
--include-data-files=src/api/js/canvas_noise.js=api/js/canvas_noise.js \
|
||
--output-dir=/build/dist \
|
||
src/entry_worker.py
|
||
|
||
# runtime: Python тут остаётся — он нужен только на один шаг (установка Chrome через `patchright install`,
|
||
# тот же механизм, что и раньше), а дальше запускается готовый standalone-бинарник, а не исходники из
|
||
# requirements.txt. Полный интерпретатор здесь — осознанный компромисс: переносить apt-зависимости Chrome
|
||
# между стадиями вручную хрупко (patchright сам решает, что ставить под конкретную версию Chrome).
|
||
FROM python:3.12-slim-bookworm AS runtime
|
||
|
||
ENV PIP_NO_CACHE_DIR=1 \
|
||
PIP_DISABLE_PIP_VERSION_CHECK=1
|
||
|
||
# Xvfb — браузер запускается в headful-режиме (headless=False), нужен виртуальный дисплей
|
||
RUN apt-get update \
|
||
&& apt-get install -y --no-install-recommends tini xvfb xauth fonts-liberation fonts-noto-color-emoji ca-certificates \
|
||
&& rm -rf /var/lib/apt/lists/*
|
||
|
||
# real_chrome=True → channel "chrome": ставим Google Chrome stable вместе с системными зависимостями.
|
||
# Версия patchright — как в requirements.txt (#109): её CLI и сам пакет worker'у в runtime не нужны,
|
||
# только Chrome и его системные библиотеки, которые он поставит через apt
|
||
ARG OFFLINE=0
|
||
COPY wheels/ /tmp/wheels/
|
||
RUN set -e; \
|
||
if [ "$OFFLINE" = "1" ]; then PIP_OPTS="--no-index --find-links=/tmp/wheels"; else PIP_OPTS=""; fi; \
|
||
pip install $PIP_OPTS patchright==1.63.0 \
|
||
&& patchright install --with-deps chrome \
|
||
&& pip uninstall -y patchright \
|
||
&& rm -rf /tmp/wheels /var/lib/apt/lists/* /root/.cache
|
||
|
||
RUN useradd --create-home --uid 1000 app \
|
||
&& mkdir -p /app/extra \
|
||
&& chown -R app:app /app
|
||
|
||
WORKDIR /app
|
||
COPY --from=builder --chown=app:app /build/dist/entry_worker.dist/ ./
|
||
|
||
USER app
|
||
|
||
# профили браузера (extra/user_data_dir) и geoip-кэш — в volume
|
||
VOLUME ["/app/extra"]
|
||
|
||
# tini как PID 1: пробрасывает сигналы и подбирает осиротевшие процессы Chrome/Xvfb (иначе — зомби)
|
||
ENTRYPOINT ["/usr/bin/tini", "-g", "--"]
|
||
# BROWSER_SLOTS/WORKER_THREADS читает сам entry_worker.bin (--processes 1, потоков — вдвое больше слотов)
|
||
CMD ["sh", "-c", "exec xvfb-run -a --server-args='-screen 0 1920x1080x24' ./entry_worker.bin"]
|
||
|
||
# test (make test-browser; не публикуется, не используется по умолчанию — docker build должен указывать
|
||
# --target=runtime): entry_worker.bin — скомпилированный бинарник, pytest/tests/harness.py не может
|
||
# подключиться к его internals напрямую. Этой стадии нужны тот же Chrome/Xvfb из runtime и исходники
|
||
# как обычный интерпретируемый Python рядом с бинарником — они не конфликтуют (разные пути)
|
||
FROM runtime AS test
|
||
USER root
|
||
COPY requirements.txt ./
|
||
RUN pip install --no-cache-dir -r requirements.txt
|
||
COPY --chown=app:app src/ ./src/
|
||
USER app
|
||
WORKDIR /app
|