
要是你还没听说过 n8n,先补个课:这是一个开源的工作流自动化平台,GitHub 上 179K Star,比很多老牌项目都高。简单说,它就是让你用"拖拖拽拽 + 写点代码"的方式,把系统之间的那些破事串起来自动跑——告警了自动拉群、表格更新了自动同步、来了邮件自动分类。今天聊聊我怎么用它把运维值班的活儿砍掉一半的,顺便把踩的坑也摆出来。
先说说我为什么要用这类工具。运维的日常里有一大堆"胶水活儿":监控告警要转发到企微、工单要同步到表格、发布完要通知测试、备份完了要检查结果……这些事单看每件都不难,但架不住多、碎、天天有。
我的解决方案进化史大概是这样:
阶段一:shell 脚本 + crontab。 能跑,但脚本一多就成了屎山,谁也不敢动,改一行崩一片。
阶段二:Python + 消息队列。 灵活了,但开发成本上去了,而且同事接手成本高——不是谁都想读你的代码。
阶段三:n8n。 工作流可视化,每个节点干嘛一目了然,出问题了看一眼执行日志就知道卡在哪一步。非技术同事(比如运营)居然也能自己改流程。
那为什么不直接用 Zapier?三个理由:
部署也简单,Docker 一把梭:
# docker-compose.yml
version: "3.8"
services:
n8n:
image: n8nio/n8n:latest
container_name: n8n
restart: unless-stopped
ports:
- "5678:5678"
environment:
# 基础配置
- N8N_HOST=n8n.internal.company.com
- N8N_PORT=5678
- N8N_PROTOCOL=https
# 时区,不设的话定时任务会按 UTC 跑,坑过我不止一次
- GENERIC_TIMEZONE=Asia/Shanghai
- TZ=Asia/Shanghai
# 数据持久化
- N8N_USER_FOLDER=/data
# 加密密钥,换容器不丢凭据(务必自己生成一个,别用默认值)
- N8N_ENCRYPTION_KEY=这里换成你自己的随机字符串
# 队列模式(生产环境建议开启,用 Redis 做队列)
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
# 执行历史保留时间
- EXECUTIONS_DATA_MAX_AGE=168
volumes:
- n8n_data:/data
- /var/run/docker.sock:/var/run/docker.sock # 需要操作 docker 时挂载
depends_on:
- redis
redis:
image: redis:7-alpine
container_name: n8n-redis
restart: unless-stopped
volumes:
- redis_data:/data
volumes:
n8n_data:
redis_data:# 一键启动
docker compose up -d
# 检查状态
docker compose logs -f n8n五分钟就能跑起来。但跑起来只是开始,真正的价值在后面的工作流设计上。
我们监控用的是 Prometheus + Alertmanager,告警会推送到企微群。问题是所有告警都往一个群里怼——磁盘水位 85% 是它,服务宕机是它,某个不重要的定时任务失败也是它。值班同学看到手机响,还得先点开看是不是要紧事,一天下来光"辨别告警严重程度"就够烦的。
我用 n8n 搭了一条分流流水线,思路是:告警进来 → 先做严重度判断 → 严重的直接打电话 + 拉群,普通的发群消息,噪音级别的直接归档。
整个流程长这样:

n8n 的核心逻辑写在 Function 节点里,贴一段实际的告警解析代码:
// Function 节点:解析 Alertmanager 告警并做分流决策
// 输入:Alertmanager webhook 的 payload
const alerts = $input.first().json.body.alerts || [];
const results = [];
// 严重度权重表:根据我们的业务实际情况定义
const SEVERITY_RULES = {
// 直接打电话的:核心链路
'ServiceDown': { level: 'critical', action: 'call_and_page' },
'DatabaseReplicationLag': { level: 'critical', action: 'call_and_page' },
'PaymentServiceError': { level: 'critical', action: 'call_and_page' },
// 发群消息的:需要关注但不紧急
'HighCPUUsage': { level: 'warning', action: 'notify_group' },
'DiskSpaceLow': { level: 'warning', action: 'notify_group' },
'CertificateExpiringSoon': { level: 'warning', action: 'notify_group' },
// 归档不推送的:噪音
'InstanceRestarted': { level: 'info', action: 'archive' },
'TestJobFailed': { level: 'info', action: 'archive' },
};
for (const alert of alerts) {
const alertName = alert.labels.alertname || 'Unknown';
const rule = SEVERITY_RULES[alertName] || { level: 'warning', action: 'notify_group' };
// 抑制逻辑:同一个实例 10 分钟内重复告警,只处理第一次
const dedupeKey = `${alertName}:${alert.labels.instance}`;
const lastSeen = $getWorkflowStaticData('global')[dedupeKey] || 0;
const now = Date.now();
if (now - lastSeen < 10 * 60 * 1000) {
results.push({
json: { ...alert, level: 'suppressed', reason: '10分钟内重复告警' }
});
continue;
}
$getWorkflowStaticData('global')[dedupeKey] = now;
// 给企微消息生成一段"人话"摘要
const summary = buildSummary(alert);
results.push({
json: {
alertname: alertName,
level: rule.level,
action: rule.action,
instance: alert.labels.instance || 'unknown',
summary: summary,
startsAt: alert.startsAt,
// 打电话用的紧急程度标记
needsCall: rule.action === 'call_and_page',
}
});
}
function buildSummary(alert) {
const name = alert.labels.alertname;
const instance = alert.labels.instance;
const desc = alert.annotations.description || '';
// 按告警类型定制话术,值班同学一眼看懂
const templates = {
'ServiceDown': `💥 服务挂了!${instance} 无响应,${desc}`,
'DiskSpaceLow': `💾 ${instance} 磁盘快满了,${desc}。建议先看看日志是不是又没清理`,
'HighCPUUsage': `🔥 ${instance} CPU 打满了,${desc}。可能是流量上来或者有死循环`,
'CertificateExpiringSoon': `🔒 ${instance} 证书快过期了,${desc}。别等真过期了才想起来`,
};
return templates[name] || `⚠️ ${name}: ${desc || '无详细描述'}`;
}
return results;然后 n8n 会按 action 字段走不同的分支——打电话的走 Twilio 节点,发群的走企微 Webhook 节点,归档的写入 Postgres。这些全是在画布上拖出来的,只有解析逻辑需要写代码。
上线一个月后的变化:
指标 | 之前 | 之后 |
|---|---|---|
值班群日均消息数 | 140+ | 25 |
critical 响应时间 | 平均 8 分钟 | 平均 40 秒(电话叫醒) |
噪音告警打扰次数 | 每天二十多次 | 0 |
告警处理遗漏 | 偶尔发生 | 基本消除 |
最明显的是半夜的电话——以前 critical 告警混在群里可能十几分钟后才被看到,现在电话直接打过来,想睡过去都难。
n8n 最近这一年最火的玩法是跟 AI 结合,它内置了 LLM Chain、AI Agent 等节点。我把之前写的"IT 工单分类"逻辑迁到了 n8n 上,感受很深:同样的功能,n8n 上从搭建到上线只用了一下午,自己写代码得两天。

