Руководство по настройке Image Converter

Image converter — это микросервис на Node.js, который рендерит SVG-фоны площадок в оптимизированные изображения WebP. Он создаёт три варианта для каждого фона — в полном разрешении, превью на 1000px и размытый плейсхолдер на 400px со встроенными данными base64 для прогрессивной загрузки. Изображения хранятся в S3-совместимом объектном хранилище или в локальной файловой системе.

Стек: Node.js 22, Express 5, Puppeteer (headless Chromium для рендеринга SVG), Sharp (обработка изображений), Redis (очередь конвертаций).

Основные настройки

Setting Type Default Описание
PORT integer 3000 Порт, на котором слушает сервер Express
NODE_ENV string production Окружение Node
DEBUG boolean false Включает режим отладки (устанавливает LOG_LEVEL в debug)
LOG_LEVEL string info Уровень логирования Winston: error, warn, info, http, verbose, debug

Настройки работы с файлами

Setting Type Default Описание
UPLOAD_FOLDER string /tmp Временный каталог для загружаемых файлов (очищается после обработки)
MAX_CONTENT_LENGTH integer 16777216 Максимальный размер загружаемого файла в байтах (по умолчанию 16 МБ)

Настройки обработки изображений

Setting Type Default Описание
MAX_IMAGE_WIDTH integer 15000 Максимально допустимая ширина выходного изображения в пикселях
MAX_IMAGE_HEIGHT integer 15000 Максимально допустимая высота выходного изображения в пикселях
DEFAULT_WIDTH integer 440 Ширина по умолчанию для базового эндпоинта конвертации
DEFAULT_HEIGHT integer 246 Высота по умолчанию для базового эндпоинта конвертации
PNG_OPTIMIZATION boolean false Включает сжатие изображений
PNG_OPTIMIZATION_QUALITY string 0.6-0.8 Диапазон качества для вывода в WebP

Настройки хранилища

Setting Type Default Описание
STORAGE_TYPE string s3 Бэкенд хранилища: s3 или local
LOCAL_STORAGE_PATH string ./storage Путь в файловой системе при использовании локального хранилища

Конфигурация AWS S3

Эти настройки обязательны при STORAGE_TYPE=s3.

Setting Type Default Описание
AWS_ACCESS_KEY_ID string (required) Ключ доступа IAM для аутентификации в S3
AWS_SECRET_ACCESS_KEY string (required) Секретный ключ IAM для аутентификации в S3
AWS_REGION string us-east-1 Регион AWS для бакета S3
AWS_BUCKET_NAME string (required) Имя бакета S3 для хранения изображений
AWS_ENDPOINT_URL string Пользовательский эндпоинт S3 для S3-совместимых провайдеров (MinIO, DigitalOcean Spaces, Garage)
AWS_PUBLIC_BASE_URL string Переопределяет базовый публичный URL для генерируемых URL изображений (см. ниже)

ImportantУчётные данные AWS следует передавать через защищённые переменные окружения или секреты Kubernetes. Никогда не коммитьте учётные данные в систему контроля версий.

Разрешение публичного URL

Конвертер генерирует публичные URL для загруженных изображений. Базовый URL разрешается в следующем порядке приоритета:

  1. AWS_PUBLIC_BASE_URL (если задан) — используется как есть. Пример: https://bucket.nyc3.digitaloceanspaces.com
  2. AWS_ENDPOINT_URL (производный) — path-style: {endpoint}/{bucket}. Пример: https://storage.yandexcloud.net/my-bucket
  3. По умолчанию — virtual-hosted style AWS: https://{bucket}.s3.{region}.amazonaws.com

Клиентам с self-hosted-развёртыванием, использующим не-AWS S3-провайдеров (DigitalOcean Spaces, MinIO, Garage, Yandex Cloud), следует задать AWS_PUBLIC_BASE_URL, чтобы обеспечить корректные публичные URL.

Политика бакета S3

Чтобы разрешить публичный доступ на чтение к изображениям, примените следующую политику бакета:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*"
    }
  ]
}

Конфигурация CORS

Чтобы предотвратить проблемы с tainted canvas при обработке изображений в веб-браузерах, настройте CORS на бакете:

[
  {
    "AllowedHeaders": ["*"],
    "AllowedMethods": ["GET"],
    "AllowedOrigins": ["*"],
    "ExposeHeaders": []
  }
]

NoteДля production-окружений ограничьте AllowedOrigins вашими конкретными доменами вместо *.

