n8n 源码深度剖析与生产应用教程

2 阅读15分钟

版本: n8n v2.18.0 (master @ 2026-04) 仓库: github.com/n8n-io/n8n.… 定位: Fair-code 许可的工作流自动化平台 (Workflow Automation),对标 Zapier / Make,强调可自托管、代码可扩展、AI 原生。

本文档分为两大部分:

  • 第一部分:对 n8n 源码进行体系化、模块级、直至关键类与方法的深度剖析。
  • 第二部分:从零搭建生产环境(单机 → Queue 集群 → 多主节点 HA),并给出最佳实践。

目录


一、全局认知:定位、License 与技术栈

1.1 产品定位

n8n = "nodemation"(node + automation)。一个可自托管的可视化自动化引擎:

  • 400+ 集成节点(HTTP/SaaS/DB/AI/文件/消息队列…)
  • 支持 Code 节点(JS / Python)、AI Agent(LangChain)、Sub-workflowWebhook/Cron/MQ 触发器
  • 提供 REST APIPublic APIWebhook URL,可以把工作流作为一等 HTTP 服务暴露

1.2 License:Fair-code

n8n 采用 Sustainable Use License(基于 Apache 2.0 + Commons Clause 类约束):

  • 源码可读、可自托管、可二次修改
  • 商业化托管 n8n 本身(提供 n8n-as-a-Service)受限,需企业许可
  • 企业版功能(SSO/SAML/LDAP/高级 RBAC/Log Streaming/Multi-Main)在 *.ee.ts 文件中,运行时需要 License Key 激活。代码依然开源(Source Available)。

1.3 技术栈速览

选型
语言TypeScript 全栈
后端Node.js ≥ 22.16,Express 4,TypeORM (SQLite/Postgres/MariaDB/MySQL)
前端Vue 3 + Vite + Pinia + Element Plus + VueFlow(画布)
队列Bull 4 + Redis(Queue 模式)
DI@n8n/di(自研轻量 IoC,类装饰器)
打包pnpm workspaces + Turbo + Biome + ESLint + lefthook
测试Jest (unit)、Vitest (FE)、Playwright (E2E)、nock (HTTP mock)
Python 代码节点@n8n/task-runner-python(沙箱子进程)
JS 代码节点isolated-vm V8 隔离

二、Monorepo 结构解剖

pnpm-workspace.yaml 将 40+ 个包聚合。关键包(按依赖层级自底向上):

┌────────────────────────────────────────────────────────┐
│  packages/workflow         —— 纯类型 + 纯函数图遍历     │
│  packages/core             —— 执行引擎 (WorkflowExecute)│
│  packages/@n8n/config      —— 集中式配置 (class-validator) │
│  packages/@n8n/db          —— TypeORM entities / repos  │
│  packages/@n8n/di          —— IoC 容器                  │
│  packages/@n8n/decorators  —— @Service/@Command/@OnShutdown│
│  packages/cli              —— Express Server、命令、Scaling│
│  packages/nodes-base       —— 306+ 内置节点(见 ls 统计)│
│  packages/@n8n/nodes-langchain —— AI / LangChain 节点   │
│  packages/@n8n/task-runner —— JS/Python 代码沙箱 runner │
│  packages/frontend/editor-ui  —— Vue3 编辑器            │
│  packages/frontend/@n8n/design-system —— UI 组件库      │
│  packages/@n8n/api-types   —— FE/BE 共享 DTO            │
└────────────────────────────────────────────────────────┘

分层原则:

  • workflow 不依赖 core;core 不依赖 cli;cli 粘合所有。
  • 节点包只依赖 workflowinterfaces,保证节点可独立发布 (community node)。
  • editor-ui 与后端通过 @n8n/api-types 共享 DTO,避免类型漂移。

三、核心模型

3.1 Workflow(工作流)

来自 packages/workflow/src/workflow.ts:

class Workflow {
  id: string;
  nodes: INode[];
  connections: IConnections;  // 以 source node 为 key
  nodeTypes: INodeTypes;
  settings: IWorkflowSettings;
  staticData: IDataObject;    // 节点持久化数据(如 OAuth token)
  pinData?: IPinData;
  // 图遍历:
  getParentNodes(name): string[];
  getChildNodes(name): string[];
  getStartNode(destination?): INode;
}

重要概念:workflow.connections 的 key 是 source 节点。反向查询父节点必须先用 mapConnectionsByDestination() 反转(见 AGENTS.md 明确规定)。这是因为触发顺序本质是 输出 → 输入,source-indexed 便于 BFS 前向传播。

3.2 Node(节点)

节点 = 一个带 description 的类,描述参数 schema + 一个 execute()(或 trigger / poll / webhook)。

interface INodeType {
  description: INodeTypeDescription;   // JSON schema-like
  execute?(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
  trigger?(this: ITriggerFunctions): Promise<ITriggerResponse>;
  poll?(this: IPollFunctions): Promise<INodeExecutionData[][] | null>;
  webhook?(this: IWebhookFunctions): Promise<IWebhookResponseData>;
}

返回的 INodeExecutionData[][] 第一层是 output index(多 branch,如 IF 节点),第二层是 items(一个分支下多条记录)。

3.3 Connection(连接)

连接区分类型(NodeConnectionTypes):

