En Mi Local Funciona

El blog técnico de knowmad mood para apasionados de la tecnología, la IA, el desarrollo de software, la arquitectura y las buenas prácticas en proyectos reales.

Primeros pasos con n8n (Parte 5) : Modo Queue

Publicado por Víctor Madrid el

Arquitectura de SolucionesAutomatizaciónWorkflowsAgente IAOrquestaciónDockern8nEscalabilidadModo queueArquitectura Distribuidawebworkers

En este quinto artículo vamos a explicar todos los aspectos relacionados con el funcionamiento de la herramienta n8n en modo distribuido, con concurrencia y cuáles son sus diferentes opciones.

A modo de recordatorio pongo los enlaces a los artículos que se irán relacionando :

Este es el índice que se va a utilizar para estructurar este artículo

  • 1. Introducción
  • 2. Stack Tecnológico
  • 3. Ejemplos de Uso
  • 4. Conclusiones

1. Introducción

En este apartado se tratarán los siguiente puntos :

  • 1.1. Situación actual
  • 1.2. Modos de funcionamiento de n8n
  • 1.3. Propuesta de diseño de arquitectura modo queue
  • 1.4. Configuración necesaria

1.1. Situación actual

En la actualidad, nos encontramos en un momento en el que las empresas son cada vez más dependientes de las automatizaciones y tienen por objetivos: optimizar sus procesos y mejorar la eficiencia operativa.

Source: Lexica Aperture v3.5 (Model)

Debido a estas necesidades, los flujos de trabajo (workflows) están creciendo enormemente tanto en volumen (número) como en complejidad. Esto nos lleva a la situación en la que los sistemas actuales no son capaces de cubrir estas nuevas necesidades, en vez de convertirse en una solución se están convirtiendo en el nuevo cuello de botella ... en un problema ☹️.

Por otro lado, tenemos que tener en cuenta que el contexto actual también exige la aparición de la escalabilidad y el alto rendimiento.

La solución para ello es el modo queue de n8n.

Aclaración: La ejecución n8n en modo "normal" o "standalone" se realiza con una única instancia. Este planteamiento puede generar problemas de rendimiento, sobrecarga y fallos del sistema cuando se enfrenta a estas nuevas necesidades.

1.2. Modos de funcionamiento de n8n

La herramienta n8n tienes 2 modos de funcionamiento:

  • Modo normal (standalone)
  • Modo cola (queue)

Modo normal (standalone)

Este modo ejecuta los workflows de forma directa y síncrona.

Consideraciones:

  • Se trata del modo de funcionamiento básico y el que tiene activado por defecto la herramienta
  • Todas las tareas son procesadas por un único servidor
    • Limita el nº de workflows ejecutados a la vez
      • Caso 1: Sin configuración específica
        • Este límite lo establecen los recursos del sistema
      • Caso 2: Con configuración específica
        • Se puede establecer un límite de concurrencia para las ejecuciones N8N_CONCURRENCY_PRODUCTION_LIMIT
    • Uso compartido de recursos limitados
    • Escalabilidad limitada
  • No tiene separación entre el proceso principal y el proceso trabajador (worker)
    • Todo lo hace la misma instancia
  • Ejecución directa de workflows
    • Cada flujo de trabajo se ejecuta inmediatamente
    • Un fallo puede afectar a todo el proceso
  • Presenta problemas de rendimiento según:
    • La cantidad de workflows que se ejecuten a la vez
    • La complejidad de los workflows ejecutados
    • El tiempo de ejecución de workflows cuando estos son complejos o bien realizan muchas tareas
    • Los recursos disponibles para n8n
  • Requiere aplicar estrategias para el uso eficiente de los recursos

El propio fabricante explica en su página web que una sola instancia de n8n con una configuración bastante "normalita" para grandes cargas de trabajo es capaz de ejecutar hasta 220 workflows por segundo

  • Hardware: ECS c5a.large instance (4GB RAM)
  • Configuración n8n: Única instancia en modo normal con una base de datos Postgres
  • Workflow utilizado: Operativa básica con un nodo Webhook Trigger y un nodo de edición de un campo
Performance and benchmarking | n8n Docs
n8n performance and resource consumption benchmarking.

Modo cola (queue)

Este modo ejecuta los workflows de forma asíncrona gracias al uso de una cola de mensajes y a la distribución de la carga

Consideraciones:

  • Se trata del funcionamiento distribuido de la herramienta
  • Requiere una configuración específica den8n
  • Requiere añadir una infraestructura específica para este funcionamiento
    • Sistema de colas de mensajes: Componente software donde son encolados los workflows. (Ejemplo: Redis)
    • Workers: Instancias de tipo trabajador de n8n que son las encargadas de la ejecución de los workflows
  • Establece una arquitectura master-worker con un proceso principal y varios procesos del tipo trabajador ("worker")
  • Optimiza el uso de los recursos
    • Las tareas se distribuyen entre varios servidores worker
    • Facilita el procesamiento en paralelo
    • Asignación dinámica de los recursos (CPU y memoria)
    • Reducción del riesgo a tener un cuello de botella
  • Mejora la tolerancia a fallos de los workflows
  • Mejorar la escalabilidad horizontal
    • Se pueden añadir o quitar instancias worker según la carga de trabajo
  • Mejora la disponibilidad de la herramienta
    • Si un worker falla, otros pueden continuar procesando workflows desde la cola
    • Los workflows de larga duración no bloquean el servidor principal
    • Se mejora la tolerancia a fallos

Referencias:

Configuring queue mode | n8n Docs
Documentation for n8n, a workflow automation platform.

1.3. Propuesta de diseño de arquitectura modo queue

Importante: Toda la configuración que se enseñará en este punto deberá de variar según las características de las máquinas host utilizadas

Componentes de la propuesta de arquitectura:

  • Instancia Principal / Main Instance
  • Instancias Trabajador / Worker Instances
  • Cola de Mensajes / Message Broker
  • Base de datos / Database

