强大的自动化工作流工具,你值得拥有
背景
最近在家忙着开发telegram机器人项目,每次部署到nas上时,都会有一个比较重复的步骤:
- 推送到远程触发ci构建docker镜像,等待镜像构建完成
- 打开终端ssh进入nas,然后cd到机器人项目的目录
- 运行docker-compose下拉命令和重新构建命令
重复次数多了之后,我就想能不能找个工具代替(没办法我实在是懒),而且我在外面的时候就没办法进入nas所在的局域网重启了。
我需要一个按钮,按下去,程序就帮我执行完后面的2步。
其实这最后2步也可以挪到ci中完成,但我就是想找一个独立的工具,不去依赖ci。
于是我就想,应该已经有这样的工具了,配置好任务的触发器,触发之后就自动执行写好的脚本。
带着这个想法,我就立马去搜索关键词,最终搜到了n8n,基于node写的工作流自动化工具。
刚好第二天无意中看到群里有人转了奇舞精选的一篇【2024年最受欢迎的前段项目】n8n赫然在列。
好家伙,那我不得高低学一下。
搭建自动化工作流
n8n有两种方式使用,一种是直接使用官方网站,一种是在自己服务器上搭建n8n,我毫不犹疑就选了后者。毕竟这样可控制的程度更高一些
我们可以直接使用npm启动n8n,
只需要运行:
npx n8n
然后注册一个账号,进入主页就是这样子:
为了方便搭建和运行,我决定部署到在nas上,用docker容器运行n8n。
接下来整理一波n8n需要实现的工作流:
- 外部某个事件触发
- 进入到某个文件夹,执行重启脚本
- 发送邮件提示已经执行完成
开整,我们先点击右上角的【Create Workflow】新建一个工作流,然后点击中间的添加第一步,右侧会弹出可选的触发器:
app event的列表我看了一遍,全是国外的应用,看来国外开源项目的唯一缺点就是在国内水土不服。
那就只能用webhook call了,用它来新建一个触发节点,然后我们可以看到:
不得不说界面真的挺简洁明了。大概的用法就是,只要请求提供的webhook url,n8n就会从这个webhook节点开始执行任务。
接下来就是执行脚本,继续添加下一步,选择【Execute Command】
然后cd到某个目录,执行restart.sh脚本
restart.sh脚本内容如下:
# 拉取最新的镜像
docker-compose pull
# 重新创建并启动
docker-compose up -d --force-recreate
到这里我突然想到一个问题,就是我搭建的n8n是在nas里基于容器运行的,在容器内会存在命令空间隔离问题,执行docker命令肯定会提示command not found。
于是就想着如何在容器内,也能够控制宿主的docker去拉取某个镜像和重启,但是困难重重:
首先挂载宿主机的docker.sock文件,然后通过Node.js的dockerode库调用docker api去重启容器
由于我是用docker-compose.yml去管理容器,所以换成dockerode-compose去操作,本来以为这样就没问题了,然而打脸来的太快,不知道为啥报错了,过程折腾了好久,最终还是放弃了调用docker api的方案。
然后我回顾一下,如果容器直接通过ssh登录到宿主机上,也可以直接执行脚本了。
只能先这样了,如果大家有什么更好的方法,也可以在评论区发出来我学习一波。
首先问一下ai,在容器里如何获取宿主机的ip:
然后在容器内测试一下这个命令,确实可行!
再整理一下最新的工作流方案:
- 外部某个事件触发
- 执行js代码,通过child_process执行命令获取宿主机的ip地址
- 通过ssh2进入宿主机,运行命令重启容器。
好的,接下来创建一个Code节点:
获取宿主机ip的代码如下:
const { execSync } = require('child_process');
// 获取宿主机ip的命令
const findIpCommand = "ip route | awk '/default/ { print $3 }'";
const ip = execSync(findIpCommand, { encoding: 'utf8' }).trim();
console.log('ip', ip);
if (!ip) {
throw new Error('宿主ip为空');
}
return {
ip,
};
运行前,需要配置n8n支持使用内部和外部的模块,具体可以看这里:modules-in-code-node
由于n8n我是用docker-compose去部署的,所以需要在evnironment字段加上以下的值:
environment:
- NODE_FUNCTION_ALLOW_EXTERNAL=*
- NODE_FUNCTION_ALLOW_BUILTIN=*
如果引入了外部的模块,比如后面会用到的ssh2,需要先用npm安装好,因为我是用的Docker,所以我这边是选择新建一个Dockerfile,引入n8n的镜像,然后运行安装依赖的代码:
FROM docker.n8n.io/n8nio/n8n:latest
RUN npm config set registry https://registry.npmmirror.com
RUN npm install -g ssh2
所以我目前的docker-compose.yml内容如下:
version: '3.8'
services:
n8n:
container_name: n8n
build: .
restart: 'unless-stopped'
ports:
- '5678:5678'
volumes:
- ./data:/root/.n8n
environment:
# 允许http的方式登录
- N8N_SECURE_COOKIE=false
# 支持所有内部和外部的包
- NODE_FUNCTION_ALLOW_EXTERNAL=*
- NODE_FUNCTION_ALLOW_BUILTIN=*
重启n8n之后,测试运行以下这个节点,输出也没问题:
这个节点会自动把return的数据传给下一个节点,接下来就是拿到传入的ip然后通过ssh2来连接宿主机。
我们继续创建新的Core节点,代码如下:
const { Client } = require('ssh2');
const conn = new Client();
// n8n内部提供获取传入节点数据的方式,具体可以看
// https://docs.n8n.io/code/builtin/current-node-input/
// 这里拿到上一个节点写入的ip属性
const host = $json.ip;
const port = 22;
const username = 'root';
const password = 'xxxx';
// docker镜像源的用户名
const dockerUser = 'xxxxx';
// docker镜像源的密码
const dockerPassword = 'xxxx';
const loginCommand = `docker login -u ${dockerUser} -p ${dockerPassword} xxxx`;
const cdCommand = 'cd /volume1/docker/xxx';
const pullCommand = 'docker-compose pull';
const recreateCommand = 'docker-compose up -d --force-recreate';
const allCommand = `${loginCommand} && ${cdCommand} && ${pullCommand} && ${recreateCommand}`;
return new Promise((resolve) => {
conn
.on('ready', () => {
console.log('Client :: ready');
conn.shell((err, stream) => {
stream
.on('close', (code, signal) => {
console.log('执行完成');
conn.end();
})
.on('data', (data) => {
console.log(data.toString());
})
.stderr.on('data', (data) => {
console.log('STDERR: ' + data);
});
//执行命令
stream.write(allCommand + '\n');
// 退出
stream.write('exit\n');
});
}).on('close', () => {
resolve([
{
msg: 'success',
code: 0,
}
]);
})
.connect({
host,
port,
username,
password,
});
})
测试一下试试:
也是顺利执行完成,在nas管理页面也看到镜像拉取到最新的版本,并且自动重新启动了。
最后我加上发送邮件的节点,运行到这个节点就给我的邮箱发送邮件通知,这个节点搭建也比较简单,我这边就不展示了。
目前我的重启容器工作流如下:
触发的方式就是直接请求从Webhook节点里拿到的生产环境url:
当ci的docker镜像构建完之后,就可以通过curl <webhook_url>
的方式触发nas拉取镜像并重启容器。
其实还需要实现一个步骤就是内网穿透,需要把这个webhook_url暴露给外网环境,否则只能在内网里触发webhook。目前碍于篇幅的原因,我就不在这篇文章里展开啦。
总结
经过以上的步骤,已经大致摸清了n8n这个自动化工具的使用流程。它的用户界面让我印象特别深刻,特别的简洁明了,让人一目了然整个工作流的执行顺序和操作流程,让一个没使用过的人都能轻易上手创建工作流。
不仅如此,这个工具内置了许多的触发器和节点类型,功能已经相当完善,可以满足大多数人的使用需求。在官方的示例中,甚至可以跟AI结合,接收excel表格并按要求处理后导出,还有更多强大的功能等待着我们去发现和发掘。
在使用过程中,我也发现了一些问题:
- 代码节点运行js时,只能运行cjs规范的代码,目前暂不支持esm规范。
- 能作为触发器的app基本都是海外,国内的还未适配。
但是这并不影响我对这款工具的喜爱。
希望后面能够引入AI助手,我们只需要动动嘴,AI就会帮我们创建好工作流。
这样不就更加接近漫威世界里钢铁侠的贾维斯了?