  • main — 常规数据流
  • ai_toolai_languageModelai_memoryai_embedding …(AI 专用,LangChain 节点把 LLM/Memory/Tool 作为"供给连接")

3.4 Execution(执行)

一次运行 Workflow 的实例,持久化到 execution_entity 表。状态机:

new → running → success | error | canceled | crashed | waiting

IRunExecutionData 承载运行时全部状态,重要字段:

  • executionData.nodeExecutionStack: 待执行栈(核心调度结构)
  • executionData.waitingExecution / waitingExecutionSource: 多输入节点的等待区
  • resultData.runData: 已完成节点 → ITaskData
  • startData.runNodeFilter: 局部执行过滤器
  • resumeToken: wait 节点挂起后恢复凭证

四、执行引擎 (packages/core) 深剖

引擎入口:packages/core/src/execution-engine/workflow-execute.ts

4.1 类 WorkflowExecute

class WorkflowExecute {
  private status: ExecutionStatus = 'new';
  private readonly abortController = new AbortController();

  constructor(
    private readonly additionalData: IWorkflowExecuteAdditionalData,
    private readonly mode: WorkflowExecuteMode,
    private runExecutionData: IRunExecutionData = createRunExecutionData(),
    private readonly storedAt: ExecutionStorageLocation = 'db',
  ) {}

  run(opts: RunWorkflowOptions): PCancelable<IRun> { … }
  runPartialWorkflow2(…): PCancelable<IRun> { … }      // 支持从某节点部分重跑
  processRunExecutionData(workflow): PCancelable<IRun> { … }  // 核心调度循环
}

注意源码注释 明确禁止把 run()async:因为 PCancelableasync 包装后会退化为普通 Promise,失去取消能力。

4.2 核心调度算法(伪代码抽取)

调度本质是一个以 nodeExecutionStack 为主、waitingExecution 为辅的 有向无环遍历 + 多输入栅栏:

loop:
  if stack empty:
     if waiting empty: break  // 完成
     else: 处理超时 / 条件恢复
  taskData = stack.pop()
  node    = taskData.node
  
  if node 被 runNodeFilter 排除: continue
  if 有未到齐的 main 输入 && !所有源已完成:
     放入 waitingExecution 等区, continue

  建立 ExecuteContext(IExecuteFunctions) —— 见 4.3
  try:
     result = await node.execute.call(ctx)    # PCancelable
  catch (NodeOperationError | NodeApiError):
     按 continueOnFail / errorOutput 路由

  按 connections 把 result 的每个 branch 推到后继节点的 stack

关键点:

  • 调度顺序受 workflow.settings.executionOrder(v0/v1)影响。新版 v1 优先深度优先,改善并行分支的视觉一致性。
  • handleCycles() 识别环;环内节点需手动用 If + Wait 终止。
  • 取消:abortController.signalPCancelable 配合,activeExecutions.stop() 触发 TimeoutExecutionCancelledError / ManualExecutionCancelledError

4.3 执行上下文(Node Execution Contexts)

位于 packages/core/src/execution-engine/node-execution-context/:

Context用于暴露能力
ExecuteContext常规 execute()helpers.httpRequest / returnJsonArray / getInputData / getCredentials
ExecuteSingleContextexecuteSingle 项级每条 item 独立上下文
TriggerContexttrigger()emit() 推入调度;workflow 激活期长驻
PollContextpoll()只暴露轮询相关 helper
WebhookContextwebhook()req/res + getBodyData/getHeaderData
HookContextlifecycle hooks无 IO
LoadOptionsContext动态下拉枚举读凭据/调 API 填充 Select
SupplyDataContextAI 节点"供给"侧为 Agent 提供 Tool/Memory/LLM
CredentialsTestContext凭据测试按钮单独发一次 API 验 token

每种 Context 都派生自 BaseExecuteContext,共享:DI-scoped LoggerbinaryDataServiceexpressionResolver(n8n 表达式 {{ $json.a }} 解析)。

4.4 RoutingNode(声明式 HTTP 节点)

packages/core/src/execution-engine/routing-node.ts 提供"配置即节点"——节点只写 routing: { request: { … } },引擎自动组装 HTTP 请求、分页、重试、预处理、后处理。nodes-base 里 70% 的 SaaS 节点都用此模式,见 AirtableNotionSlack 节点。

4.5 TriggersAndPollers

triggers-and-pollers.ts 负责:

  • 启动 trigger() 返回的长连接(如 IMAP IDLE、MQ 订阅)
  • 通过 cron 调度 poll()
  • 统一把 emit 的数据塞进 WorkflowRunner.run()

五、CLI / Server 进程启动链 (packages/cli)

二进制入口:packages/cli/bin/n8n。命令通过 @Command 装饰器注册,位于 packages/cli/src/commands/:

命令作用
n8n start主进程:HTTP Server + 激活 workflow + (regular 模式下)执行器
n8n workerQueue 模式下从 Redis 拉任务执行
n8n webhook独立 webhook 进程,只处理 HTTP,把执行推入队列
n8n execute / execute-batchCLI 直跑工作流(运维/CI 场景)
n8n export / import工作流/凭据备份与恢复
n8n user-management:reset管理员密码重置
n8n update:workflow批量运维
n8n ldap:reset / mfa:disable企业版辅助

5.1 Start 命令关键路径

packages/cli/src/commands/start.ts:62:

@Command({ name: 'start', … })
export class Start extends BaseCommand {
  protected server = Container.get(Server);
  override needsCommunityPackages = true;
  override needsTaskRunner = true;

  async init() { await super.init(); /* DB/License/Nodes/EventBus/Scaling */ }
  async run() {
    // 1. 启动 HTTP Server
    await this.server.start();
    // 2. 激活所有 active: true 的 workflow(webhook 注册 + trigger 启动)
    await this.activeWorkflowManager.init();
    // 3. 启动定期 pruning(老执行记录清理)
    Container.get(ExecutionsPruningService).init();
    // 4. 启动 Task Runner 子进程
    if (this.needsTaskRunner) await Container.get(TaskRunnerModule).start();
    // 5. 如果 --open 自动开浏览器
    if (flags.open) this.openBrowser();
  }
  async stopProcess() { … /* 见 start.ts:91 */ }
}

BaseCommand(见 base-command.ts)统一做:

  • Crash Journal(断电恢复):把 executing 的 execution 标 crashed 并触发 recovery
  • DB 初始化(migrations / entities)
  • LoadNodesAndCredentials(扫描 packages/nodes-base/dist/** + 社区 node_modules)
  • License 校验(@n8n/license SDK 调用)
  • EventBus 初始化(消息总线)
  • @OnShutdown 优先级化关闭

5.2 启停优先级

@OnShutdown(priority) 装饰器(@n8n/decorators),HIGHEST_SHUTDOWN_PRIORITY 先跑(如 push 通知关闭),DB 最后。这保证:

  • 不再接受新 HTTP 请求
  • 正在跑的 execution 有 N8N_GRACEFUL_SHUTDOWN_TIMEOUT 内 wrap-up 机会
  • Queue 模式下 worker 不再 getNextJob,但完成手头 job

六、HTTP 层:AbstractServer、控制器与 DI

6.1 AbstractServer

packages/cli/src/abstract-server.ts 抽取三种进程共享的 Express 栈:

  • middlewares: helmetcookie-parserrawBodyrateLimitbodyParsercompressioncors(可关)
  • webhooks mounting: /webhook/*/webhook-test/*/webhook-waiting/*
  • health: /healthz/healthz/readiness
  • markAsReady() 在 listen 后设标志,反向代理/LB 探针才通过

6.2 Server(主进程)

server.ts:72 继承 AbstractServer,额外:

  • 静态资源:托管 editor-ui 打包产物(EDITOR_UI_DIST_DIR)
  • 挂载 40+ 控制器(顶部一堆 import '@/controllers/*'—— 副作用式注册,依赖 decorator 收集元数据)
  • Chat Server(WebSocket for AI Assistant)
  • Public API(OpenAPI,packages/cli/src/public-api/)
  • Push(SSE 向前端推实时执行状态)

6.3 控制器模式

用自研 DI + 装饰器做类 Nest 写法:

@RestController('/workflows')
export class WorkflowsController {
  constructor(private readonly workflowService: WorkflowService) {}
  @Get('/')
  @GlobalScope('workflow:list')
  async list(req: WorkflowRequest.GetMany) { … }
  @Post('/')
  @Licensed('feat:advancedExecutionFilters')
  async create(req: WorkflowRequest.Create) { … }
}

装饰器做了:路由注册、权限 scope 校验(@GlobalScope / @ProjectScope)、License gating、Zod body validation、错误转 HTTP status。这比 Nest 更轻,没有 Module,但也因此所有 Service 都是全局单例。

6.4 DI 容器(@n8n/di)

最小可用 IoC,大致:

// di.ts 心智模型
const registry = new Map<Token, any>();
export function Service() {
  return (target) => { registry.set(target, /* lazy singleton */); };
}
export const Container = {
  get<T>(token: Token<T>): T { /* resolve constructor deps via reflect-metadata */ }
};

所有 controller/service/repository 都 @Service(),构造器注入。测试里用 mock<T>() + Container.set(Token, mock) 替换。packages/@n8n/backend-test-utils 提供统一 fixture。


七、触发器、Webhook 与 ActiveWorkflowManager

7.1 ActiveWorkflowManager