Instancia Principal / Main Instancia

Características:

  • Se trata de la instancia principal de n8n
  • Requiere configuración para indicar que está funcionando en modo "queue"
  • Comparte con las instancias workers tanto las instancias de base de datos (Postgres) como la del message broker (Redis)
  • Funcionamiento respecto a los trabajos (ejecuciones de worflows)
    • Los workflows, credenciales y ejecuciones serán guardados en la base de datos
    • Esta instancia ya no ejecuta los trabajados directamente, sino que los encola y los envía al message broker (Redis). Posteriormente estos trabajos se deberían de distribuir entre las instancias worker y deberían de ser ellas las que se encarguen de ejecutar los trabajos.
    • EXCEPCIÓN: Esta instancia SÍ que ejecuta los trabajos manuales (tienen el trigger de disparo manual)
      • Los trabajos enviados a las instancias workers deberían de tener disparadores (triggers) diferentes del tipo manual
    • La instancia principal si que gestiona los temporizadores y llamadas a webhooks pero NO sus ejecuciones
  • Esta instancia proporciona las siguientes funcionalidades más importantes:
    • Editor UI: Proporciona la interfaz de usuario de la herramienta.
      • Desde esta parte se pueden implementar manualmente los workflows, configurar las credenciales, ejecutar manualmente, monitorizar el estado de las ejecuciones, etc.
    • APIs Internas (Internal API) : Proporciona el conjunto de APIs que facilitan la gestión y las operativas sobre los workflows.
      • Desde esta parte se puede crear un nuevo worflow, obtener la implementación de un workflow, etc.
    • Escucha para Webhooks: Proporciona el listening para controlar las invocaciones que se puedan realizar de forma externa.

Instancias Trabajador / Worker Instances

Características

  • Se trata de la instancia de ejecución de trabajos de n8n
  • Puede estar compuesto por una o varias instancias
    • Hay que determinar de inicio cuantos workers serán necesarios
    • Posteriormente esto se irá adaptando
  • Requiere configuración a nivel de instancia para indicar que está funcionando en modo "worker"

Para que una instancia sea del tipo worker hay que arrancar la instancia con el comando:

worker
  • Requiere configuración a nivel de número de tareas concurrentes que una instancia de este tipo puede controlar
    • Por defecto su valor es 10

Para que una instancia sea del tipo worker se configure con otro número de tareas concurrente hay que arrancar la instancia con siguiente comando:

# Ejemplo de instancia worker con 20 tareas concurrentes configuradas
worker --concurrency 20 
  • Comparten con la instancia principal tanto las instancias de base de datos (Postgres) como la del message broker (Redis)
  • Funcionamiento respecto a los trabajos (ejecuciones de worflows)
    • Reciben los trabajos desde la instancia principal a través del message broker según un mecanismo de distribución estos son asignados a las instancias worker disponibles
    • Cada instancia worker recibe y ejecuta los trabajos asignados
      • Dependerá de los recursos HW de la máquina host

Cola de mensaje / Message Broker

Características:

  • Plataforma de mensajería encargada de almacenar los trabajos para tenerlos en espera y distribuirlos por los workers disponibles
  • Se utilizará Redis
  • Se puede mejorar el rendimiento mediante cambios en la configuración
  • Funcionamiento respecto a los trabajos (ejecuciones de worflows)
    • Recibe las solicitudes de ejecución de workflows desde la instancia principal y las encola en la plataforma
  • Para ello proporciona una lista de posibles parámetros que se pueden cambiar
  • IMPORTANTE: Recordar que los parámetros que se pueden cambiar dependen de los recursos disponibles (HW y SW) de la máquina host
  • Se aconseja revisar la documentación de la versión de base de datos utilizada
Key eviction
Overview of Redis key eviction policies (LRU, LFU, etc.)
  • Se pueden modificar desde la sección de "command" de la imagen Docker
  • La imagen de Docker no incorpora muchas opciones variables de entorno que se puedan cambiar

Base de datos / Database

Características:

  • Componente encargado de la persistencia de workflows, credenciales y ejecuciones
  • Se utilizará PostgreSQL
  • Se puede mejorar el rendimiento mediante cambios en la configuración
  • Para ello proporciona una lista de posibles parámetros que se pueden cambiar
  • IMPORTANTE: Recordar que los parámetros que se pueden cambiar dependen de los recursos disponibles (HW y SW) de la máquina host
  • Se aconseja revisar la documentación de la versión de base de datos utilizada
20.4. Resource Consumption
20.4. Resource Consumption # 20.4.1. Memory 20.4.2. Disk 20.4.3. Kernel Resource Usage 20.4.4. Cost-based Vacuum Delay 20.4.5. Background Writer 20.4.6. Asynchronous …
  • Se pueden modificar desde la sección de "command" de la imagen Docker
  • La imagen de Docker incorpora una serie de variables de entorno que se pueden cambiar, que afecta a ciertos aspectos anteriores. Por ejemplo:
POSTGRES_SHARED_BUFFERS
  • Se puede mejorar la infraestructura de base de datos añadiendo una lista de base de datos réplicas y un gestor para esto.
    • Se pueden tener múltiples instancias de base de datos
    • Facilita la replicación y el balanceo de carga entre instancias
    • Es una arquitectura mucho más compleja
    • Dificultada encontrar errores y el mantenimiento

1.4. Configuración necesaria

Existen varios aspectos de configuración particular de los workers:

  • Establecer la clave de encriptación
  • Establecer el modo de ejecución
  • Establecer la integración con Redis
  • Establecer la integración con Postgres
  • Establecer la configuración específica de las instancias workers
  • Establecer el número de instancias workers

Establecer la clave de encriptación

La primera vez que se arranca n8n genera un clave de encriptación en directorio de instalación (Por ejemplo: /home/node/.n8n) en concreto en el fichero config

Ejemplo de fichero config con la clave generada:

{
	"encryptionKey": "KmJA0TeA1FMWd298tSPGyxYHd8Jh5hvW"
}

