Назад в Центр знаний
Развертывание

Автоматизация браузера в Docker: развертывание и масштабирование

Развертывание автоматизации браузера в контейнерах Docker: примеры Dockerfile, масштабирование через Compose, монтируемые тома и рекомендации для продакшена.

BotBrowser Team

Документация

Нужна поддерживаемая продуктовая документация?

У этой статьи есть соответствующая страница в центре документации. Используйте docs для каноничного сценария настройки, актуальных флагов и долгосрочной справки.

Docker дает развертыванию браузера повторяемый образ, закрытую файловую систему, собственное сетевое пространство имен и ограничения ресурсов. Каждый контейнер стартует из одного и того же известного состояния, упавший worker можно поднять политикой перезапуска, а проблема с памятью в одном контейнере остается внутри этого контейнера. Это реальные эксплуатационные преимущества для парков BotBrowser, и поэтому большинство команд переходит от серверов, настроенных вручную, к образам.

TL;DR

  • Сначала увеличьте общую память: контейнер получает 64 MB в /dev/shm по умолчанию, поэтому задайте --shm-size=2g или shm_size: 2gb до того, как вкладки начнут падать.
  • Запускайте Xvfb в entrypoint и экспортируйте DISPLAY=:10.0 до запуска браузера, иначе контейнер завершится с "Cannot open display".
  • Берите базовый образ Ubuntu 22.04 или Debian: бинарникам на основе Chrome нужен glibc, а образ Alpine с musl их не запустит.
  • Передавайте --init или init: true, чтобы Docker убирал осиротевшие процессы браузера, и выделяйте каждому контейнеру собственный том пользовательских данных.
  • Публикуйте порт отладки как -p 127.0.0.1:9222:9222, а не как -p 9222:9222 без адреса: тот, кто дотянется до этого порта, управляет браузером.

Содержание

Требования к контейнеру до первого запуска

Docker не решает, будет ли развертывание успешным на конкретном сайте, и не заставляет все хосты вести себя одинаково. Он изолирует процессы и ограничивает ресурсы. Браузеру по-прежнему нужны системные библиотеки, дисплей, общая память и сетевой маршрут, как любому браузеру на основе Chrome. От того, насколько точно эти пять требований выполнены в образе, зависит, будет ли развертывание запускаться каждый раз или сломается через час.

Общая память идет первой. Chrome использует /dev/shm для обмена между своими процессами, а контейнер Docker получает /dev/shm размером 64 МБ, пока вы его не измените. Для браузера с реальными страницами этого очень мало, и обычные признаки такие: вкладки аварийно закрываются, страницы становятся пустыми или появляется сообщение о нехватке памяти при большом запасе свободной RAM на хосте. Начните с --shm-size=2g в docker run или shm_size: 2gb в файле Compose и считайте риском любое значение меньше 1 ГБ. Итоговый размер должен опираться на измерения ваших собственных страниц.

Второе требование: виртуальный дисплей. BotBrowser нужен дисплей X11 даже в режиме headless, поэтому контейнер должен запустить Xvfb раньше браузера и экспортировать DISPLAY с подходящим значением. Если entrypoint сначала запускает браузер, контейнер сразу завершается с сообщением "Cannot open display". Самое простое решение: короткая пауза после запуска Xvfb, а более аккуратное: проверка состояния, подтверждающая, что сокет дисплея существует.

Третье требование: базовый образ. Браузеры на основе Chrome зависят от glibc, поэтому берите базу Debian или Ubuntu. Alpine использует musl и не запускает эти бинарные файлы. В документированном образе используется Ubuntu 22.04 с общими библиотеками, перечисленными в Dockerfile ниже. Отсутствующая библиотека проявляется как сбой запуска, поэтому храните список пакетов в системе контроля версий и пересобирайте образ, а не правьте работающий контейнер.

Четвертое требование: процесс init. Браузер порождает дочерние процессы, и когда один из них остается без родителя, его никто не подбирает. При долгой работе такие процессы-зомби накапливаются. Передайте --init в docker run или задайте init: true в Compose, чтобы собственный init Docker стал PID 1 и убирал их.

Пятое требование: отдельный каталог пользовательских данных для каждого процесса браузера. В контейнере этот каталог находится в записываемом слое контейнера и исчезает при удалении контейнера, поэтому смонтируйте том по этому пути, если нужно сохранить cookie и хранилище после перезапуска. Не позволяйте двум работающим контейнерам делить один каталог, потому что два браузера, пишущие в один каталог профиля, портят состояние друг друга.

