webhook+docker实现vue项目的持续集成

目录

  • 前言
  • Docker
    • Dockerfile
    • docker-compose
  • 编写服务器脚本
  • webhook
  • 服务端使用git拉取项目
  • 启动服务器脚本
    • 安装gitee-webhook-middleware
    • pm2
  • 安全组配置
  • 一些问题
    • 容器成功重启,但浏览器访问时页面内容没更新。
    • 页面刷新报404
      • 404.html
      • 将vue项目托管到nginx上。
      • 自己手写一个服务器脚本

 
 

前言

前段时间将vue项目署到了云服务器,可是后续每次项目有修改时都需要我手动将代码push到云服务器和码云,并且还要重启docker容器,实在麻烦。于是开始研究持续集成。
本文涉及到技术有下面这些,但不会谈及太多基础知识:

  • linux
  • docker/docker-compose
  • pm2
  • nodejs
  • http-server

思路是这样的:

  1. 提前写好Dockerfile与docker-compose文件存在服务器。
  2. 提前在云服务器写好一个shell脚本,内含拉取最新代码的git命令,以及重启docker-compose的命令。
  3. 提前在服务器运行一个nodejs服务器脚本,用于接收来自码云的webhook请求(http)。
  4. 在自己电脑上使用git将项目push到码云。
  5. 码云的webhook会以发送http请求的方式告知云服务器有用户push了代码。
  6. 当服务器收到webhook的请求后自动启动一个新进程运行此shell脚本。

我的项目目录如下:
webhook+docker实现vue项目的持续集成_第1张图片
 

Docker

Dockerfile

想使用docker部署vue项目必然需要一个合适的镜像,然而我没找到直接可用的镜像。vue官网确实给出了一个Dockerfile,可以构建出包含nodejs环境的镜像,不过这个镜像内是没有c语言编译环境的,而我常用的sass是需要在本机编译的,所以只得自己写了一个Dockerfile,读者可以借鉴。另外,如果有可以简化的地方请留言告诉我,谢谢。

在项目根目录下创建Dockerfile文件,下面是文件的内容:

# escape=`
FROM ubuntu
RUN apt-get update --assume-yes && `
apt-get upgrade --assume-yes && `
apt-get install --assume-yes curl software-properties-common && `
curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | apt-key add - && `
add-apt-repository "deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" && `
apt-get update --assume-yes && `
apt-get install nodejs --assume-yes && `
apt install nodejs --assume-yes && `
apt install npm --assume-yes && `
npm config set registry http://registry.npm.taobao.org/ && `
npm install n -g && `
n stable && `
npm install -g http-server
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 8080
CMD [ "http-server","dist","-c-1" ]

唯一需要注意的就是在使用http-server的时候要指定-c选项并将其值设为-1,表示不使用强缓存,这也是目前通常的策略。详情可以参考这篇博文。

docker-compose

可以看到npm installnpm run build都是在镜像的构建过程中执行的,也就是说,一但项目使用了新的包,或者代码发生了修改就需要:

  1. 停止正在运行的容器
  2. 重新构建镜像
  3. 使用新镜像创建容器

如果我使用docker启动容器的话,在不知道容器id的情况下无法停止指定的容器。为了解决这个问题,可以借助docker-compose。
在项目根目录下创建docker-compose.yml文件,下面是文件的内容:

version: "3.7"
services: 
    web:
        build: .
        image: word/ys
        ports: 
            - "8080:8080"

是否指定image无关紧要,build的值为.表示使用当前目录下的Dockerfile文件来构建镜像。ports表示将容器内的8080端口(后)映射到服务器的8080端口(前)。

 
 

编写服务器脚本

我们需要在云服务器启动一个小型服务器脚本,目的是接收托管服务器(码云)发来的webhook请求。我将脚本直接放在了项目的根目录下且命名为webhook-gitee,脚本使用nodejs编写,内容如下:

const http = require('http')
/* 
gitee-webhook-middleware是一个中间件,可以接收和验证webhook请求。
使用这个中间件一定要将webhook请求头的contentType设置为application/json。
express等框架大材小用,自己写又重复造轮子,所以选择这个包。
*/
const createHandler = require('gitee-webhook-middleware')
const handler = createHandler({
    //api
    path: "/docker_deploy",
    //验证请求者身份的密码
    token: "yourSecret"
})

/* 这才是关键代码。
child_process是nodejs自带的库,用来创建子进程。
我们可以使用子进程执行shell脚本,在脚本内拉取最新代码 */
const { spawn } = require("child_process")

async function run_cmd(cmd, args) {
    return new Promise((resolve, reject) => {
        /* 
            第一个参数是要执行的命令,
            args是命令的参数 
        */
        const child = spawn(cmd, args);
        let resp = ""
        /* 
            stdout相当于在主进程与子进程间建立的管道,
            数据会在管道内流动,
            监听data实则是监听从子进程流到主进程的数据,
            这里的数据是shell脚本执行时打印在控制台的内容 
        */
        child.stdout.on("data", function (buffer) {
            resp += buffer.toString()
        })
        child.stdout.on("error", function (error) {
            //不抛error,返回错误堆栈信息即可
            resolve(error)
        })
        child.stdout.on("end", function () {
            resolve(resp)
        })
    })
}


handler.on('error', err => {
    console.log(`webhook error: ${err}`);
})