En primer lugar y antes de hacer nada con n8n, podemos decidir hacer uso de esta clave generada o bien podemos personalizar la clave poniendo una nuestra.

Ejemplo de clave de encriptación personalizada:

{
	"encryptionKey": "changeEncryptionKey"
}

Con esta clave que parece de "broma" quedarían encriptadas nuestras credenciales, etc.

Una vez elegida la clave de encriptación que utilizaremos tenemos que decidir varias cosas:

  • Si se van a utilizar varias instancias principales hay que decidir:
    • Si queremos que cada una tenga su propia clave de encriptación
    • Si queremos que compartan la misma clave de encriptación
      • Esta será establecida vía fichero de configuración o vía variable de entorno
  • Si se va a utilizar una arquitectura distribuida (como la explicada en el punto anterior)
    • La clave de encriptación de la instancia principal debería de ser replicada en todas las instancias trabajadoras
        • Esta será establecida vía fichero de configuración o vía variable de entorno
Es muy importante utilizar la misma clave de encriptación en las instancias trabajadoras para poder acceder a las credenciales almacenadas en la base de datos de forma correcta

Ejemplo de variable de entorno:

export N8N_ENCRYPTION_KEY=KmJA0TeA1FMWd298tSPGyxYHd8Jh5hvW

Establecer el modo de ejecución

Se debería de establecer el modo queue en la instancia principal y las instancias workers.

  • Este será establecido vía fichero de configuración o vía variable de entorno
export EXECUTIONS_MODE=queue

Establecer la integración con Redis

Se debería de establecer la comunicación Redis desde la instancia principal y las instancias workers.

  • Este será establecido vía fichero de configuración o vía variable de entorno
export QUEUE_BULL_REDIS_HOST=redis
export QUEUE_BULL_REDIS_PORT=6379
  • QUEUE_BULL_REDIS_HOST=redis: Define la dirección del servidor Redis
  • QUEUE_BULL_REDIS_PORT=6379: Especifica el puerto de Redis (por defecto 6379)

Existen más variables que se pueden utilizar para la configuración de Redis

Queue mode environment variables | n8n Docs
Environment variables to configure queue mode on your self-hosted n8n instance.

Establecer la integración con Postgres

Este apartado ya se vio en el artículo "Primeros pasos con n8n (Parte 2) : Uso de Base de Datos".

Esta configuración tiene ser usada en la instancia principal y en todas las instancias workers.

Establecer la configuración específica de las instancias workers

Las instancias worker necesitan tener en cuenta varios aspectos de configuración:

  • Deberían de tener la misma versión que la instancia principal
  • Tener acceso a la base de datos Postgres y a Redis
  • Ser arrancadas como procesos worker, para ello se modificará su arranque para ejecutar el comando:
n8n worker
  • Habilitar aspectos que puede ayudar a conocer el estado de funcionamiento de estas instancias
    • Activar la variable QUEUE_HEALTH_CHECK_ACTIVE como true permite habilitar el endpoint de /healthz que retorna si la instancia worker está "levantada"
  • Establecer la concurrencia sobre el número de trabajos que pueden ejecutar en paralelos
    • Por defecto su valor es de 10
    • Se aconseja usar concurrencia >= 5
n8n worker --concurrency=6

Establecer el número de instancias worker

Para probar con instancias worker en Docker de forma sencilla existen 2 opciones:

Opción 1: Añadir la sección deploy en el fichero Docker compose

deploy:
  replicas: 3

Opción 2: Solicitar la creación de replicas desde la ejecución del fichero Docker Compose

docker-compose up --scale worker=3

2. Stack Tecnológico

Este es el stack tecnológico elegido para implementar la funcionalidad "n8n":

  • Docker - Tecnología de Contenedores/Containers
  • Docker Hub - Repositorio de Docker Público donde se ubican las imágenes oficiales
  • n8n - Herramienta de automatización de procesos + Agentes IA
  • PostgreSQL - Base de Datos relacional (Versión 16)
  • Redis - Cola de mensajes / Caché / Estructura de Datos de alto rendimiento (Versión 7)

3. Ejemplos de Uso

Para enseñar a utilizar todo lo que se ha explicado y así practicar se ha habilitado un repositorio, este repositorio se reutilizará para otros artículos de la serie "Primeros pasos con n8n".

Aclaración: Todos los ejemplos que se han usado hasta ahora han sido utilizando n8n en modo "normal", es decir, con ejecución síncrona de los workflows.

La parte que tiene que ver con los ejemplos que se mostrarán en este apartado se encuentran en el apartado del repositorio example/queue

Para que n8n funcione en modo "queue" necesitamos varias cosas:

  • Añadir la base de datos PostgresSQL para persistencia
  • Añadir el message broker Redis para que funcione como cola de mensajes
  • Añadir instancias workers
  • Cambios en la configuración de n8n específicos para este modo
  • Optimización de configuración para mejorar el rendimiento
  • Configuración específica a nivel de recursos utilizados: memoria, CPU, etc

Ejemplos de uso que se explicarán:

  • 3.1. Preparar la cola de mensajes Redis (Opcional)
  • 3.2. Ejemplo Ad-hoc
  • 3.3. Ejemplo con ficheros de variables de entorno
  • 3.4. Ejemplo con simplificación de workers
  • 3.5. Ejemplo con configuración de recursos

3.1. Preparar la cola de mensajes Redis (Opcional)

Este apartado es opcional. Para asegurarnos de que la integración se realiza con pocos problemas. Trataremos de hacer primero un análisis básico de la infraestructura de Redis que utilizaremos en Docker y nos aseguraremos de que funciona correctamente.

Para su realización seguiremos una serie de pasos:

  • Paso 1: Creación del fichero Docker Compose
  • Paso 2: Verificación de la herramienta Redis

Paso 1: Creación del fichero Docker Compose

Este ejemplo esta descrito en el fichero : QUEUE-docker-compose-prepare-redis.yml

