Github webhook + Node Server + pm2 前端自动部署

Published on

对前端 next.js 项目自动部署做的一次折腾尝试。

更加成熟的方案应该是Docker Jenkins CI/CD那一套。

这里 Next.js 项目运行通过 Node.js server 部署,脚本执行的执行也是通过 Node.js 完成的,所以本地和服务器都会强依赖 Node 环境~

思路以及要做的效果是这样:1.本地 git push代码到github --> 2. github webhook调用请求Node server url --> 3. 服务端脚本执行代码更新,编译和启动服务

pm2 启动项目

pm2 是一个很强大的 Node 应用管理工具,保持应用后台运行再合适不过。包含了一些常用命令:list(ls), start, stop, reload, restart, delete 等等。

例如pm2 ls列出 pm2 任务:

pm2也可以运行npm命令:

pm2 start npm --watch --name <taskname> -- run <scriptname>;
# 其中 -- watch监听代码变化,-- name重命名任务名称,-- run后面跟脚本名字

如经常执行的npm run dev通过 pm2 启动就是 pm2 start npm -- run dev , 等下就需要通过 pm2 执行 yarn build 和 yarn serve 来启动项目 server。(yarn serve 在 package.json 里对应的是 next start

配合 Node 的 child_process, 通过 Node 脚本执行 pm2 命令,可以在项目根目录下新建一个sitebuild.js,做这几件事:

  • pull 最新代码
  • build 打包
  • (移除后重新) 启动项目 server
const { execSync } = require('child_process')

function reDeployVMainsite(Callback) {
  // __dirname目录下执行,加上输出便于调试
  function execSyncCurDir(cmd) {
    // console.info(`🕑 [${cmd} | Begins..]`)
    execSync(cmd, { cwd: `${__dirname}` })
    // console.info(`✨ [${cmd} -> Finish ! ]`)
  }
  try {
    execSyncCurDir('git pull')
    execSyncCurDir('yarn build')
    try {
      execSyncCurDir('pm2 delete vmainsite')
    } catch (error) {
      console.info('🌝 [vmainsite] not exist, start new one ~')
    } finally {
      execSyncCurDir('pm2 start yarn --name vmainsite -- serve')
    }
    Callback('[Start Finish]')
  } catch (error) {
    console.info('[error] - sync & build', error)
    Callback('[Start Fail]' + error.toString())
  }
}

// reDeployVMainsite()

module.exports = {
  reDeployVMainsite,
}

pm2 可以 restart 一个任务,但是在任务停掉之后依然要经过 start,这里就用 delete 和 start 对启动任务和更新做统一处理

代码里将项目 server 命名为vmainsite, 解开reDeployVMainsitelog注释,直接 Node 执行这个脚本,log 信息查看执行流程。执行完后就已经通过 pm2 运行了项目服务,可以通过pm2 list命令查看vmainsite是否存在, 没问题的话 localhost:3000 就可以访问到页面了。

这个 build.js 脚本的核心方法就是reDeployVMainsite,在服务端监听到webhook后得到调用,现在只是完成了第三步。如何监听 webhook?接着往下。

github webhook

这一部分的流程:github 仓库中有对应的 webhook 设置,配置一个 URL,在仓库中有一些push & star等等动作触发后,github 会请求这个 URL, 带上仓库信息的 payload,自己创建一个 Node.js 服务处理这个请求,触发构建脚本任务。

gituhb webhook 配置

如下 URL 配置,请求的触发时机是可以设置的,仓库的任何事件或者仅仅是 push 时。

设置完成后,可以直接对仓库进行一次 push,然后查看页面上Recent Deliveries中的记录,其中可以获取到具体的 payload 对象,这个里面的信息可以用作下面服务实现里的判断。

实现 Node.js webhook server

接着需要实现这个 payload URL 服务,主要通过 Node.js 的 http 模块编写, webhook-github.js:

/**
 * 监听 github webhook push
 */

const http = require('http')
const url = require('url')
const fs = require('fs')
const { reDeployVMainsite } = require('../../vmainsite/sitebuild')

const apiPort = 9600

const resolvePost = (req) =>
  new Promise((resolve) => {
    let chunk = ''
    req.on('data', (data) => {
      chunk += data
    })
    req.on('end', () => {
      resolve(JSON.parse(chunk))
    })
  })

const onRequest = async function (request, response) {
  const parsedUrl = url.parse(request.url)
  const pathname = parsedUrl.pathname
  if (pathname === '/xxxx/xxxx') {
    const payload = await resolvePost(request)
    // console.info("[Payload]", payload);
    // 分支信息
    response.writeHead(200, { 'Content-Type': 'application/json' })
    const result = {
      resCode: 200,
      msg: '开始构建',
    }
    response.end(JSON.stringify(result)) //将json传回浏览器

    // 写入 log
    if (payload.ref === 'refs/heads/xxxxx') {
      setTimeout(() => {
        fs.appendFileSync('./vmainsite.log', `\n[VMAIN PUSH] ${new Date().toLocaleString()}`)
        reDeployVMainsite((error) => {
          fs.appendFileSync('./vmainsite.log', `  ` + error)
        })
      }, 2000)
    }
  } else {
    response.end('unHandle url:', request.url) //将json传回浏览器
  }
}

const server = http.createServer(onRequest)
server.listen(apiPort, '127.0.0.1') //

console.log(`server started on localhost:${apiPort}`)

判断 pathname 为对应 payload URL 中的 pathname,也可以进一步判断 branch name, 在 payload 的 ref 中字段里(上面代码中 xxxx 替换为真实数据) ,导入上一步中编写的reDeployVMainsite方法,在处理响应 response 后调用(在响应前同步调用会导致超时),同时可以向 log 中写入一些触发信息便于查看。

这个处理 github webhook 的服务通过 pm2 运行起来:

pm2 start webhook-github.js

到这里就结束了。完整链路是这样:

pm2 运行 webhook-github.js,启动一个 Node.js webhook 服务,在需要自动部署的代码仓库里 push 到远端 github 的时候,就会触发请求设置的 payload URL,我们的 Node.js webhook 服务接收处理这个请求,调用构建脚本中的 reDeployVMainsite 方法,开始 pull 最新代码然后打包,重新 serve。

运行完整个流程后,pm2 中就会两个任务,一个是监听的 webhook server, 一个是项目自身的server(vmainsite)