ТребованиеНастройка DockerЧто вы увидите без него
Общая память от 1 до 2 ГБ--shm-size=2g или shm_size: 2gbАварийные закрытия и пустые страницы под нагрузкой
Виртуальный дисплей до запускаXvfb в entrypoint, DISPLAY=:10.0"Cannot open display" при старте
Базовый образ с glibcDebian или Ubuntu, не AlpineБинарный файл не запускается
Процесс init--init или init: trueНакапливаются процессы-зомби
Собственный каталог пользовательских данныхОтдельный том на экземпляр в /app/dataПотерянные сеансы или поврежденное состояние при общем доступе

Устройство контейнера Docker с BotBrowser: дисплей Xvfb, браузер с подключенным профилем, порт отладки только на loopback, ограничения ресурсов, том профилей только для чтения и том пользовательских данных для каждого экземпляра.

Бинарные файлы для Ubuntu и Linux требуют подходящего тарифа BotBrowser. В документации по развертыванию в Docker для них указан ENT Tier 1 или выше, поэтому проверьте свой тариф до того, как строить образ вокруг пакета .deb.

Соберите образ и запустите один контейнер

Dockerfile ниже следует документации BotBrowser по Docker. Он устанавливает общие библиотеки и Xvfb, ставит пакет .deb BotBrowser, который вы скачали для своего тарифа, и создает два каталога, которые контейнер использует для профилей и пользовательских данных. Параметр --platform=linux/amd64 фиксирует архитектуру, чтобы образ, собранный на одном хосте, собирался так же и на другом.