packages/cli/src/active-workflow-manager.ts:79 是**把"static workflow 定义"→"运行时订阅者"**的桥梁。职责:

  1. 启动时:init() 拉出 active=true 的工作流 → 对每一个 trigger node 调 trigger(),注册长连接;对每一个 webhook 在 webhook_entity 写静态路由。
  2. 运行时:workflow 被保存/删除/切换激活 → 通过 Publisher 发布 workflow-activated / workflow-deactivated pub/sub 消息,其他 main / webhook / worker 进程订阅并同步状态。
  3. 错误重试:激活失败(如外部 API 拒绝)走指数退避 WORKFLOW_REACTIVATE_INITIAL_TIMEOUT → WORKFLOW_REACTIVATE_MAX_TIMEOUT
  4. 优雅下线:@OnShutdown 关掉所有 trigger/poller。

7.2 Webhook 路径

从 HTTP 请求到执行一次工作流:

[Client] POST /webhook/abc-xyz
   │
   ▼
AbstractServer.webhookRouter
   │
   ▼
WebhookService.findByPath → 查 webhook_entity 拿到 workflowId、nodeName、method
   │
   ▼
WebhookController.executeWebhook
   │   建立 WebhookContext → node.webhook() → 返回 responseData
   ▼
WorkflowRunner.run({ workflowData, startNodes:[webhookNode], pinData, triggerToStartFrom })
   │
   ├─ regular 模式: new WorkflowExecute(...).run() 直接本地跑
   └─ queue 模式: ScalingService.addJob(...) 丢到 Bull,HTTP 可选择"同步等完成"或立即返回

responseMode 支持三种:

  • onReceived:webhook 立即 200,背后异步执行
  • lastNode:阻塞到最后一个节点的返回值作为 HTTP body
  • responseNode:由用户显式的 Respond to Webhook 节点返回(可自定义 status/headers/stream)

八、Queue 模式:Bull + Redis 横向伸缩

8.1 架构

            ┌──────────────┐     Bull Queue (Redis)
            │   Main(s)    │ ───────────────────────────►  ┌──────────┐
            │  Webhook(s)  │  job: { executionId, load… }  │ Workers  │
            └──────────────┘                               │ (N 个)   │
                 ▲                                         └──────────┘
                 │  pub/sub   (worker-status, job-finished)      │
                 └─────────────────────────────────────────────  │
                                                                 ▼
                                                          [ DB write result ]

8.2 ScalingService

packages/cli/src/scaling/scaling.service.ts:33:

  • setupQueue():创建 Bull Queue n8n-jobs,配置 maxStalledCount: 0(禁用 Bull 的默认 retry,n8n 自己管)
  • setupWorker(concurrency):queue.process(JOB_TYPE_NAME, concurrency, async job => jobProcessor.processJob(job))
  • setupQueueRecovery():Leader 定期扫描 stuck 的 running execution(worker 宕机但 Bull 未标 stalled 的边界情况),搬到 crashed 并触发 recovery

8.3 JobProcessor

job-processor.ts:57:worker 里实际执行逻辑。

  • 从 DB 加载 execution data
  • 组装 WorkflowExecute 实例,注册 getLifecycleHooksForScalingWorker(这类 hook 不写 DB,只通过 pub/sub 向 main 汇报进度,因为 main 负责最终持久化)
  • 运行,产出 IRun,通过 JobFinishedMessage 回传到 main

8.4 为什么要分这么多进程

  • main — UI、API、激活 workflow、聚合执行结果
  • webhook(可选独立)— 只处理 HTTP 入口,吞吐高,可独立 HPA
  • worker — CPU 密集的 workflow 执行,数量随队列深度扩

env 开关:EXECUTIONS_MODE=queue,Redis 连接 QUEUE_BULL_REDIS_HOST/PORT/PASSWORD/DB/TLS


九、Multi-Main HA:Leader 选举与 PubSub

9.1 问题

多 main 同时跑时,如果都去激活同一个 cron 触发器,会重复触发;且 ActiveWorkflowManager 的内存状态需要同步。

9.2 MultiMainSetup

packages/cli/src/scaling/multi-main-setup.ee.ts(企业版):

  • 基于 Redis SET NX EX 实现 租约式 Leader 选举(key n8n:main-leader,TTL 15s,续约 5s)
  • @OnLeaderTakeover / @OnLeaderStepdown 装饰器回调服务,如:
    • Only Leader: 运行 ExecutionsPruningServiceQueueRecovery
    • All Mains: 响应 API、处理 webhook
  • 切 Leader 时,ActiveWorkflowManager 响应 pubsub.event 暂停/接管 trigger

9.3 PubSub 通道(Redis Pub/Sub)

packages/cli/src/scaling/pubsub/:

Channel用途
n8n.commands跨进程指令:workflow-activatedrestart-task-runnercommunity-package-install
n8n.worker-responseworker → main 状态同步

Publisher / Subscriberioredis,PubSubRegistry 汇总所有订阅者。业务代码用 @OnPubSubEvent('workflow-activated') 装饰方法。


十、节点 (Nodes) 扩展体系

306 个内置节点 + LangChain 节点包 + 社区节点。一个最小节点:

