前段时间将vue项目署到了云服务器,可是后续每次项目有修改时都需要我手动将代码push到云服务器和码云,并且还要重启docker容器,实在麻烦。于是开始研究持续集成。
本文涉及到技术有下面这些,但不会谈及太多基础知识:
思路是这样的:
想使用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,表示不使用强缓存,这也是目前通常的策略。详情可以参考这篇博文。
可以看到npm install
和npm run build
都是在镜像的构建过程中执行的,也就是说,一但项目使用了新的包,或者代码发生了修改就需要:
如果我使用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的作用是在我push一段代码到托管服务器后,托管服务器会自动向我设定好的api发送请求。利用它,我可以将代码自动同步到云服务器。
我的项目是托管在码云的,使用的是码云webhook。
服务器ip:端口号/路由
。例如使用我上面的服务器脚本:服务器ip:9633/docker_deploy
所有文件写完后就可以使用git上传到码云了。之后在服务器上创建个空文件夹,然后cd到文件夹内执行git clone 你的项目地址 .
将项目clone到服务器上,注意指令最后的那个.
,它表示clone到当前目录,否则会在当前目录下创建一个新目录来存放clone下来的代码。
比如我是在root目录下创建了docker/frontend
文件夹。
ps:
如果服务器是首次运行git的话,务必先全局配置一下用户名和邮箱,以及将公钥保存到托管服务器(码云)上。
在运行服务器脚本之前我们还得手动安装下gitee-webhook-middleware。因为我的服务器脚本是放在项目根目录下的,所以cd到docker/frontend
,执行sudo npm install gitee-webhook-middleware
即可。
如果服务器脚本在其他目录下的话,还需要先执行npm init
初始化项目目录,再安装。
除了项目的持续继承之外还需要考虑服务器脚本自身的更新问题。我希望一旦webhook-gitee.js
有更新就自动重新执行这个脚本,而不是每次还需要登录到服务器手动重启。此时需要用到pm2这个进程管理工具。cd到项目根目录执行pm2 start webhook-gitee.js --watch
即可。watch选项的含义是:当webhook-gitee.js有更新的时候自动重新运行它。
ps:
如果你的服务器还没有nodejs环境的话可以参考这篇博文来配置。
如果没有pm2的话,输入npm install pm2 -g
安装pm2。指令基本用法参考这篇博文
如果你是root账户登录的那么有两点需要注意:
/usr/bin
目录下,而非文章中的/usr/local/bin
。安装目录/bin
内,所以安装完pm2之后还需要创建个软连接ln -s 安装目录/bin/pm2 /usr/bin
我的云服务器使用的是阿里云,默认是不开启9633端口的,而我的服务器脚本监听了这个端口发来的请求,所以要去控制台安全组规则那边开启此端口。这部分操作就不赘述了。写出来只是为了提个醒。
这里收录了一些可能遇到的问题以及我个人的解决方案。
按F12打开控制台——》打开network——》选中没有更新的那个文件,观察响应头中是否有类似下面的字段
cache-control: max-age=3600
如果有说明服务器为当前页面设置了强缓存,3600表示在3600秒内无论服务器上的文件内容有无更新都使用本地缓存。
其实,我们实际工作中都会将此字段设置为cache-control:no-cache
表示不使用强缓存,这样每次打开页面浏览器都会给服务端发送请求。
如何和我一样使用了http-server
,那么所有的路由都是交给http-server
处理的。服务器端除了index.html之外无任何静态html文件可供服务器路由,而刷新页面的时候相当于发送了针对某个html文件的请求,服务器端是不可能查找到相应html文件的。
为何主页点击跳转到其他页面不受影响呢?如果你和我一样是使用vueRouter做路由管理的话,那么跳转到其他页面实则是使用js创建相应页面的DOM节点,然后将其挂载到
下,此过程不涉及服务器端的路由请求。
话说回来,想解决这个刷新问题的话有下面几种方案。
按照http-server官方文档的说法,默认情况下它会返回index.html,而匹配不到的路由则会转发给404.html。于是我们干脆将index.html更名为404.html。这样就相当于将所有路由托管给了vue-router来处理。
做法也很简单,只需将根目录下的vue.config.js
中的indexPath
改为404.html
即可。像下面这样:
module.exports = {
indexPath:"404.html"
}
之后git提交,等待一会儿再刷新页面,发现没问题了。
但这种方式破坏了我们项目本身的配置,实在不是个好方案。
由于篇幅问题请跳转到此文章阅读这部分内容。
如果只是希望解决路由问题,而对服务器没有其他需求的话,使用nginx足矣,否则才会考虑这种方案。相关内容后续补充。
未完待续…