FROM --platform=linux/amd64 ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
ENV DISPLAY=:10.0
RUN apt-get update && apt-get install -y \
    libnss3 libatk1.0-0 libatk-bridge2.0-0 \
    libcups2 libdrm2 libxcomposite1 libxdamage1 \
    libxrandr2 libgbm1 libxkbcommon0 libgtk-3-0 \
    libpangocairo-1.0-0 libpango-1.0-0 \
    libwayland-client0 libwayland-server0 \
    libdbus-1-3 libatspi2.0-0 libasound2 libxss1 \
    fonts-liberation libcurl4 libvulkan1 \
    xvfb curl ca-certificates \
    && rm -rf /var/lib/apt/lists/*
COPY botbrowser_*.deb /tmp/
RUN dpkg -i /tmp/botbrowser_*.deb || apt-get install -f -y \
    && rm /tmp/botbrowser_*.deb
RUN mkdir -p /app/profiles /app/data
WORKDIR /app
COPY entrypoint.sh /app/entrypoint.sh
RUN chmod +x /app/entrypoint.sh
ENTRYPOINT ["/app/entrypoint.sh"]

Entrypoint запускает виртуальный дисплей, а затем заменяет себя браузером через exec, так что сигналы от docker stop приходят прямо в браузер. Все, что вы передадите после имени образа, поступает как "$@", поэтому один и тот же образ можно запускать с разными профилями и прокси без пересборки.

#!/bin/bash
Xvfb :10 -screen 0 1920x1080x24 -ac &
sleep 1
exec chromium-browser \
    --headless \
    --no-sandbox \
    --disable-dev-shm-usage \
    --user-data-dir=/app/data \
    --remote-debugging-port=9222 \
    --remote-debugging-address=0.0.0.0 \
    "$@"

Два параметра этого скрипта требуют пояснения. --disable-dev-shm-usage велит Chrome писать файлы общей памяти в /tmp, а не в /dev/shm. Это альтернативная мера против маленького /dev/shm. После её включения Chrome перестаёт использовать /dev/shm, поэтому --shm-size важен в основном тогда, когда параметра нет, а использование обоих служит подстраховкой. --no-sandbox используется потому, что контейнеры обычно запускаются без привилегий, которых требует песочница Chrome. Такой выбор переносит границу изоляции на контейнер, поэтому запускайте контейнер от ограниченного пользователя, если образ это позволяет, оставьте ограничения ресурсов Docker и никогда не монтируйте пути хоста, которые вы не доверили бы браузеру.

Соберите образ и запустите один контейнер с размером общей памяти, процессом init, монтированием профилей только для чтения, томом пользовательских данных и портом отладки, привязанным к адресу loopback хоста.

docker build -t botbrowser:latest .
docker run -d --name botbrowser --init --shm-size=2g \
  -v "$PWD/profiles:/app/profiles:ro" \
  -v bb-data:/app/data \
  -p 127.0.0.1:9222:9222 \
  botbrowser:latest \
  --bot-profile=/app/profiles/profile.enc \
  --proxy-server=socks5://user:[email protected]:1080

Форма -p 127.0.0.1:9222:9222 важна. Простой -p 9222:9222 публикует порт на всех интерфейсах хоста, а порт удаленной отладки дает полный контроль над браузером тому, кто до него дотянется. Привязка к loopback оставляет его доступным только с хоста, а добраться до него откуда-то еще можно через SSH-туннель или внутреннюю сеть. Справочник по docker run подробно описывает синтаксис публикации, параметр --shm-size и --init.

Остановка контейнера тоже часть проекта. docker stop отправляет SIGTERM и ждет десять секунд, прежде чем отправить SIGKILL. Поскольку entrypoint передает управление браузеру через exec, браузер получает сигнал и может закрыть страницы и сохранить пользовательские данные. Если вашим сеансам нужно больше времени на завершение, увеличьте тайм-аут через --stop-timeout или stop_grace_period в Compose и помечайте образы версией вместо latest, чтобы откат сводился к смене одного тега.

Учетные данные прокси в примере условные. Передавайте настоящие значения при запуске через хранилище секретов, файл окружения или секреты Compose и держите их вне образа и системы контроля версий.

Подключите профили и храните пользовательские данные для каждого экземпляра

BotBrowser загружает конфигурацию из файла профиля. В контейнере чистый способ передать этот файл: монтирование только для чтения, как в команде выше. Образ остается прежним, профиль лежит на хосте, а замена профиля сводится к правке файла и перезапуску контейнера. Никто ничего не пересобирает, и никто не может изменить файл изнутри контейнера, потому что монтирование только для чтения.

Используйте --bot-profile, когда каждый контейнер должен стартовать с конкретного профиля. Это подходящая форма для парка, где у контейнера один прокси, один регион и один сознательно выбранный профиль. Переданный путь должен совпадать с путем внутри контейнера, а расхождение между точкой монтирования и параметром является самой частой причиной ошибки запуска "профиль не найден".

Используйте --bot-profile-dir, когда контейнер должен сам выбрать профиль из каталога. BotBrowser выбирает один файл .enc из каталога при каждом запуске. Эти два параметра нельзя сочетать, а если передать оба, приоритет получает каталог. Выбирайте форму с каталогом, когда разнообразие профилей важнее жесткой пары, и форму с одним файлом, когда профиль контейнера нужно знать заранее.

Подбирайте пакет профилей под старшую версию BotBrowser. Сочетание пакета профилей с другой старшей версией не описано в документации, поэтому держите отдельный каталог профилей для каждой версии и монтируйте тот, что соответствует образу. Поскольку файлы профилей монтируются, а не встраиваются в образ, обновление браузера и обновление профилей становятся двумя отдельными обратимыми шагами.

Поведение, заданное профилем, определяет смонтированный файл профиля, а не хост контейнера. Это полезное свойство для эксплуатации, но не стоит читать его как обещание, что любой базовый образ, ядро, хост, прокси и целевой сайт дадут одинаковый результат. Проверяйте ту комбинацию, которую вы действительно запускаете, и записывайте в журнал развертывания тег образа, версию BotBrowser и имя файла профиля.

Пользовательские данные требуют такой же заботы. Именованный том для каждого экземпляра, например bb-data или bb1-data в примерах, сохраняет cookie, локальное хранилище и другое состояние сеанса между перезапусками и оставляет каждый экземпляр независимым. Удаляйте том только тогда, когда нужно, чтобы экземпляр начал с чистого листа. Для коротких одноразовых запусков обойдитесь без тома и позвольте записываемому слою контейнера исчезнуть вместе с ним.

Масштабируйте через Docker Compose

Compose описывает каждый экземпляр как службу и собирает порты, тома, ограничения и поведение при перезапуске в одном файле, удобном для проверки. Версия ниже запускает два контейнера из одного образа. У каждого свой файл профиля, свой прокси, свой том пользовательских данных и свой порт отладки на хосте.

services:
  bb-1:
    image: botbrowser:latest
    init: true
    shm_size: 2gb
    mem_limit: 4g
    cpus: 2
    ports:
      - '127.0.0.1:9222:9222'
    volumes:
      - ./profiles:/app/profiles:ro
      - bb1-data:/app/data
    command:
      - --bot-profile=/app/profiles/profile-1.enc
      - --proxy-server=socks5://user:[email protected]:1080
    restart: unless-stopped
    healthcheck:
      test: ['CMD', 'curl', '-f', 'http://localhost:9222/json/version']
      interval: 30s
      timeout: 10s
      retries: 3
  bb-2:
    image: botbrowser:latest
    init: true
    shm_size: 2gb
    mem_limit: 4g
    cpus: 2
    ports:
      - '127.0.0.1:9223:9222'
    volumes:
      - ./profiles:/app/profiles:ro
      - bb2-data:/app/data
    command:
      - --bot-profile=/app/profiles/profile-2.enc
      - --proxy-server=socks5://user:[email protected]:1080
    restart: unless-stopped
    healthcheck:
      test: ['CMD', 'curl', '-f', 'http://localhost:9222/json/version']
      interval: 30s
      timeout: 10s
      retries: 3
volumes:
  bb1-data:
  bb2-data:

Ограничения памяти и CPU не дают одному вышедшему из-под контроля контейнеру занять весь хост, а проверка состояния обращается к конечной точке отладки внутри контейнера, чтобы Docker мог пометить не отвечающий экземпляр как неисправный. Политика restart: unless-stopped возвращает упавший экземпляр без участия человека. Драйвер журналирования Docker по умолчанию хранит журналы контейнеров без ограничения размера, поэтому для долгоживущих служб задайте параметры журналирования max-size и max-file, пока загруженный экземпляр не заполнил диск. Слишком жесткие ограничения приведут к тем же сбоям, что и маленький /dev/shm, поэтому подбирайте их по измерениям на ваших страницах и ожидайте, что цифры будут различаться между нагрузками.

Фиксированные сопоставления портов подходят для нескольких именованных служб и плохо подходят для реплик. Когда нужно несколько одинаковых контейнеров, каждый из которых сам выбирает профиль, уберите фиксированный порт хоста и позвольте Docker выбрать его.

services:
  botbrowser:
    image: botbrowser:latest
    init: true
    shm_size: 2gb
    ports:
      - '127.0.0.1::9222'
    volumes:
      - ./profiles:/app/profiles:ro
    command:
      - --bot-profile-dir=/app/profiles
    restart: unless-stopped

Форма 127.0.0.1::9222 публикует для каждой реплики случайный свободный порт хоста на loopback. Запустите реплики и выведите назначенные порты командами ниже. У каждой реплики своя файловая система контейнера, поэтому ее пользовательские данные закрыты от других и исчезают вместе с ней. Если реплике нужно устойчивое состояние сеанса, дайте ей именованную службу и именованный том, как в первом файле.

docker compose up -d --scale botbrowser=3
docker compose ps

Емкость зависит от хоста, загружаемых страниц и используемых прокси. Увеличивайте число реплик по шагам, следите на хосте за памятью, CPU и числом перезапусков и останавливайтесь, когда запас становится небольшим. Руководство по оптимизации производительности рассказывает об измерениях, а статья о настройке headless-сервера описывает те же требования к дисплею и библиотекам на обычном хосте, что помогает сравнить контейнер с уже работающим сервером.

Проверьте контейнер и разберите типичные сбои

Развертывание готово, когда браузер запускается, конечная точка отладки отвечает, навигация завершается, а в журналах нет ошибок загрузчика. Начните с журналов и конечной точки.

docker logs botbrowser
curl http://127.0.0.1:9222/json/version

Отсутствующая общая библиотека появляется в журнале контейнера при старте как ошибка загрузчика с именем файла, а контейнер, который сразу завершается, почти всегда страдает от этого, от неверного пути к профилю или от отсутствия дисплея. Если конечная точка возвращает документ JSON с версией браузера, процесс работает и слушает порт. Затем выполните пробную навигацию с хоста. Фрагмент ниже подключается через порт на loopback и загружает нейтральную страницу.

import { chromium } from 'playwright-core';
const browser = await chromium.connectOverCDP('http://127.0.0.1:9222');
const context = browser.contexts()[0];
const page = await context.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close();

Достаточно вывести заголовок страницы. Это доказывает, что дисплей, библиотеки, монтирование профиля и сетевой маршрут работают вместе, и дает одну команду, которую можно повторять после каждого изменения образа. Держите эту проверку небольшой. Проверка развертывания должна подтверждать, что контейнер работает, и не должна пытаться измерять поведение сторонних сайтов. Для визуальной проверки изнутри контейнера в материале Надёжные скриншоты в headless-браузере описано, как делать снимки экрана стабильно.

ПроблемаЧто проверить
Контейнер сразу завершаетсяВыполните docker logs и найдите отсутствующую библиотеку, нечитаемый путь к профилю или отсутствующий дисплей
"Cannot open display"Запустите Xvfb раньше браузера и убедитесь, что в образе задан DISPLAY=:10.0
"Out of memory" или аварийные страницыУвеличьте --shm-size, затем лимит памяти, если ограничен сам контейнер
Порт 9222 недоступен с хостаОставьте --remote-debugging-address=0.0.0.0 внутри контейнера и публикуйте порт на loopback
Накапливаются процессы-зомбиДобавьте --init или init: true
Профиль не найденСравните точку монтирования с путем в --bot-profile и выведите каталог через docker exec
Медленный первый запускПервый запуск с пустым каталогом пользовательских данных идет дольше, последующие используют его повторно

Считайте порт отладки производственным риском. Любой, кто может подключиться, способен читать страницы, выполнять скрипты и управлять браузером, поэтому никогда не публикуйте его на общедоступном интерфейсе, поставьте перед ним правило межсетевого экрана или частную сеть и дайте каждому контейнеру свой порт. Журналы тоже требуют осторожности. Удаляйте учетные данные прокси и идентификаторы аккаунтов, прежде чем делиться журналом за пределами команды.

Что обеспечивает BotBrowser и что остается за вами

BotBrowser позволяет загрузить файл профиля из тома, смонтированного только для чтения, через --bot-profile или выбрать профиль для каждого контейнера через --bot-profile-dir, так что каждый контейнер стартует с конфигурацией, заданной его смонтированным профилем, а профили можно менять без пересборки образа. BotBrowser не может обеспечить общую память контейнера, виртуальный дисплей, системные библиотеки, качество прокси и оркестрацию, и он не гарантирует, что целевой сайт примет трафик или что бинарные файлы для Linux будут доступны без подходящего тарифа.

Разделение ниже сохраняет ясность ответственности, когда что-то ломается. В первом столбце то, что делает браузер со смонтированным файлом. Во втором то, что обслуживает ваша команда.

Обеспечивает BotBrowserОбслуживает ваша команда
Загрузка файла профиля, переданного через --bot-profileОбщая память, лимиты CPU и памяти, сам хост
Выбор профиля из --bot-profile-dir при запускеВиртуальный дисплей, системные библиотеки и базовый образ
Применение настроек прокси, заданных через --proxy-serverКачество прокси, ротация и сетевой маршрут
Работа с указанным вами каталогом пользовательских данныхТома, резервные копии и очистка состояния сеанса
Запуск со смонтированным профилем без пересборки образаПубликация порта отладки, сетевая политика, управление доступом и оркестрация

Используйте эту таблицу как контрольный список во время инцидента. Сбой, в котором упоминается память, связан с лимитом контейнера. Ошибка запуска с именем библиотеки указывает на образ. Ошибка пути к профилю указывает на монтирование. Ни одна из них не касается профиля браузера, и ни одну не решит смена профиля. Не включайте результаты на конкретных сайтах в критерии готовности развертывания, потому что они зависят от правил самого сайта и от трафика, который вам разрешено отправлять.

Когда будете готовы к сборке, скачайте BotBrowser и убедитесь, что ваш тариф включает бинарные файлы для Linux. Для большого парка страница для предприятий описывает варианты тарифов. Рецепты CLI перечисляют использованные здесь параметры и их сочетания, что пригодится при расширении entrypoint.

Источники

Практический итог

Запишите пять настроек контейнера в образ и командную строку, а не в руководство, которое никто не открывает: значение --shm-size, шаг Xvfb с DISPLAY=:10.0, базу Ubuntu 22.04, --init и том пользовательских данных на каждый экземпляр. Затем запустите один контейнер, смонтируйте профиль через --bot-profile и убедитесь, что опубликованный на loopback порт 9222 отвечает, прежде чем масштабировать тот же файл через Compose.

#Docker#развертывание#сервер#автоматизация#Devops

Эта статья входит в руководство: Развёртывание и выпуск браузера: операционная карта

Переведите BotBrowser из исследований в продакшн

Используйте эти руководства, чтобы понять модель, а затем перейти к кроссплатформенной валидации, изолированным контекстам и масштабируемому браузерному развертыванию.