// packages/nodes-base/nodes/HelloWorld/HelloWorld.node.ts
export class HelloWorld implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'Hello World',
    name: 'helloWorld',
    group: ['transform'],
    version: 1,
    description: 'Greets input items',
    defaults: { name: 'Hello World' },
    inputs: [NodeConnectionTypes.Main],
    outputs: [NodeConnectionTypes.Main],
    properties: [
      { displayName: 'Greeting', name: 'greeting', type: 'string', default: 'Hello' },
    ],
  };
  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    const items = this.getInputData();
    const out = items.map((item, i) => ({
      json: { ...item.json, msg: `${this.getNodeParameter('greeting', i)} from n8n` },
    }));
    return [out];
  }
}

10.1 加载

LoadNodesAndCredentials(packages/cli/src/load-nodes-and-credentials.ts)在启动时扫描:

  1. packages/nodes-base/dist/**/*.node.js
  2. 社区包目录(~/.n8n/nodes/node_modules/n8n-nodes-*)
  3. 企业 gateway 推送的动态包 每个节点按 typeVersion 索引(旧工作流可稳定运行旧版本),热更新支持(dev)。

10.2 Community Node 安全

社区节点默认不允许(N8N_COMMUNITY_PACKAGES_ENABLED=true 打开)。新增安全验证 @n8n/scan-community-package,在安装前静态扫描:eval/Functionchild_process、可疑网络、权限声明。


十一、Task Runner:代码节点与进程隔离

问题:用户在 Code 节点里写 require('fs').unlinkSync('/') 怎么办?

方案演进:

  • 早期:vm2 / isolated-vm 在 main 进程隔离 → 仍共享内存
  • 现在:独立 Task Runner 子进程,通过 IPC(node:worker_threads + 消息协议)接任务

packages/@n8n/task-runner/ 实现 JS runner;packages/@n8n/task-runner-python/ 对应 Python(调 Pyodide/Node-Python bridge)。

Main 进程通过 packages/cli/src/task-runners/TaskRunnerModule:

  • 启动 runner(spawn 或外部 runner via WebSocket)
  • TaskBroker 维护 runner 池,分发 task,超时/内存限制、rate-limit
  • 支持 Internal(同机子进程)与 External(独立容器,K8s 侧车)两种部署

env:N8N_RUNNERS_ENABLED=true(新安装默认 true),N8N_RUNNERS_AUTH_TOKEN=...,N8N_RUNNERS_MODE=internal|external


十二、AI / LangChain 节点体系

packages/@n8n/nodes-langchain/ 用 LangChain.js 封装了 Agent、Chain、Memory、Tool、Retriever、Vector Store 等概念为节点:

  • AI Agent / AI Chain — 根 Executor 节点,通过 ai_languageModelai_memoryai_tool 输入端口连接
  • Chat Trigger/chat WebSocket / SSE 长连接前端 chat UI
  • MCP Server — 把一个 workflow 暴露为 Model Context Protocol server,LLM 可以外部调用
  • Vector Stores:Pinecone、Qdrant、Supabase、PGVector、In-Memory
  • Tool = 子 workflow(通过 SupplyDataContext 暴露给 Agent),这实现了 可视化 Agent 编排

关键源码:

  • Agent 执行器:nodes/agents/Agent/Agent.node.ts
  • LangChain ↔ n8n 数据桥:utils/N8nLangChainLogger.ts
  • MCP:mcp/core/McpServer.ts,在 ScalingService.setupQueue 中注入 session store(Redis-backed,支持分布式)

环境变量:N8N_AI_ENABLEDN8N_AI_ASSISTANT_ENABLED(编辑器右侧 AI 助手)。


十三、前端 editor-ui (Vue 3 + Pinia)

13.1 技术细节

  • Vite 构建,Pinia store(packages/frontend/editor-ui/src/stores/)
  • 画布基于 VueFlow,自定义节点卡片、连线、minimap
  • 表达式编辑器:CodeMirror 6 + 自研 @n8n/codemirror-lang({{ $json.a }} 语法高亮 + 自动补全)
  • 设计系统:packages/frontend/@n8n/design-system/,所有颜色/间距/字号走 CSS 变量(见 AGENTS.md CSS Variables 章节)
  • i18n 强制化:所有 UI 文案必须走 @n8n/i18n

13.2 实时通信

前端通过 /rest/push(SSE 或 WebSocket,取决于 N8N_PUSH_BACKEND)订阅:

  • executionStarted / nodeExecuteBefore / nodeExecuteAfter / executionFinished
  • testWebhookReceived(test-webhook 进入后自动选中节点)

后端 Push 服务(packages/cli/src/push/)在执行 lifecycle hook 里发事件,按 sessionId 路由到具体浏览器 tab。


十四、数据模型、加密与凭据