Настройки Redis

Конвертации ставятся в очередь Redis при REDIS_ENABLED=true (значение по умолчанию в Helm-чарте). Каждый запрос записывается как задание; конвертер с GPU забирает его первым, CPU-конвертер — после CPU_CLAIM_DELAY_MS, а задание, чей worker упал во время рендеринга, возвращается в очередь и повторяется. При REDIS_ENABLED=false конвертер рендерит внутри запроса и Redis не нужен. Если Redis включён, но недоступен, конвертер тоже рендерит внутри запроса, отвечает 503 на GET /jobs/{id} и пишет ошибки подключения в лог; очередь запускается сама, как только Redis становится доступен.

Setting Type Default Описание
REDIS_ENABLED boolean false Ставить конвертации в очередь Redis вместо рендеринга внутри запроса
REDIS_HOST string localhost Имя хоста сервера Redis
REDIS_PORT integer 6379 Порт сервера Redis
REDIS_PASSWORD string Пароль Redis (если требуется аутентификация)
CONVERTER_ROLE string cpu gpu забирает задание из очереди сразу; cpu сначала ждёт CPU_CLAIM_DELAY_MS
CPU_CLAIM_DELAY_MS integer 5000 Фора GPU-конвертера, прежде чем CPU-конвертер заберёт задание
WORKER_ENABLED boolean true false — под принимает запросы, но не рендерит
JOB_TTL_SECONDS integer 3600 Время жизни записи задания в Redis
RESULT_TTL_SECONDS integer 900 Время жизни готового результата в Redis
JOB_VISIBILITY_TIMEOUT_MS integer 300000 Бюджет на рендеринг, после которого забранное задание возвращается в очередь
JOB_MAX_ATTEMPTS integer 3 Число попыток, после которого задание помечается как failed
WORKER_IDLE_POLL_MS integer 500 Частота опроса очереди простаивающим worker
REAPER_INTERVAL_MS integer 30000 Интервал обхода, возвращающего потерянные задания в очередь

Аутентификация

Setting Type Default Описание
API_KEY string Общий секрет. Если задан, каждый маршрут конвертации и GET /jobs/{id} требуют Authorization: Bearer <значение> и без него отвечают 401; /health, /metrics и маршруты локального хранилища остаются открытыми. Helm-чарт генерирует значение и передаёт его в editor-service

Эндпоинты API

Method Path Описание
POST / Базовая конвертация SVG в изображение. Возвращает файл изображения напрямую. Query-параметры: width, height
POST /background/ Полная конвертация фона. Возвращает JSON с тремя URL вариантов (full, preview, blurred). Query-параметры: filename, debug
POST /thumbnail/ Конвертация миниатюры. Возвращает JSON с тремя URL вариантов. Query-параметры: filename, debug
POST /photo/ Конвертация фото секции. Возвращает JSON с URL фото и миниатюры
GET /jobs/{id} Задание из очереди: { requestId, kind, status, attempts, enqueuedAt, finishedAt, result }. status — pending, running, succeeded или failed; result равен null, пока задание не завершено, затем — тот JSON, который вернул бы синхронный маршрут. 404 для неизвестного или истёкшего id, 503 при выключенной очереди
GET /health Проверка работоспособности сервиса. Возвращает { "status": "healthy" }
GET /health/redis Статус подключения к Redis, роль конвертации пода (role) и глубина очередей pending и processing
GET /metrics Метрики Prometheus в текстовом формате

POST /, /background/ и /thumbnail/ принимают multipart/form-data с SVG-файлом (image/svg+xml). POST /photo/ принимает файл JPEG, PNG или WebP до 10 МБ и отвечает полями url и thumbUrl.

При включённой очереди маршрут конвертации отвечает 202 Accepted с id задания, а результат читается из GET /jobs/{id}; editor-service выполняет этот опрос сам. При выключенной очереди маршруты отвечают результатом напрямую.

Ограничение частоты: RATE_LIMIT_MAX запросов (по умолчанию 600) за RATE_LIMIT_WINDOW_MS (по умолчанию 60000) на один IP-адрес для маршрутов конвертации, включая /photo/. У GET /jobs/{id} свой бюджет — JOBS_RATE_LIMIT_MAX запросов в минуту (по умолчанию 5000). /health и /metrics не ограничиваются.

Таймаут запроса: 5 минут (300 секунд).

Формат вывода

