Guía de configuración de Image Converter

Image converter es un microservicio de Node.js que renderiza fondos SVG de recintos en imágenes WebP optimizadas. Produce tres variantes por cada fondo: resolución completa, una previsualización de 1000px y un marcador de posición difuminado de 400px con datos base64 incorporados para la carga progresiva. Las imágenes se almacenan en almacenamiento de objetos compatible con S3 o en el sistema de archivos local.

Stack: Node.js 22, Express 5, Puppeteer (Chromium headless para renderizar SVG), Sharp (procesamiento de imágenes), Redis (cola de conversiones).

Ajustes principales

Setting Type Default Descripción
PORT integer 3000 Puerto de escucha del servidor Express
NODE_ENV string production Entorno de Node
DEBUG boolean false Activa el modo de depuración (establece LOG_LEVEL en debug)
LOG_LEVEL string info Nivel de log de Winston: error, warn, info, http, verbose, debug

Ajustes de manejo de archivos

Setting Type Default Descripción
UPLOAD_FOLDER string /tmp Directorio temporal para los archivos subidos (se limpia tras el procesamiento)
MAX_CONTENT_LENGTH integer 16777216 Tamaño máximo del archivo subido en bytes (por defecto 16 MB)

Ajustes de procesamiento de imágenes

Setting Type Default Descripción
MAX_IMAGE_WIDTH integer 15000 Ancho máximo permitido de la imagen de salida en píxeles
MAX_IMAGE_HEIGHT integer 15000 Alto máximo permitido de la imagen de salida en píxeles
DEFAULT_WIDTH integer 440 Ancho por defecto para el endpoint básico de conversión
DEFAULT_HEIGHT integer 246 Alto por defecto para el endpoint básico de conversión
PNG_OPTIMIZATION boolean false Activa la compresión de imágenes
PNG_OPTIMIZATION_QUALITY string 0.6-0.8 Rango de calidad para la salida WebP

Ajustes de almacenamiento

Setting Type Default Descripción
STORAGE_TYPE string s3 Backend de almacenamiento: s3 o local
LOCAL_STORAGE_PATH string ./storage Ruta del sistema de archivos al usar almacenamiento local

Configuración de AWS S3

Estos ajustes son obligatorios cuando STORAGE_TYPE=s3.

Setting Type Default Descripción
AWS_ACCESS_KEY_ID string (required) Clave de acceso IAM para la autenticación en S3
AWS_SECRET_ACCESS_KEY string (required) Clave secreta IAM para la autenticación en S3
AWS_REGION string us-east-1 Región de AWS para el bucket de S3
AWS_BUCKET_NAME string (required) Nombre del bucket de S3 para almacenar las imágenes
AWS_ENDPOINT_URL string Endpoint S3 personalizado para proveedores compatibles con S3 (MinIO, DigitalOcean Spaces, Garage)
AWS_PUBLIC_BASE_URL string Sobrescribe la base de la URL pública para las URL de imágenes generadas (ver más abajo)

ImportantLas credenciales de AWS deben proporcionarse mediante variables de entorno seguras o secretos de Kubernetes. Nunca incluyas credenciales en el control de versiones.

Resolución de la URL pública

El conversor genera URL públicas para las imágenes subidas. La base de la URL se resuelve en este orden de prioridad:

  1. AWS_PUBLIC_BASE_URL (si está definida) — se usa tal cual. Ejemplo: https://bucket.nyc3.digitaloceanspaces.com
  2. AWS_ENDPOINT_URL (derivada) — path-style: {endpoint}/{bucket}. Ejemplo: https://storage.yandexcloud.net/my-bucket
  3. Por defecto — estilo virtual-hosted de AWS: https://{bucket}.s3.{region}.amazonaws.com

Los clientes self-hosted que usan proveedores de S3 distintos de AWS (DigitalOcean Spaces, MinIO, Garage, Yandex Cloud) deben definir AWS_PUBLIC_BASE_URL para garantizar URL públicas correctas.

Política del bucket de S3

Para permitir el acceso público de lectura a las imágenes, aplica la siguiente política de bucket:

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

Configuración de CORS

Para evitar problemas de tainted canvas al procesar imágenes en navegadores web, configura CORS en el bucket:

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

NotePara entornos de producción, restringe AllowedOrigins a tus dominios específicos en lugar de *.