14.1 主要表(TypeORM packages/@n8n/db/)

  • workflow_entity:name / nodes(JSON)/ connections(JSON)/ active / settings / staticData
  • execution_entity + execution_data(二进制分表,执行详情大 JSON 单独存,便于 pruning)
  • credentials_entity:name / type / data(AES-256-CBC 加密) / shared via ACL
  • webhook_entity:path / method / workflowId / nodeId(激活时写入,查找 O(1))
  • userroleprojectshared_workflowshared_credentials:RBAC
  • settings(kv)、installed_packages(社区节点版本)、workflow_history(变更记录)

14.2 加密密钥

~/.n8n/configencryptionKey(首次启动生成,32 字节随机)。生产必须显式设 N8N_ENCRYPTION_KEY,否则:

  • 不同 pod/节点解密不一致 → 凭据失效
  • 备份恢复后密钥丢失 → 所有凭据报废

加密入口:packages/core/src/encryption/

14.3 凭据热替换(Credential Overwrites)

企业场景:集中 Vault 提供真 secret,编辑器里保留占位符。CredentialsOverwritesCREDENTIALS_OVERWRITE_DATA env 或 HTTP endpoint(CREDENTIALS_OVERWRITE_ENDPOINT)动态取值注入。


十五、生产部署:Docker / Compose / Kubernetes

15.1 最小单机

docker volume create n8n_data
docker run -d --name n8n --restart unless-stopped \
  -p 5678:5678 \
  -v n8n_data:/home/node/.n8n \
  -e N8N_HOST=n8n.example.com \
  -e N8N_PROTOCOL=https \
  -e WEBHOOK_URL=https://n8n.example.com/ \
  -e N8N_ENCRYPTION_KEY=$(openssl rand -hex 32) \
  -e GENERIC_TIMEZONE=Asia/Shanghai \
  -e TZ=Asia/Shanghai \
  -e DB_TYPE=postgresdb \
  -e DB_POSTGRESDB_HOST=pg \
  -e DB_POSTGRESDB_DATABASE=n8n \
  -e DB_POSTGRESDB_USER=n8n \
  -e DB_POSTGRESDB_PASSWORD=*** \
  docker.n8n.io/n8nio/n8n

把默认 SQLite 换 Postgres:SQLite 在并发执行 > 5 会写冲突。

15.2 Queue 模式 Docker Compose

version: "3.8"
services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: n8n
      POSTGRES_USER: n8n
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes: [pg:/var/lib/postgresql/data]
    healthcheck:
      test: [CMD-SHELL, pg_isready -U n8n]
      interval: 5s

  redis:
    image: redis:7-alpine
    command: redis-server --requirepass ${REDIS_PASSWORD} --appendonly yes
    volumes: [redis:/data]

  # ========= 公共环境 =========
  x-n8n-common: &n8n-common
    image: docker.n8n.io/n8nio/n8n:latest
    restart: unless-stopped
    environment: &n8n-env
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_DATABASE: n8n
      DB_POSTGRESDB_USER: n8n
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
      EXECUTIONS_MODE: queue
      QUEUE_BULL_REDIS_HOST: redis
      QUEUE_BULL_REDIS_PASSWORD: ${REDIS_PASSWORD}
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      N8N_HOST: n8n.example.com
      N8N_PROTOCOL: https
      WEBHOOK_URL: https://n8n.example.com/
      GENERIC_TIMEZONE: Asia/Shanghai
      TZ: Asia/Shanghai
      N8N_RUNNERS_ENABLED: "true"
      N8N_LOG_LEVEL: info
      N8N_METRICS: "true"
      EXECUTIONS_DATA_PRUNE: "true"
      EXECUTIONS_DATA_MAX_AGE: "336"   # 14 天
      N8N_PAYLOAD_SIZE_MAX: "32"       # MB
    depends_on:
      postgres: { condition: service_healthy }
      redis:    { condition: service_started }

  n8n-main:
    <<: *n8n-common
    command: ["n8n", "start"]
    ports: ["5678:5678"]

  n8n-webhook:
    <<: *n8n-common
    command: ["n8n", "webhook"]
    ports: ["5679:5678"]           # 反代只把 /webhook/* 打到这

  n8n-worker:
    <<: *n8n-common
    command: ["n8n", "worker", "--concurrency=10"]
    deploy:
      replicas: 3

volumes: { pg: {}, redis: {} }