handler.on("Push Hook", async event => {
    /* 只有往master分支推送代码的时候才pull它 */
    if(event.payload.ref==="refs/heads/master"){
        console.log("master update")
        const msg = await run_cmd('sh', ["./deploy-dev.sh"])
        //脚本执行完毕或异常退出后打印控制台的输出
        console.log(msg);
    }
})

http.createServer((req, res) => {
    //使用handler处理请求
    handler(req, res, err => {
        res.statusCode = 404
        res.end("access denied")
    })
}).listen(9633, () => {
    console.log('listening on 9633 ...')
})

 
 

webhook

webhook的作用是在我push一段代码到托管服务器后,托管服务器会自动向我设定好的api发送请求。利用它,我可以将代码自动同步到云服务器。
我的项目是托管在码云的,使用的是码云webhook。

  1. 打开自己项目的「仓库主页」->「管理」->「WebHooks」
  2. 点击添加,URL内输入服务器API,格式为:服务器ip:端口号/路由。例如使用我上面的服务器脚本:服务器ip:9633/docker_deploy
  3. 设定WeebHook密码,这可以一定程度上避免伪造的webhook请求。
  4. 选择Push事件并勾选激活选项。其他事件根据需要进行勾选即可,持续集成的话,接收push事件足矣。
     
     

服务端使用git拉取项目

所有文件写完后就可以使用git上传到码云了。之后在服务器上创建个空文件夹,然后cd到文件夹内执行git clone 你的项目地址 . 将项目clone到服务器上,注意指令最后的那个. ,它表示clone到当前目录,否则会在当前目录下创建一个新目录来存放clone下来的代码。

比如我是在root目录下创建了docker/frontend文件夹。

ps:
如果服务器是首次运行git的话,务必先全局配置一下用户名和邮箱,以及将公钥保存到托管服务器(码云)上。
 
 

启动服务器脚本

安装gitee-webhook-middleware

在运行服务器脚本之前我们还得手动安装下gitee-webhook-middleware。因为我的服务器脚本是放在项目根目录下的,所以cd到docker/frontend,执行sudo npm install gitee-webhook-middleware即可。

如果服务器脚本在其他目录下的话,还需要先执行npm init初始化项目目录,再安装。
 

pm2

除了项目的持续继承之外还需要考虑服务器脚本自身的更新问题。我希望一旦webhook-gitee.js有更新就自动重新执行这个脚本,而不是每次还需要登录到服务器手动重启。此时需要用到pm2这个进程管理工具。cd到项目根目录执行pm2 start webhook-gitee.js --watch 即可。watch选项的含义是:当webhook-gitee.js有更新的时候自动重新运行它。

ps

  1. 如果你的服务器还没有nodejs环境的话可以参考这篇博文来配置。

  2. 如果没有pm2的话,输入npm install pm2 -g 安装pm2。指令基本用法参考这篇博文
    如果你是root账户登录的那么有两点需要注意:

    • 全局指令应该是在/usr/bin目录下,而非文章中的/usr/local/bin
    • 使用-g全局安装的包的软连会被放在安装目录/bin内,所以安装完pm2之后还需要创建个软连接ln -s 安装目录/bin/pm2 /usr/bin

 
 

安全组配置

我的云服务器使用的是阿里云,默认是不开启9633端口的,而我的服务器脚本监听了这个端口发来的请求,所以要去控制台安全组规则那边开启此端口。这部分操作就不赘述了。写出来只是为了提个醒。

 
 

一些问题

这里收录了一些可能遇到的问题以及我个人的解决方案。

容器成功重启,但浏览器访问时页面内容没更新。

按F12打开控制台——》打开network——》选中没有更新的那个文件,观察响应头中是否有类似下面的字段

cache-control: max-age=3600

如果有说明服务器为当前页面设置了强缓存,3600表示在3600秒内无论服务器上的文件内容有无更新都使用本地缓存。
其实,我们实际工作中都会将此字段设置为cache-control:no-cache表示不使用强缓存,这样每次打开页面浏览器都会给服务端发送请求。
 

页面刷新报404

如何和我一样使用了http-server,那么所有的路由都是交给http-server处理的。服务器端除了index.html之外无任何静态html文件可供服务器路由,而刷新页面的时候相当于发送了针对某个html文件的请求,服务器端是不可能查找到相应html文件的。

为何主页点击跳转到其他页面不受影响呢?如果你和我一样是使用vueRouter做路由管理的话,那么跳转到其他页面实则是使用js创建相应页面的DOM节点,然后将其挂载到下,此过程不涉及服务器端的路由请求。

话说回来,想解决这个刷新问题的话有下面几种方案。

  1. 将webpack出口文件命名为404.html。
  2. 选择功能更强大的nginx。
  3. 自己手写一个服务器脚本。

404.html

按照http-server官方文档的说法,默认情况下它会返回index.html,而匹配不到的路由则会转发给404.html。于是我们干脆将index.html更名为404.html。这样就相当于将所有路由托管给了vue-router来处理。
做法也很简单,只需将根目录下的vue.config.js中的indexPath改为404.html即可。像下面这样:

module.exports = {
	indexPath:"404.html"
}

之后git提交,等待一会儿再刷新页面,发现没问题了。
但这种方式破坏了我们项目本身的配置,实在不是个好方案。

将vue项目托管到nginx上。

由于篇幅问题请跳转到此文章阅读这部分内容。
 

自己手写一个服务器脚本

如果只是希望解决路由问题,而对服务器没有其他需求的话,使用nginx足矣,否则才会考虑这种方案。相关内容后续补充。
未完待续…

你可能感兴趣的:(经验总结与分享,持续集成系统,docker,vue)