Para ver como lanzarlo, ver fichero README dentro del de la sección "queue".

Este ejemplo se puede lanzar individualmente desde Docker Compose o bien se ha montado una herramienta de gestión rápida sobre tecnología Makefile que ayudará en la gestión de la plataforma formativa de n8n.

Crearemos un fichero Docker Compose que se componga de los siguiente elementos:

Redis

Cola de mensajes / Message Broker / Estructura de Datos

  • Se establece la imagen con una versión establecida: redis:7.4.2
    • Esta es la versión con la que configuró la imagen
    • Se aconseja estar actualizado a nivel de la herramienta
  • Se define el nombre del contenedor como: redis
  • Se define la política de restart
  • Se define la red en la que se ubicará : demo
    • En este caso se trata de una red privada
  • En este caso no se realiza ninguna configuración mediante variables de entorno
  • Se define el puerto:
    • 6379 para las peticiones a la cola de mensajes
  • Se definen los volúmenes del contenedor:
    • Se mapea el directorio de configuración de redis
  • Control de healthcheck para saber si esta disponible

Redis-Insight

Plataforma web de gestión y visualización de Redis

  • Se establece la imagen con una versión establecida: redis/redisinsight:2.66.0
    • Esta es la versión con la que configuró la imagen
    • Se aconseja estar actualizado a nivel de la herramienta
  • Se define el nombre del contenedor como: redis-insight
  • Se define la política de restart
  • Se define la red en la que se ubicará : demo
    • En este caso se trata de una red privada
  • En este caso no se realiza ninguna configuración mediante variables de entorno
  • Se define el puerto:
    • 5540 para acceso al portal web
  • Se definen los volúmenes del contenedor:
    • Se mapea el directorio de configuración de redis-insight

Ejemplo de fichero QUEUE-docker-compose-prepare-redis.yml

# Use Case: Prepare Redis
services:

  # Project URL: https://github.com/redis/redis
  # Docs URL: https://redis.io/docs/latest/
  redis:
    #image: redis:7.2.7-alpine
    image: redis:7.4.2
    container_name: "redis"
    restart: always
    networks: ['demo']
    ports:
      - 6379:6379
    volumes:
      # *** Volume configuration ***
      - ./redis-data:/data
    healthcheck:
      test: ['CMD', 'redis-cli', 'ping']
      interval: 5s
      timeout: 5s
      retries: 10

  # Project URL: https://github.com/RedisInsight/RedisInsight
  # Docs URL: https://redis.io/docs/latest/develop/tools/insight/
  redis-insight:
    image: redis/redisinsight:2.66.0
    container_name: "redis-insight"
    restart: always
    networks: ['demo']
    ports:
      - 5540:5540
    volumes:
      - ./redis-insight-data:/data

volumes:
  redis-data:
  redis-insight-data:

networks:
  demo:
    name: demo
    driver: bridge

Arrancar el fichero docker compose.

docker compose -f QUEUE-docker-compose-prepare-redis.yml up -d

Paso 2: Verificación de la herramienta Redis

Verificaremos que podemos acceder a Redis desde Redis-Insigh

Pasos a seguir:

  • Verificar que se encuentran disponibles los 2 contenedores
    • redis
    • redis-insight
  • Acceder a la plataforma de redis-insight: http://localhost:5540/
  • Aceptar los términos del contrato y pulsar sobre el botón "Submit"
  • Se accederá a la pantalla de configuración de Redis
  • Pulsar sobre el botón "Add Redis database"
  • Se mostrará el siguite modal de configuración:
    • Podriamos crear la conexión cambiando la parte marcada en azul por el nombre del contenedor de redis ("redis") en el campo de conexión de la URL.
    • Pero pulsaremos sobre el botón "Connection Settings"
  • Como es una conexión básica a Redis, nos valdrá con cambiar el nombre del host por : redis
  • Realizaremos un test de conexión
  • Si es correcto pulsaremos sobre el botón "Add Redis Database"
  • Se mostrará una pantalla de gestión de las colas de mensaje / base de datos Redis
  • Pulsaremos sobre la que acabamos de crear

Se mostrará la información para la monitorización de la herramienta.

3.2. Ejemplo Adhoc

Este ejemplo esta descrito en el fichero : QUEUE-docker-compose-01.yml

Es un ejemplo en el que se van a montar todos los componentes integrados para proporcionar la plataforma distribuida y de alto rendimiento.

La configuración de los elementos utilizados se definirá de forma particular en cada uno de ellos.

En este ejemplo NO se va a realizar configuración relacionada con la reserva de recursos

Para su realización seguiremos una serie de pasos:

  • Paso 1: Creación de un fichero .env
  • Paso 2: Creación de un fichero de inicialización de base de datos "init-data.sh"
  • Paso 3: Creación del fichero Docker Compose
  • Paso 4: Verificación de la herramienta PostgreSQL

Paso 1: Creación de un fichero .env

Crearemos un fichero .env con las variables de entorno que usaremos para establecer la configuración de la base de datos PostgreSQL y de n8n

Se ha facilitado un fichero .env de ejemplo que lleva por nombre .env.example. Para hacerlo funcionar bastaría con crear una copia del fichero en esta ubicación y eliminar la parte del ".env"
### --- N8N SETTINGS ---

ENCRYPTION_KEY=KmJA0TeA1FMWd298tSPGyxYHd8Jh5hvW

### --- POSTGRESQL SETTINGS ---

DB_POSTGRES_USER=admin
DB_POSTGRES_PASSWORD=admin
DB_POSTGRES_DB=n8ndb

DB_POSTGRES_NON_ROOT_USER=n8n
DB_POSTGRES_NON_ROOT_PASSWORD=n8n

Estas variables de entorno están establecidas de forma custom a nivel de nombre por mí y no por el fabricante de PostgreSQL (variables de entorno) ni por n8n. A nivel de comportamiento funcional serán iguales que las del fabricante. Se ha pensado así para que puedan ser compartidas por los diferentes contenedores ya que pueden ser referenciados con otros valores a nivel de nombre.