反代(Nginx / Traefik)按路径分流:

  • /webhook/*/webhook-test/*/webhook-waiting/*n8n-webhook
  • 其他(//rest/*/api/*) → n8n-main

15.3 Kubernetes 骨架(含 HPA)

关键 Chart 要素:

  • Deployment: n8n-main(replicas ≥ 2 需企业版 Multi-Main;社区版 replicas=1)
  • Deployment: n8n-webhook command: [n8n, webhook],HPA 基于 CPU/QPS
  • Deployment: n8n-worker command: [n8n, worker, --concurrency=10],HPA 基于 Bull 队列长度(用 prometheus-adaptern8n_queue_waiting_count)
  • Postgres(RDS / CloudNativePG)+ Redis(ElastiCache / bitnami chart)
  • Secrets:N8N_ENCRYPTION_KEY、DB、Redis;绝不放 ConfigMap
  • Ingress:nginx.ingress.kubernetes.io/proxy-body-size: 64m;webhook host 可独立
  • N8N_PROXY_HOPS=1(Ingress 加一跳)让 express.set('trust proxy', …) 正确取 IP

15.4 Graceful Shutdown

K8s preStopsleep 10,terminationGracePeriodSeconds: 60,env N8N_GRACEFUL_SHUTDOWN_TIMEOUT=45。优先级 hook 会确保:正在跑的 execution 写完 DB、Leader lease 释放。


十六、可观测性、备份、运维 SOP

16.1 Metrics (Prometheus)

N8N_METRICS=true/metrics 端点。关键指标:

  • n8n_active_workflow_count
  • n8n_queue_waiting_count / active_count / completed_count / failed_count
  • n8n_execution_duration_seconds(labeled by workflow_id, status)
  • 默认 Node.js 进程指标(GC、event loop lag)

建议的告警:

  • Bull 队列 waiting_count > 阈值持续 5min → 扩 worker
  • execution failed rate > 2% → 业务方排查
  • Main Leader lease 丢失且未选出新 Leader > 30s → Redis 故障

16.2 日志

  • N8N_LOG_LEVEL=info(生产);调试 debug
  • N8N_LOG_OUTPUT=console,file + N8N_LOG_FILE_LOCATION=/var/log/n8n/
  • 企业版 Log Streaming 可把 Event Bus 事件推到 Syslog / Kafka / Webhook

16.3 备份

必须备份三样:

  1. Postgres 全库 —— pg_dump,每日增量 + WAL 归档
  2. ~/.n8n/config —— 含 encryptionKey(未用 env 覆盖时)
  3. binary data 存储(N8N_DEFAULT_BINARY_DATA_MODE=filesystem:/home/node/.n8n/binaryData;或 S3:bucket 版本 + 生命周期)

恢复测试每季度演练一次。务必保留 N8N_ENCRYPTION_KEY 的同一值,否则凭据全部失效。

16.4 Pruning(自动清理)

EXECUTIONS_DATA_PRUNE=true 打开,EXECUTIONS_DATA_MAX_AGE=336(小时),EXECUTIONS_DATA_PRUNE_MAX_COUNT=50000。服务:ExecutionsPruningService,Leader 独占跑。


十七、安全加固清单

  • N8N_ENCRYPTION_KEY 显式设置,32 bytes 随机;使用 KMS/Secrets Manager
  • N8N_BASIC_AUTH_ACTIVE 已废弃,改 User Management(默认开,首账号即管理员);若暴露到公网必须启用 MFA
  • N8N_SECURE_COOKIE=true,仅 HTTPS
  • N8N_BLOCK_ENV_ACCESS_IN_NODE=true 防 Code 节点读取宿主 env
  • N8N_PAYLOAD_SIZE_MAX=16 限制 webhook body(默认 16 MB)
  • N8N_RUNNERS_MODE=external 生产更安全;N8N_RUNNERS_AUTH_TOKEN 强随机
  • 禁用社区节点(N8N_COMMUNITY_PACKAGES_ENABLED=false)或审计安装列表
  • 关闭公开注册(N8N_DISABLE_USER_MANAGEMENT=false 但不暴露 invite 路由到公网)
  • Postgres 用只有 n8n 库权限的账号;Redis 设密码 + requirepass + 网络隔离
  • Webhook 路径带 UUID(n8n 默认);对外 API 网关加 IP 白名单 + WAF
  • Code 节点默认关 eval;N8N_BLOCK_FILE_ACCESS_TO_N8N_FILES=true
  • 启用企业版 Audit Log 或自行从 EventBus 拉 user.*/workflow.* 事件
  • /healthz/readiness 在 Ingress 配 basic-auth 或 deny,防泄露版本

十八、从一个实际工作流看整条执行链路

场景:用户调用 POST /webhook/orders → 校验 → 写 Postgres → 发 Slack → 返回 JSON。

Webhook ─main─▶ Function (validate) ─main─▶ Postgres (insert) ─main─▶ Slack ─main─▶ Respond to Webhook

整条链(Queue 模式):

1. Nginx 把请求转到 n8n-webhook pod
2. AbstractServer 路由 → WebhookController.executeWebhook
3. WebhookService.findByPath 查 webhook_entity → 命中 workflowId=42
4. 组装 WebhookContext → 调 Webhook.node.ts 的 webhook() → 取 body
5. WorkflowRunner.run():
   - 创建 execution_entity(status=new)
   - ScalingService.addJob({ executionId, loadStaticData:false })
