feat: implement worker-runner sidecar pattern for n8n

replace replicated workers/runners with dynamically generated 1:1
worker-runner pairs where each runner connects to its worker via
localhost (network_mode: service). this improves task routing by
ensuring each worker has dedicated runner capacity.

add generate_n8n_workers.sh script to create docker-compose.n8n-workers.yml
This commit is contained in:
Yury Kossakovsky
2025-12-09 17:34:04 -07:00
parent bcd2da2dfc
commit d54eca620c
3 changed files with 94 additions and 25 deletions

3
.gitignore vendored
View File

@@ -10,4 +10,5 @@ shared/
supabase/
dify/
volumes/
docker-compose.override.yml
docker-compose.override.yml
docker-compose.n8n-workers.yml

View File

@@ -67,6 +67,7 @@ x-n8n: &service-n8n
N8N_RUNNERS_BROKER_LISTEN_ADDRESS: 0.0.0.0
N8N_RUNNERS_ENABLED: true
N8N_RUNNERS_MODE: external
OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS: ${OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS:-true}
N8N_SMTP_HOST: ${N8N_SMTP_HOST:-}
N8N_SMTP_OAUTH_PRIVATE_KEY: ${N8N_SMTP_OAUTH_PRIVATE_KEY:-}
N8N_SMTP_OAUTH_SERVICE_CLIENT: ${N8N_SMTP_OAUTH_SERVICE_CLIENT:-}
@@ -106,15 +107,16 @@ x-init-ollama: &init-ollama
- "-c"
- "sleep 3; OLLAMA_HOST=ollama:11434 ollama pull qwen2.5:7b-instruct-q4_K_M; OLLAMA_HOST=ollama:11434 ollama pull nomic-embed-text"
x-n8n-runner: &service-n8n-runner
# Worker-runner anchor for sidecar pattern (runner connects to worker via localhost)
x-n8n-worker-runner: &service-n8n-worker-runner
image: n8nio/runners:2.0.0
environment: &service-n8n-runner-env
environment:
N8N_RUNNERS_AUTH_TOKEN: ${N8N_RUNNERS_AUTH_TOKEN}
N8N_RUNNERS_AUTO_SHUTDOWN_TIMEOUT: 15
N8N_RUNNERS_EXTERNAL_ALLOW: "*"
N8N_RUNNERS_MAX_CONCURRENCY: ${N8N_RUNNERS_MAX_CONCURRENCY:-5}
N8N_RUNNERS_STDLIB_ALLOW: "*"
N8N_RUNNERS_TASK_BROKER_URI: http://n8n:5679
N8N_RUNNERS_TASK_BROKER_URI: http://127.0.0.1:5679
NODE_FUNCTION_ALLOW_BUILTIN: "*"
NODE_FUNCTION_ALLOW_EXTERNAL: cheerio,axios,moment,lodash
@@ -175,40 +177,26 @@ services:
n8n-import:
condition: service_completed_successfully
n8n-worker:
# Template services for worker-runner pairs (used by docker-compose.n8n-workers.yml via extends)
# These templates use profile "n8n-template" which is never activated directly
n8n-worker-template:
<<: *service-n8n
profiles: ["n8n"]
restart: unless-stopped
profiles: ["n8n-template"]
command: worker
volumes:
- n8n_storage:/home/node/.n8n
- ./shared:/data/shared
depends_on:
n8n:
condition: service_started
redis:
condition: service_healthy
postgres:
condition: service_healthy
deploy:
replicas: ${N8N_WORKER_COUNT:-1}
n8n-runner:
<<: *service-n8n-runner
profiles: ["n8n"]
restart: unless-stopped
n8n-runner-template:
<<: *service-n8n-worker-runner
profiles: ["n8n-template"]
entrypoint: ["/bin/sh", "-c", "/usr/local/bin/task-runner-launcher javascript python"]
depends_on:
n8n:
condition: service_started
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:5680/healthz || exit 1"]
interval: 30s
timeout: 10s
retries: 5
start_period: 30s
deploy:
replicas: ${N8N_RUNNER_COUNT:-1}
qdrant:
image: qdrant/qdrant

80
scripts/generate_n8n_workers.sh Executable file
View File

@@ -0,0 +1,80 @@
#!/bin/bash
# Генерирует docker-compose.n8n-workers.yml с N парами worker-runner
# Использование: N8N_WORKER_COUNT=3 bash scripts/generate_n8n_workers.sh
#
# Этот скрипт идемпотентен - при повторном запуске файл перезаписывается
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
# Source utilities if available
if [[ -f "$SCRIPT_DIR/utils.sh" ]]; then
source "$SCRIPT_DIR/utils.sh"
else
# Fallback logging functions
log_info() { echo "[INFO] $*"; }
log_warning() { echo "[WARN] $*"; }
log_error() { echo "[ERROR] $*" >&2; }
fi
# Загрузить N8N_WORKER_COUNT из .env если не задан
if [[ -z "${N8N_WORKER_COUNT:-}" ]] && [[ -f "$PROJECT_DIR/.env" ]]; then
N8N_WORKER_COUNT=$(grep -E "^N8N_WORKER_COUNT=" "$PROJECT_DIR/.env" | cut -d'=' -f2 || echo "1")
fi
N8N_WORKER_COUNT=${N8N_WORKER_COUNT:-1}
# Валидация N8N_WORKER_COUNT
if ! [[ "$N8N_WORKER_COUNT" =~ ^[1-9][0-9]*$ ]]; then
log_error "N8N_WORKER_COUNT must be a positive integer, got: '$N8N_WORKER_COUNT'"
exit 1
fi
OUTPUT_FILE="$PROJECT_DIR/docker-compose.n8n-workers.yml"
log_info "Generating n8n worker-runner pairs configuration..."
log_info "N8N_WORKER_COUNT=$N8N_WORKER_COUNT"
# Перезаписываем файл (идемпотентно)
cat > "$OUTPUT_FILE" << 'EOF'
# Auto-generated file for n8n worker-runner pairs
# Regenerate with: bash scripts/generate_n8n_workers.sh
# DO NOT EDIT MANUALLY - this file is overwritten on each run
services:
EOF
for i in $(seq 1 "$N8N_WORKER_COUNT"); do
cat >> "$OUTPUT_FILE" << EOF
n8n-worker-$i:
extends:
file: docker-compose.yml
service: n8n-worker-template
container_name: n8n-worker-$i
profiles: ["n8n"]
restart: unless-stopped
depends_on:
n8n:
condition: service_started
redis:
condition: service_healthy
postgres:
condition: service_healthy
n8n-runner-$i:
extends:
file: docker-compose.yml
service: n8n-runner-template
container_name: n8n-runner-$i
profiles: ["n8n"]
restart: unless-stopped
network_mode: "service:n8n-worker-$i"
depends_on:
n8n-worker-$i:
condition: service_started
EOF
done
log_info "Generated $OUTPUT_FILE with $N8N_WORKER_COUNT worker-runner pair(s)"