Por ejemplo: DB_POSTGRES_DB

  • La variable de entorno referenciada desde el contenedor de postgres será POSTGRES_DB
  • La variable de entorno referenciada desde el contenedor de n8n será DB_POSTGRESDB_DATABASE

Los valores que aquí aparecen pueden ser cambiados sin problemas.

Paso 2: Creación de un fichero de inicialización de base de datos "init-data.sh"

Importante: Esta estrategia esta pensada para funcionar con PostgresSQL

Crearemos un fichero de script "init-data.sh" ejecutable con los valores o configuración que queremos que tenga la base de datos en su inicio.

Consideraciones:

  • Se ejecuta la primera vez que se inicia el contenedor con datos vacíos
  • Se utiliza para cargar configuración inicial o bien carga de datos de prueba
  • Compatible con varios motores de base de datos en Docker

Este script de shell se suele colocar dentro de docker-entrypoint-initdb.d/ y se utiliza para ejecutar comandos personalizados durante la inicialización del contenedor.

#!/bin/bash
set -e;

if [ -n "${POSTGRES_NON_ROOT_USER:-}" ] && [ -n "${POSTGRES_NON_ROOT_PASSWORD:-}" ]; then
	psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
		CREATE USER ${POSTGRES_NON_ROOT_USER} WITH PASSWORD '${POSTGRES_NON_ROOT_PASSWORD}';
		GRANT ALL PRIVILEGES ON DATABASE ${POSTGRES_DB} TO ${POSTGRES_NON_ROOT_USER};
		GRANT CREATE ON SCHEMA public TO ${POSTGRES_NON_ROOT_USER};
	EOSQL
else
	echo "ERROR: No Environment variables"
fi
Como este script se ejecuta dentro del contenedor tendrá que tener disponible las variables de entorno utilizadas dentro del propio contenedor. Se tratarán de pasar desde el propio fichero docker-compose

Con este script se pretende crear un usuario con permisos de administrador pero diferente del usuario tipo admin principal de la base de datos. Con ello, crearemos un usuario de servicio para n8n.

Los valores de las variables de entorno vendrán dados por el fichero .env anterior.

Importante: Este fichero tiene que tener permisos de ejecución.

Paso 3: Creación del fichero Docker Compose

Crearemos un fichero Docker Compose que se componga de los siguiente elementos:

Base de datos Postgresql

Base de datos relacional que utilizaremos

  • Se establece la imagen con una versión establecida: postgres:16
    • Esta es la versión con la que se configuró la imagen
    • Se aconseja estar actualizado a nivel de la herramienta
  • Se define el nombre del contenedor como: n8n-postgres
  • Se define la política de restart
  • Se define la red en la que se ubicará: demo
    • En este caso se trata de una red privada
  • Se establecen algunas variables de entorno que ayudarán a la configuración de la herramienta
    • En este caso se define datos de autenticación del usuario tipo admin, nombre del esquema de base de datos y otra información
    • Se podrían establecer variables de entorno para el Tunning de la base de datos con configuraciones como: máximo numero de trabajadores paralelos, memoria reservada para trabajo, etc.
  • Se define el puerto:
    • 5432 para conexión de base de datos
  • Se definen los volúmenes del contenedor:
    • Se establece la referencia del fichero de inicialización de base de datos externo con su valor interno dentro del contenedor : init-data.sh
    • Se mapea el directorio de configuración de postgres
  • Control de healthcheck para saber si esta disponible
  # Project URL: https://github.com/postgres/postgres
  # Docs URL: https://www.postgresql.org/docs/16/index.html
  n8n-postgres:
    image: postgres:16
    container_name: "n8n-postgres"
    restart: always
    networks: ['demo']
    environment:
      # *** Settings ***
      - POSTGRES_USER=${DB_POSTGRES_USER}
      - POSTGRES_PASSWORD=${DB_POSTGRES_PASSWORD}
      - POSTGRES_DB=${DB_POSTGRES_DB}
      - POSTGRES_NON_ROOT_USER=${DB_POSTGRES_NON_ROOT_USER}
      - POSTGRES_NON_ROOT_PASSWORD=${DB_POSTGRES_NON_ROOT_PASSWORD}
      # *** Tunnings  ***
      # High performance and scalability
      # IMPORTANT: Depends on the host machine
      #- POSTGRES_WORK_MEM=64MB
      #- POSTGRES_SHARED_BUFFERS=2GB
      #- POSTGRES_MAINTENANCE_WORK_MEM=1GB
      #- POSTGRES_EFFECTIVE_CACHE_SIZE=2GB
      #- POSTGRES_MAX_PARALLEL_WORKERS=10
      #- POSTGRES_MAX_PARALLEL_WORKERS_PER_GATHER=5
    ports:
      - 5432:5432
    volumes:
      # *** Database Initializer ***
      - ./init-data.sh:/docker-entrypoint-initdb.d/init-data.sh
      # *** Volume configuration ***
      - ./postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -h localhost -U ${DB_POSTGRES_NON_ROOT_USER} -d ${DB_POSTGRES_DB}']
      interval: 5s
      timeout: 5s
      retries: 10

Cola de mensajes Redis

Componente de cola de mensajes

  • Se establece la imagen con una versión establecida: redis:7.4.2
    • Esta es la versión con la que configuró la imagen
    • Se aconseja estar actualizado a nivel de la herramienta
  • Se define el nombre del contenedor como: n8n-redis
  • Se define la política de restart
  • Se define la red en la que se ubicará : demo
    • En este caso se trata de una red privada
  • En este caso no se realiza ninguna configuración mediante variables de entorno
  • Se define el puerto:
    • 6379 para las peticiones a la cola de mensajes
  • Se definen los volúmenes del contenedor:
    • Se mapea el directorio de configuración de redis
  • Control de healthcheck para saber si esta disponible
  # Project URL: https://github.com/redis/redis
  # Docs URL: https://redis.io/docs/latest/
  n8n-redis:
    image: redis:7.4.2
    container_name: "n8n-redis"
    restart: always
    networks: ['demo']
    ports:
      - 6379:6379
    volumes:
      # *** Volume configuration ***
      - ./redis-data:/data
    healthcheck:
      test: ['CMD', 'redis-cli', 'ping']
      interval: 5s
      timeout: 5s
      retries: 10