6. Bull 把 job 入 Redis
7. n8n-worker pod 的 queue.process 触发 JobProcessor.processJob:
   a. 从 DB 加载 executionData
   b. new WorkflowExecute(additionalData, 'webhook', data, 'db')
   c. .run({ startNode: Webhook-node }) → PCancelable
   d. processRunExecutionData 调度循环:
      - pop Webhook 节点 → 已有输出数据 → 前向传递
      - push Function 节点 → 调 Task Runner 子进程执行 user code
      - push Postgres 节点 → RoutingNode 构建 SQL → pg client 写库
      - push Slack 节点 → RoutingNode 发 Incoming Webhook
      - push Respond to Webhook → 通过 pub/sub 把 response 发回 n8n-webhook pod
   e. hooks 把每个 nodeExecuteAfter 通过 pub/sub 推 main/webhook,供前端 SSE
8. n8n-webhook pod 收到 RespondToWebhookMessage → HTTP res.json(body)
9. JobProcessor 完成 → JobFinishedMessage → main 持久化 execution result、发 push

这条链路解释了:为什么一个 webhook 在 Queue 模式下的 "response latency" = Redis RTT × 3 + 节点执行和,以及为什么 Respond to Webhook 节点能跨进程返回——通过 pub/sub 而非共享内存。


附录 A:核心环境变量速查

变量默认作用
N8N_HOSTlocalhost对外访问主机名(编辑器链接用)
N8N_PORT5678监听端口
N8N_PROTOCOLhttphttps 用于生成正确 webhook URL
WEBHOOK_URL= N8N_PROTOCOL://N8N_HOST:PORT/webhook 注册到外部平台的基 URL
N8N_ENCRYPTION_KEYauto生产必设
DB_TYPEsqlitepostgresdb / mysqldb / mariadb
EXECUTIONS_MODEregularqueue 启用集群模式
QUEUE_BULL_REDIS_HOSTRedis 主机
QUEUE_BULL_REDIS_PASSWORDRedis 密码
N8N_METRICSfalse/metrics Prometheus
N8N_LOG_LEVELinfo`errorwarninfodebug`
N8N_PAYLOAD_SIZE_MAX16webhook body 最大 MB
EXECUTIONS_DATA_PRUNEfalse开启自动清理
EXECUTIONS_DATA_MAX_AGE336小时
N8N_RUNNERS_ENABLEDtrueTask Runner
N8N_RUNNERS_AUTH_TOKEN内部通信 token
N8N_GRACEFUL_SHUTDOWN_TIMEOUT30
N8N_PROXY_HOPS0反代跳数
N8N_COMMUNITY_PACKAGES_ENABLEDtrue社区节点开关
N8N_AI_ENABLEDtrueAI 节点可见性
GENERIC_TIMEZONEAmerica/New_YorkCron 时区
EXECUTIONS_TIMEOUT-1全局执行超时秒
N8N_DISABLE_PRODUCTION_MAIN_PROCESS_WEBHOOKSfalse让 main 不再处理生产 webhook(独立 webhook 进程用)

附录 B:常见坑与排障

  1. "Workflow with ID X is currently not active" 通常是 multi-instance 共享 DB 但 ActiveWorkflowManager 没有正确同步 → 检查 pub/sub(Redis 是否被清 key、网络分区)。

  2. Webhook 在 queue 模式下超时 Respond to Webhook 必须在所有节点完成前执行(用 responseMode: responseNode),否则 LB 会先断开。检查 Nginx proxy_read_timeout

  3. 凭据突然全部失效 99% 是 N8N_ENCRYPTION_KEY 变了。检查:Helm chart 重建 Secret?env 没注入?恢复方案:拿旧 key 进容器、导出凭据 JSON 明文、用新 key 重新导入。

  4. SQLite 并发崩溃(database is locked) 老部署生产仍用 SQLite。迁移到 Postgres:n8n export:workflow --all --output=/tmp/wf.json → 新库 n8n import:workflow --input=/tmp/wf.json(凭据同理)。

  5. Worker OOM 大 binary data(PDF、图片)被塞进 INodeExecutionData.binary。切 N8N_DEFAULT_BINARY_DATA_MODE=filesystems3,数据走外存,INodeExecutionData 里只留引用。

  6. Cron 触发器多次触发 Multi-main 无 Leader 选举 → 每个 main 都在跑。要么降成单 main,要么买企业版开 Multi-Main。

  7. 前端执行日志不更新 Push 通道被反代缓冲。Nginx 里对 /rest/pushproxy_buffering off; proxy_read_timeout 3600s;

  8. Community Node 安装后不生效 Queue 模式下必须把包同时装进 main、webhook、worker 三种镜像;推荐自建镜像 RUN npm i -g n8n-nodes-xxx 统一分发。


结语:n8n 是一个把"工作流引擎 + 节点生态 + 前端编辑器 + 执行集群 + AI Agent"缝合得极其工整的工程体。读懂它的三条主线——Workflow 图模型、执行上下文 (ExecuteContext)、Queue 模式进程分工——就基本掌握了 80% 的源码。剩下 20% 是 306 个节点的集成细节与企业版的 SSO/审计/多租户,按需深入即可。

部署方面的金律只有三条:Postgres 起步、Queue 模式横扩、K8s Helm 管 Secret。严格执行本文第十五、十六、十七节清单,即可得到生产可用的高可用 n8n 集群。