Конвертер генерирует изображения WebP (не PNG), обеспечивая на 30–50% меньший размер файлов с поддержкой альфа-канала.

Для каждого фона создаётся три варианта:

Variant Width Quality Назначение
full Original 60-80% Фон площадки в полном разрешении
preview 1000px 60-80% Быстро загружаемое превью для первоначального рендера
blurred 400px 30-50% Плейсхолдер со встроенными данными base64 для прогрессивной загрузки

Файлы хранятся по пути backgrounds/{filename}/{variant}.webp в настроенном бэкенде хранилища.

Docker

Сервис работает в Docker-контейнере на базе node:22.14.0-alpine с установленным Chromium для Puppeteer.

Порт: 3000

Проверка работоспособности: GET /health каждые 30 секунд (3 повтора, период старта 5 секунд)

Переменные окружения для Puppeteer:

  • PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true (использует системный Chromium)
  • PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser

Безопасность: Работает от имени non-root-пользователя (uid 10001). Использует tini в качестве init-процесса для предотвращения зомби-процессов.

Мониторинг

Метрики Prometheus

Эндпоинт /metrics предоставляет:

  • http_request_duration_seconds — гистограмма с метками method, route, status_code
  • http_requests_total — счётчик с метками method, route, status_code
  • Стандартные метрики процесса Node.js (память, CPU, задержка event loop, GC)

Бакеты метрик оптимизированы для нагрузок обработки изображений: 0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30 секунд.

Эндпоинты работоспособности

  • GET /health — возвращает 200, если сервис работает
  • GET /health/redis — возвращает статус подключения к Redis, роль пода при конвертации (role) и глубину очередей pending и processing

Способы конфигурации

Приложение можно настраивать несколькими способами, перечисленными в порядке приоритета:

  1. Переменные окружения
  2. Kubernetes ConfigMap/Secrets
  3. Файл .env (только для разработки)

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

При развёртывании в Kubernetes конфигурация управляется через значения Helm и секреты:

converter:
  image:
    repository: registry.gitlab.com/seatmap.pro/seatmap/converter-service
    tag: latest
  env:
    PORT: '3000'
    LOG_LEVEL: 'info'
    STORAGE_TYPE: 's3'
    AWS_REGION: 'us-east-1'
    AWS_BUCKET_NAME: 'your-bucket'
    # AWS_PUBLIC_BASE_URL: "https://your-cdn.example.com"  # if using non-AWS S3

Управление секретами

Учётные данные AWS и пароли Redis следует хранить безопасно, используя один из этих методов:

  • Kubernetes Secrets (внедряются через seatmap-helm-secrets)
  • External Secrets Operator
  • IAM roles for Service Accounts (IRSA)

Настройка для разработки

Для локальной разработки с использованием Docker Compose:

cd products/converter-service
cp .env.sample .env
# Edit .env with your settings (use STORAGE_TYPE=local for development)
docker compose up

Это запускает сервис конвертера на порту 3000 и экземпляр Redis на порту 6379.

Никогда не коммитьте чувствительные учётные данные в систему контроля версий.

Устранение неполадок

Распространённые проблемы конфигурации:

  • Загрузка изображения не удаётся: Проверьте MAX_CONTENT_LENGTH и настройку ограничения размера тела запроса в ingress. Лимит по умолчанию 16 МБ может потребоваться увеличить для больших SVG-файлов.
  • Отказ в доступе к S3: Проверьте учётные данные AWS и права доступа к бакету. Убедитесь, что AWS_PUBLIC_BASE_URL соответствует реальной схеме URL вашего бакета.
  • Пустые или повреждённые изображения: Убедитесь, что Chromium установлен (/usr/bin/chromium-browser). В Docker убедитесь, что переменная окружения PUPPETEER_EXECUTABLE_PATH задана корректно.
  • Высокое потребление памяти: Sharp и Puppeteer могут потреблять значительный объём памяти при работе с большими SVG-файлами. Установите лимиты памяти контейнера не менее 512 МБ, рекомендуется 2 ГБ для production.
  • WebP не загружается в renderer: Убедитесь, что заголовки CORS настроены на бакете S3. Renderer’у нужен кросс-доменный доступ для загрузки фоновых изображений.
  • Неверные публичные URL: Если вы используете не-AWS S3-провайдера, задайте AWS_PUBLIC_BASE_URL явно. Генерация URL по умолчанию предполагает virtual-hosted-стиль бакета AWS.

Введите название функции, настройки, эндпоинта или метода SDK.