Instancia Principal de N8n

  • Se establece la imagen de n8n con una versión establecida: n8n:1.82.1
    • Esta es la versión con la que se configuró la imagen
    • Se aconseja estar actualizado a nivel de la herramienta
  • Se define el nombre del contenedor como: n8n-main
  • Se define la política de restart
  • Se define la red en la que se ubicará: demo
    • En este caso se trata de una red privada
  • Se establecen algunas variables de entorno que ayudarán a la configuración de la herramienta
    • En este caso se define el "time zone" que es un aspecto importante para la monitorización y el logging
    • Aspectos generales de la herramienta
    • Configuración de la base de datos Postgres
    • Configuración del modo queue
    • Configuración relacionada con la optimización de las ejecuciones
    • Nota: Estas variables puede cambiar de una versión a otra
  • Se define el puerto:
    • 5678 para las peticiones HTTP a la interfaz Web
  • Se definen los volúmenes del contenedor:
    • Se mapea el socket de Docker
      • Requerido por el uso del provider de Docker
      • Define un bind mount para que el socket de comunicación con Docker esté disponible para el contenedor
    • Se mapea el directorio de configuración de n8n
    • Se mapea otro directorio dentro del contenedor que no sea oculto
  • Dependencia de carga respecto a los contenedores: n8n-postgres y n8n-redis
# Project URL: https://github.com/n8n-io/n8n
  # Docs URL: https://docs.n8n.io/
  n8n-main:
    image: n8nio/n8n:1.82.1
    container_name: "n8n-main"
    #container_name: "${PROJECT_NAME}n8n-main"
    restart: always
    networks: ['demo']
    environment:
      # *** General ***
      - GENERIC_TIMEZONE=Europe/Madrid
      - TZ=Europe/Madrid
      - NODE_ENV=production
      # *** Settings ***
      - N8N_SECURE_COOKIE=false
      - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
      - N8N_RUNNERS_ENABLED=true
      - N8N_ENCRYPTION_KEY=${ENCRYPTION_KEY}
      # *** Database ***
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=n8n-postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=${DB_POSTGRES_DB}
      - DB_POSTGRESDB_USER=${DB_POSTGRES_NON_ROOT_USER}
      - DB_POSTGRESDB_PASSWORD=${DB_POSTGRES_NON_ROOT_PASSWORD}
      # *** Queue Mode ***
      - EXECUTIONS_MODE=queue
      - EXECUTIONS_QUEUE=redis
      - QUEUE_BULL_REDIS_HOST=n8n-redis
      - QUEUE_BULL_REDIS_PORT=6379
      - QUEUE_HEALTH_CHECK_ACTIVE=true
      # *** Executions ***
      - EXECUTIONS_DATA_PRUNE=true
      - EXECUTIONS_DATA_MAX_AGE=336
      - EXECUTIONS_DATA_PRUNE_MAX_COUNT=10000
      - N8N_CONCURRENCY_PRODUCTION_LIMIT=10
      - N8N_WORKER_CONCURRENCY=10
      - N8N_CONCURRENCY_PRODUCTION_LIMIT=10
    ports:
      - 5678:5678
    links:
      - n8n-postgres
    volumes:
      # *** General configuration ***
      - /var/run/docker.sock:/var/run/docker.sock:ro # Access to Docker on host machine
      # *** Volume configuration ***
      - ./queue01-n8n-data/n8n:/home/node/.n8n
      - ./queue01-n8n-data/other:/home/node/
    depends_on:
      - n8n-postgres
      - n8n-redis

Instancia Worker de N8n

  • Se establece la imagen de n8n con una versión establecida: n8n:1.82.1
    • Esta es la versión con la que se configuró la imagen
    • Se aconseja estar actualizado a nivel de la herramienta
  • Se define el nombre del contenedor como: n8n-worker
  • Se define la política de restart
  • Se define la red en la que se ubicará: demo
    • En este caso se trata de una red privada
  • Se establece en la sección command la ejecución de: worker
  • Se establecen algunas variables de entorno que ayudarán a la configuración de la herramienta
    • En este caso se define el "time zone" que es un aspecto importante para la monitorización y el logging
    • Aspectos generales de la herramienta
    • Configuración de la base de datos Postgres
    • Configuración del modo queue
    • Configuración relacionada con la optimización de las ejecuciones
    • Nota: Estas variables puede cambiar de una versión a otra
  • No se define ningún puerto
  • No se define ningún volumen
  • Dependencia de carga respecto a los contenedores: n8n-main
  • Se ha definido que se establezcan una cantida de replicas establecida por defecto: 3
n8n-worker:
    image: n8nio/n8n:1.82.1
    restart: always
    networks: ['demo']
    command: ["worker"]
    environment:
      # *** General ***
      - GENERIC_TIMEZONE=Europe/Madrid
      - TZ=Europe/Madrid
      - NODE_ENV=production
      # *** Settings ***
      - N8N_SECURE_COOKIE=false
      - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
      - N8N_RUNNERS_ENABLED=true
      - N8N_ENCRYPTION_KEY=${ENCRYPTION_KEY}
      # *** Database ***
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=n8n-postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=${DB_POSTGRES_DB}
      - DB_POSTGRESDB_USER=${DB_POSTGRES_NON_ROOT_USER}
      - DB_POSTGRESDB_PASSWORD=${DB_POSTGRES_NON_ROOT_PASSWORD}
      # *** Queue Mode ***
      - EXECUTIONS_MODE=queue
      - EXECUTIONS_QUEUE=redis
      - QUEUE_BULL_REDIS_HOST=n8n-redis
      - QUEUE_BULL_REDIS_PORT=6379
      - QUEUE_HEALTH_CHECK_ACTIVE=true
      - OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS=true
      # *** Executions ***
      - EXECUTIONS_DATA_PRUNE=true
      - EXECUTIONS_DATA_MAX_AGE=336
      - EXECUTIONS_DATA_PRUNE_MAX_COUNT=10000
      - N8N_CONCURRENCY_PRODUCTION_LIMIT=10
      - N8N_WORKER_CONCURRENCY=10
      - N8N_CONCURRENCY_PRODUCTION_LIMIT=10
    depends_on:
      - n8n-main
    deploy:
      replicas: 3