对应的 Prompt 组装节点代码:
// Function 节点:组装 LLM 的 Prompt(带 Few-shot 和知识库上下文)
const ticket = $input.first().json;
const similarCases = $('向量检索').all().map(i => i.json);
// 相似案例作为 few-shot 塞进 prompt,实测能提升 15% 左右准确率
const caseExamples = similarCases.map(c =>
`示例工单: "${c.description}"\n分类: ${c.category} | 处理组: ${c.team} | 平均耗时: ${c.avg_hours}h`
).join('\n---\n');
const prompt = `你是 IT 工单分类助手。参考以下历史相似工单的分类结果,分析新工单。
历史相似工单:
${caseExamples}
新工单: "${ticket.description}"
请输出 JSON(不要输出其他内容):
{
"category": "分类(hardware/network/account/software/other)",
"team": "建议处理组(desktop_support/network_ops/sysadmin/app_support)",
"entities": {"设备/系统": "", "位置": "", "现象": ""},
"confidence": 0.0到1.0,
"reasoning": "一句话判断依据",
"estimate_hours": 预估处理小时数
}
注意:confidence 低于 0.7 就老实承认,别硬编。低置信度会走人工确认。`;
return [{ json: { ...ticket, prompt } }];后面接 HTTP Request 节点调内网模型,再接一个 Function 节点做置信度分流。整条链路在 n8n 画布上看得清清楚楚,测试的时候还能单节点重跑——这个体验比裸写代码舒服太多。
除了事件驱动,n8n 的定时工作流也接管了我一堆例行任务。列几个典型的:

拿"备份完整性校验"举个例子——这个任务以前是写在 crontab 里的,但恢复测试没人真跑过,直到有一次真需要恢复数据时发现备份文件是坏的,差点出大事。迁到 n8n 之后,每月 1 号自动抽样三个备份文件做真实恢复演练,恢复失败直接电话告警:
#!/bin/bash
# n8n 的 Execute Command 节点里调用的恢复测试脚本
# 随机抽取最近的备份文件做恢复演练
BACKUP_DIR=/backups/postgres
TEST_DB=restore_test_$(date +%s)
# 随机抽 3 个最近 7 天的备份
picks=$(find $BACKUP_DIR -name "*.sql.gz" -mtime -7 | shuf -n 3)
fail_count=0
for backup in $picks; do
echo "开始恢复测试: $backup"
# 解压 + 恢复到测试库
if gunzip -c "$backup" | psql -h localhost -U postgres -c "CREATE DATABASE $TEST_DB" \
&& gunzip -c "$backup" | psql -h localhost -U postgres -d $TEST_DB; then
# 校验关键表行数 > 0
row_count=$(psql -h localhost -U postgres -d $TEST_DB -tAc \
"SELECT count(*) FROM users LIMIT 1")
if [ "$row_count" -gt 0 ]; then
echo "✅ $backup 恢复测试通过 (${row_count} rows)"
else
echo "❌ $backup 恢复后数据为空!"
fail_count=$((fail_count+1))
fi
else
echo "❌ $backup 恢复失败!"
fail_count=$((fail_count+1))
fi
# 清理测试库
psql -h localhost -U postgres -c "DROP DATABASE IF EXISTS $TEST_DB"
done
# 输出结果给 n8n,失败数 > 0 时 n8n 会走电话告警分支
echo "{\"fail_count\": $fail_count, \"total\": $(echo "$picks" | wc -l)}"
exit 0原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。