Ajustes de Redis

Las conversiones se encolan en Redis cuando REDIS_ENABLED=true (valor por defecto del chart de Helm). Cada solicitud se registra como un trabajo; un conversor con GPU lo reclama primero, un conversor por CPU lo reclama tras CPU_CLAIM_DELAY_MS, y un trabajo cuyo worker muere a mitad del renderizado vuelve a la cola y se reintenta. Con REDIS_ENABLED=false el conversor renderiza dentro de la solicitud y no necesita Redis. Si Redis está activado pero no accesible, el conversor también renderiza dentro de la solicitud, responde 503 en GET /jobs/{id} y registra errores de conexión; el encolado arranca solo cuando Redis vuelve a estar accesible.

Setting Type Default Descripción
REDIS_ENABLED boolean false Encola las conversiones en Redis en lugar de renderizar dentro de la solicitud
REDIS_HOST string localhost Nombre de host del servidor Redis
REDIS_PORT integer 6379 Puerto del servidor Redis
REDIS_PASSWORD string Contraseña de Redis (si se requiere autenticación)
CONVERTER_ROLE string cpu gpu reclama un trabajo encolado de inmediato; cpu espera primero CPU_CLAIM_DELAY_MS
CPU_CLAIM_DELAY_MS integer 5000 Ventaja que recibe un conversor con GPU antes de que un conversor por CPU reclame el trabajo
WORKER_ENABLED boolean true false hace que el pod acepte envíos sin renderizar
JOB_TTL_SECONDS integer 3600 Vida de un registro de trabajo en Redis
RESULT_TTL_SECONDS integer 900 Vida de un resultado renderizado en Redis
JOB_VISIBILITY_TIMEOUT_MS integer 300000 Presupuesto de renderizado antes de devolver a la cola un trabajo reclamado
JOB_MAX_ATTEMPTS integer 3 Intentos antes de registrar un trabajo como fallido
WORKER_IDLE_POLL_MS integer 500 Cadencia de sondeo de la cola de un worker inactivo
REAPER_INTERVAL_MS integer 30000 Intervalo del barrido que devuelve a la cola las reclamaciones perdidas

Autenticación

Setting Type Default Descripción
API_KEY string Secreto compartido. Cuando está definido, cada ruta de conversión y GET /jobs/{id} exigen Authorization: Bearer <valor> y responden 401 sin él; /health, /metrics y las rutas de activos en almacenamiento local siguen abiertos. El chart de Helm genera el valor y lo conecta con editor-service

Endpoints de la API

Method Path Descripción
POST / Conversión básica de SVG a imagen. Devuelve el archivo de imagen directamente. Query params: width, height
POST /background/ Conversión completa de fondo. Devuelve JSON con las URL de las tres variantes (full, preview, blurred). Query params: filename, debug
POST /thumbnail/ Conversión de miniatura. Devuelve JSON con las URL de las tres variantes. Query params: filename, debug
POST /photo/ Conversión de foto de sección. Devuelve JSON con las URL de la foto y de la miniatura
GET /jobs/{id} Una conversión encolada: { requestId, kind, status, attempts, enqueuedAt, finishedAt, result }. status es pending, running, succeeded o failed; result es null hasta que el trabajo termina, y después el JSON que habría devuelto la ruta síncrona. 404 para un id desconocido o caducado, 503 cuando el encolado está desactivado
GET /health Comprobación de estado del servicio. Devuelve { "status": "healthy" }
GET /health/redis Estado de la conexión con Redis, el role de conversión del pod y las profundidades de cola pending y processing
GET /metrics Métricas de Prometheus en formato de texto

POST /, /background/ y /thumbnail/ aceptan multipart/form-data con un archivo SVG (image/svg+xml). POST /photo/ acepta un archivo JPEG, PNG o WebP de hasta 10 MB y responde con url y thumbUrl.

Con el encolado activado, una ruta de conversión responde 202 Accepted con un id de trabajo y el resultado se lee en GET /jobs/{id}; editor-service hace este sondeo por sí mismo. Con el encolado desactivado las rutas responden directamente con el resultado.

Limitación de tasa: RATE_LIMIT_MAX solicitudes (600 por defecto) cada RATE_LIMIT_WINDOW_MS (60000 por defecto) por dirección IP en las rutas de conversión, /photo/ incluida. GET /jobs/{id} tiene su propio presupuesto, JOBS_RATE_LIMIT_MAX solicitudes por minuto (5000 por defecto). /health y /metrics no se limitan.

