diff --git a/.gitignore b/.gitignore index 11637c4..a88e24a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ shared/ supabase/ dify/ volumes/ -docker-compose.override.yml \ No newline at end of file +docker-compose.override.yml +docker-compose.n8n-workers.yml \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 060069f..11a17c6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/scripts/generate_n8n_workers.sh b/scripts/generate_n8n_workers.sh new file mode 100755 index 0000000..949a1b5 --- /dev/null +++ b/scripts/generate_n8n_workers.sh @@ -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)"