Arracaremos el fichero Docker compose

docker compose -f QUEUE-docker-compose-01.yml up -d

Se podrá ver en los logs que se han configurado todos los elementos

3.3. Ejemplo con ficheros de variables de entorno

Este ejemplo esta descrito en el fichero : QUEUE-docker-compose-02.yml

Es un ejemplo similar al anterior pero con la diferencia que se ha desacoplado la configuración específica de variables de entorno de cada uno de los componentes en ficheros de variables de entorno independientes.

En este ejemplo NO se va a realizar configuración relacionada con la reserva de recursos
Estos ficheros de configuración podrán ser reutilizados para otros ejemplos

Para su realización seguiremos una serie de pasos:

  • Paso 1: Creación de la estructura para la configuración
  • Paso 2: Creación de los ficheros de configuración .env
  • Paso 3: Adaptar el fichero Docker Compose

Paso 1: Creación de la estructura para la configuración

Crearemos un directorio denominado "config" donde ubicaremos los ficheros de configuración específicos de cada uno de los componentes que lo requieran.

Posteriormente crearemos en su interior los directorios que necesitamos.

Paso 2: Creación de los ficheros de configuración .env

Crearemos el fichero de configuración con las variables de entorno de cada componente.

Paso 3: Adaptar el fichero Docker Compose

Se migrarán las variables de entorno que se consideren al fichero.

Se creará la configuración especifica para cada contenedor para que cada uno de ellos utilice su fichero de variables de entorno.

Ejemplo : Container original de Postgres antes de la migración

  # Project URL: https://github.com/postgres/postgres
  # Docs URL: https://www.postgresql.org/docs/16/index.html
  n8n-postgres:
    image: postgres:16
    container_name: "n8n-postgres"
    restart: always
    networks: ['demo']
    environment:
      # *** Settings ***
      - POSTGRES_USER=${DB_POSTGRES_USER}
      - POSTGRES_PASSWORD=${DB_POSTGRES_PASSWORD}
      - POSTGRES_DB=${DB_POSTGRES_DB}
      - POSTGRES_NON_ROOT_USER=${DB_POSTGRES_NON_ROOT_USER}
      - POSTGRES_NON_ROOT_PASSWORD=${DB_POSTGRES_NON_ROOT_PASSWORD}
      # *** Tunnings  ***
      # High performance and scalability
      # IMPORTANT: Depends on the host machine
      #- POSTGRES_WORK_MEM=64MB
      #- POSTGRES_SHARED_BUFFERS=2GB
      #- POSTGRES_MAINTENANCE_WORK_MEM=1GB
      #- POSTGRES_EFFECTIVE_CACHE_SIZE=2GB
      #- POSTGRES_MAX_PARALLEL_WORKERS=10
      #- POSTGRES_MAX_PARALLEL_WORKERS_PER_GATHER=5
    ports:
      - 5432:5432
    volumes:
      # *** Database Initializer ***
      - ./init-data.sh:/docker-entrypoint-initdb.d/init-data.sh
      # *** Volume configuration ***
      - ./postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -h localhost -U ${DB_POSTGRES_NON_ROOT_USER} -d ${DB_POSTGRES_DB}']
      interval: 5s
      timeout: 5s
      retries: 10

Se han extraído las variables consideradas al fichero de configuración.

Se han considerado que las variables relacionadas con el tunning sean especificas del contenedor y por tanto, de este caso concreto.

Ejemplo : Fichero de configuración de variables de entorno para Postgres ./config/postgres/postgres.env

POSTGRES_USER=${DB_POSTGRES_USER}
POSTGRES_PASSWORD=${DB_POSTGRES_PASSWORD}
POSTGRES_DB=${DB_POSTGRES_DB}

POSTGRES_NON_ROOT_USER=${DB_POSTGRES_NON_ROOT_USER}
POSTGRES_NON_ROOT_PASSWORD=${DB_POSTGRES_NON_ROOT_PASSWORD}

Ejemplo: Container original de Postgres después de la migración

  # Project URL: https://github.com/postgres/postgres
  # Docs URL: https://www.postgresql.org/docs/16/index.html
  n8n-postgres:
    image: postgres:16
    container_name: "n8n-postgres"
    restart: always
    networks: ['demo']
    env_file:
      - ./config/postgres/postgres.env
    #environment:
      # *** Tunnings  ***
      # High performance and scalability
      # IMPORTANT: Depends on the host machine
      #- POSTGRES_WORK_MEM=64MB
      #- POSTGRES_SHARED_BUFFERS=2GB
      #- POSTGRES_MAINTENANCE_WORK_MEM=1GB
      #- POSTGRES_EFFECTIVE_CACHE_SIZE=2GB
      #- POSTGRES_MAX_PARALLEL_WORKERS=10
      #- POSTGRES_MAX_PARALLEL_WORKERS_PER_GATHER=5
    ports:
      - 5432:5432
    volumes:
      # *** Database Initializer ***
      - ./init-data.sh:/docker-entrypoint-initdb.d/init-data.sh
      # *** Volume configuration ***
      - ./postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -h localhost -U ${DB_POSTGRES_NON_ROOT_USER} -d ${DB_POSTGRES_DB}']
      interval: 5s
      timeout: 5s
      retries: 10