Tiempo de espera de la solicitud: 5 minutos (300 segundos).

Formato de salida

El conversor genera imágenes WebP (no PNG), lo que proporciona archivos un 30-50% más pequeños con soporte de canal alfa.

Se producen tres variantes para cada fondo:

Variant Width Quality Propósito
full Original 60-80% Fondo del recinto a resolución completa
preview 1000px 60-80% Previsualización de carga rápida para el render inicial
blurred 400px 30-50% Marcador de posición con datos base64 incorporados para la carga progresiva

Los archivos se almacenan en backgrounds/{filename}/{variant}.webp en el backend de almacenamiento configurado.

Docker

El servicio se ejecuta en un contenedor Docker basado en node:22.14.0-alpine con Chromium instalado para Puppeteer.

Puerto: 3000

Comprobación de estado: GET /health cada 30 segundos (3 reintentos, período de inicio de 5 segundos)

Variables de entorno para Puppeteer:

  • PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true (usa el Chromium del sistema)
  • PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser

Seguridad: Se ejecuta como usuario non-root (uid 10001). Usa tini como proceso init para evitar procesos zombie.

Monitorización

Métricas de Prometheus

El endpoint /metrics expone:

  • http_request_duration_seconds — histograma con etiquetas method, route, status_code
  • http_requests_total — contador con etiquetas method, route, status_code
  • Métricas por defecto del proceso de Node.js (memoria, CPU, latencia del event loop, GC)

Los buckets de métricas están optimizados para cargas de procesamiento de imágenes: 0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 30 segundos.

Endpoints de estado

  • GET /health — devuelve 200 si el servicio está en ejecución
  • GET /health/redis — devuelve el estado de la conexión con Redis, el role de conversión del pod y las profundidades de las colas pending y processing

Métodos de configuración

La aplicación se puede configurar mediante varios métodos, enumerados en orden de prioridad:

  1. Variables de entorno
  2. Kubernetes ConfigMap/Secrets
  3. Archivo .env (solo para desarrollo)

Despliegue en Kubernetes

Al desplegar en Kubernetes, la configuración se gestiona mediante valores de Helm y secretos:

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

Gestión de secretos

Las credenciales de AWS y las contraseñas de Redis deben gestionarse de forma segura mediante uno de estos métodos:

  • Kubernetes Secrets (inyectados a través de seatmap-helm-secrets)
  • External Secrets Operator
  • IAM roles for Service Accounts (IRSA)

Configuración para desarrollo

Para el desarrollo local usando Docker Compose:

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

Esto inicia el servicio del conversor en el puerto 3000 y una instancia de Redis en el puerto 6379.

Nunca incluyas credenciales sensibles en el control de versiones.

Resolución de problemas

Problemas de configuración comunes:

  • Fallo al subir la imagen: Comprueba MAX_CONTENT_LENGTH y la configuración del tamaño del cuerpo en el ingress. El límite por defecto de 16 MB puede necesitar un aumento para archivos SVG grandes.
  • Acceso denegado a S3: Verifica las credenciales de AWS y los permisos del bucket. Comprueba que AWS_PUBLIC_BASE_URL coincide con el esquema de URL real de tu bucket.
  • Imágenes en blanco o corruptas: Verifica que Chromium esté instalado (/usr/bin/chromium-browser). En Docker, asegúrate de que la variable de entorno PUPPETEER_EXECUTABLE_PATH esté configurada correctamente.
  • Uso elevado de memoria: Sharp y Puppeteer pueden consumir mucha memoria con archivos SVG grandes. Establece límites de memoria del contenedor de al menos 512 MB, con 2 GB recomendados para producción.
  • WebP no se carga en el renderer: Asegúrate de que las cabeceras CORS estén configuradas en el bucket de S3. El renderer necesita acceso de origen cruzado para cargar las imágenes de fondo.
  • URL públicas incorrectas: Si usas un proveedor de S3 distinto de AWS, define AWS_PUBLIC_BASE_URL de forma explícita. La generación de URL por defecto asume el estilo de bucket virtual-hosted de AWS.

Escriba una función, un ajuste, un endpoint o un método del SDK.