Los cambios afectarón a los contenedores de: n8n-postgres, n8n-main y n8n-worker.

Arrancaremos el fichero Docker Compose

docker compose -f QUEUE-docker-compose-02.yml up -d

3.4. Ejemplo con simplificación de workers

Este ejemplo esta descrito en el fichero : QUEUE-docker-compose-03.yml

Es un ejemplo es similar al anterior pero con la diferencia que se ha desacoplado la configuración del contenedor de n8n-main y de n8n-worker utilizando una estrategia permitida por Docker.

En este ejemplo NO se va a realizar configuración relacionada con la reserva de recursos
Esta estrategia se puede reutilizar para otros ejemplos

Para su realización seguiremos una serie de pasos:

  • Paso 1: Creación de la parte común de los contenedores n8n-main y n8n-worker
  • Paso 2: Adaptación de los contenedores n8n-main y n8n-worker

Paso 1: Creación de la parte común de los contenedores n8n-main y n8n-worker

Crearemos una sección por encima de la sección de servicios del fichero Docker Compose

# *** N8N shared ***
# Project URL: https://github.com/n8n-io/n8n
# Docs URL: https://docs.n8n.io/
x-n8n: &service-n8n
  image: n8nio/n8n:1.82.1
  restart: always
  networks: ['demo']
  env_file:
    - ./config/n8n/n8n.env

Se ha establecido una identificador para esta sección: service-n8n

Esta sección tendrá los valores que se han considerado comunes para ambos contenedores. Se puede observar dos aspectos comunes de esta estrategia:

  • Dispondrán de la misma versión
  • Usarán el mismo fichero de configuración
En caso de utilizar ficheros de configuración diferentes habría que definir otras estrategias

Paso 2: Adaptación de los contenedores n8n-main y n8n-worker

Se adaptarán los contenedores hacer uso de la sección anterior

Para el contenedor n8n-main

  # Project URL: https://github.com/n8n-io/n8n
  # Docs URL: https://docs.n8n.io/
  n8n-main:
    <<: *service-n8n
    ports:
      - 5678:5678
    volumes:
      # *** General configuration ***
      - /var/run/docker.sock:/var/run/docker.sock:ro # Access to Docker on host machine
      # *** Volume configuration ***
      - ./queue01-n8n-data/n8n:/home/node/.n8n
      - ./queue01-n8n-data/other:/home/node/
    depends_on:
      - n8n-postgres
      - n8n-redis

Para el contenedor n8n-worker

  n8n-worker:
    <<: *service-n8n
    command: ["worker"]
    depends_on:
      - n8n-main
    deploy:
      replicas: 3

Como se peude ver con todos los cambios aplicados en los diferentes ejemplos, se reduce bastante la configuración y queda bastante claro.

Arrancaremos el fichero Docker Compose

docker compose -f QUEUE-docker-compose-03.yml up -d

3.5. Ejemplo con configuración de recursos

Este ejemplo esta descrito en el fichero : QUEUE-docker-compose-04.yml

Este un ejemplo es similar al anterior pero con la diferencia que ha añadido cierta configuración sobre el uso de recursos de cada uno de los contenedores gracias a la funcionalidad facilitada por docker desde la sección de "deploy" y su apartado de "resources".

En este ejemplo SÍ se va a realizar configuración relacionada con la reserva de recursos
Esta estrategia se puede reutilizar para otros ejemplos
Importante: Estas configuraciones dependerán mucho de la máquina host donde se ejecutará

Para su realización hay que añadir la sección y adaptar al configuración para cada uno de los casos

# Queue Mode with resources

# *** N8N shared ***
# Project URL: https://github.com/n8n-io/n8n
# Docs URL: https://docs.n8n.io/
x-n8n: &service-n8n
  image: n8nio/n8n:1.82.1
  restart: always
  networks: ['demo']
  env_file:
    - ./config/n8n/n8n.env

services:

  # Project URL: https://github.com/postgres/postgres
  # Docs URL: https://www.postgresql.org/docs/16/index.html
  n8n-postgres:
    ...
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M

  # Project URL: https://github.com/redis/redis
  # Docs URL: https://redis.io/docs/latest/
  n8n-redis:
    ...
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
        reservations:
          cpus: '0.2'
          memory: 256M


  # Project URL: https://github.com/n8n-io/n8n
  # Docs URL: https://docs.n8n.io/
  n8n-main:
    <<: *service-n8n
    ...
    deploy:
      resources:
        limits:
          cpus: '1.5'
          memory: 3G
        reservations:
          cpus: '1'
          memory: 2G

  n8n-worker:
    <<: *service-n8n
    command: ["worker"]
    ...
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '1'
          memory: 1.5G
        reservations:
          cpus: '0.5'
          memory: 1G

volumes:
  postgres-data:
  redis-data:
  queue01-n8n-data:

networks:
  demo:
    name: demo
    driver: bridge

Arrancaremos el fichero Docker Compose

docker compose -f QUEUE-docker-compose-04.yml up -d

4. Conclusión

Con todo lo que hemos visto en este artículo hemos realizado un gran cambio a nuestra arquitectura y sobre todo, hemos aprendido muchos más conceptos y configuraciones de la herramienta n8n. Ahora si que estamos mejor preparados para abordar las necesidades de una empresa real pudiéndonos enfrentar a muchos trabajos realizándose a la vez, a trabajos muy complejos, tolerancia a fallos, integraciones complejas, etc.

Creo que hemos ganado mucho en seguridad sobre la forma de ejecución de los trabajos y sobre la utilización de los recursos necesarios para ello.

Por lo tanto, el funcionamiento en modo queue y el planteamiento de una arquitectura se convierte en nuestras nuevas armas con las que enfrentarnos al mundo.

Espero que haya resultado interesante.

Autor

Víctor Madrid

Miembro de la Dirección Técnica en knowmad mood. Aprendiz de mucho y maestro de nada. Técnico, artista y polifacético a partes iguales ;-)