我又来了,之前写过一篇# 利用gitHub的webhook实现前端项目自动化部署---个人摸索的一次学习总结,是利用服务器监听webhook的push触发本地打包,然后部署到nginx静态目录,至于nginx配置需要预先配置好。 不过,最近接触了docker,是真香!于是有了这一期docker版本。
Docker
Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器或Windows 机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。----百度百科。
个人理解其实docker就是更轻量,更便捷,更易于移植的虚拟机,我们的应用服务运行在他集成好一套虚拟环境,主要分为两个概念:
镜像(image):类似操作系统镜像,docker创建虚拟环境需要基于一个docker镜像,镜像定义了系统需要什么初始的内容。
容器(container):就是虚拟环境,基于image创建, 像一个虚拟机,实际上是一个进程,所以更轻量。
更多docker知识不再赘述,可自行了解。
准备
一个github前端项目,并在云服务器上拉取代码,一台云服务器-----服务器安装好git、node、docker,因为是使用github的wehooks, 而且我使用node运行服务监听wehooks,git、node必不可少, git、node安装过程不是本文重点,仅列出云服务器 docker 安装方法:
# 1: 安装必要的一些系统工具
sudo yum install -y yum-utils
# 2: 添加软件源信息,使用阿里云镜像
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
# 3: 安装 docker-ce
sudo yum install docker-ce docker-ce-cli containerd.io
# 4: 开启 docker服务
sudo systemctl start docker
为前端项目添加docker配置
在项目根目录添加Dockerfile文件,定义要生成的镜像:
# build stage 第一个阶段
# 基于node:12-alpine(这个node镜像较小,足够用) 命名build-stage
FROM node:12-alpine as build-stage
# 工作目录 app
WORKDIR /app
# 复制package声明文件到工作目录,安装依赖
COPY package*.json ./
RUN npm install
# 复制其他文件到工作目录
COPY . .
# 项目打包
RUN npm run build
# deploy stage 第二个阶段
# 基于nginx:stable-alpine(你也可以指定nginx版本,不过生成的镜像可能大一些)
FROM nginx:stable-alpine
# 从build-stage阶段 复制 /app/build/ 文件夹的内容到nignx静态目录 /usr/share/nginx/html
COPY --from=build-stage /app/build/ /usr/share/nginx/html
# docker内环境暴露出80端口
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
添加 .dockerignore 文件:
node_modules
我的是前端单页项目,还需要接口转发,所以在项目里加了nginx.conf文件,利用deploy.sh引用,看个人需要。
配置 webhook
为git项目配置webhook, 项目变动会带参数请求配置的接口:
Secret用来校验请求合法性
在服务器上创建服务监听webhook
创建一个node服务, index.js:
const Koa = require('koa');
const Router = require('koa-router');
const body = require('koa-body'); // 用来解析post参数
const crypto = require('crypto')
const secret = '你在webhook中配置的secret';
const app = new Koa();
const router = new Router();
function run_cmd(cmd, args, callback) {
var spawn = require('child_process').spawn;
var child = spawn(cmd, args);
var resp = '';
var endTip = `${args[1]}部署完成, runing on port ${args[2]}`;
child.stdout.on('data', function(buffer) { resp += buffer.toString(); });
child.stdout.on('end', function() { callback (resp + ' ' + endTip) });
}
function sign (data) {
return `sha1=${crypto.createHmac('sha1', secret).update(data).digest('hex')}`
}
app.use(body());
// 一个捕捉错误的中间件
app.use(async (ctx, next) => {
try{
await next();
}catch(err){
console.log(err);
ctx.throw(500)
}
})
// post
router.post('/dockerDeploy', async ctx => {
const port = ctx.query.port;
const header = ctx.request.header;
// 参数就是payload
const body = ctx.request.body;
// 事件
const event = header['x-github-event'];
// 签名
const sig = header['x-hub-signature'];
// 本地生成签名
const localsig = sign(JSON.stringify(body));
// 分支
const ref = body.ref;
// 项目名称
const name = body.repository.name;
console.log(ref, event, port)
// 验证签名存在
if(!sig) {
ctx.body = 'no signature!'
return;
}
// 验证签名合法
if(sig !== localsig) {
ctx.body = 'signature is wrong!'
return;
}
ctx.body = 'ok';
// 仅在 push 主分支时处理
if (ref === 'refs/heads/main' && event === 'push') {
// 运行
run_cmd('sh', ['./deploy.sh', name, port], function(text){ console.log(text) });
}
})
app.use(router.routes()).use(router.allowedMethods());
app.listen(7776, () => {
console.log('listening on 7776 success!')
})
deploy.sh:
#!/bin/bash
# 项目名称
WEB_NAME="$1"
# 端口号
PORT="$2"
# 服务器上的项目代码库路径
PROJECT_PATH="/root/project/${WEB_NAME}/"
echo '部署开始.....'
cd $PROJECT_PATH
git pull
echo '更新代码'
# 创建映射挂载的目录
mkdir -p /usr/dokcer_nginx_data/$WEB_NAME/{conf.d,log}
# 删除原配置文件
rm -rf /usr/docker_nginx_data/$WEB_NAME/conf.d/*
# 移动nginx.conf并重命名
cp nginx.conf /usr/docker_nginx_data/$WEB_NAME/conf.d/default.conf
# 创建镜像
docker build . -t ${WEB_NAME}-image:latest
echo '创建镜像'
# 我们每次生成镜像是都未指定标签,从而重名导致有空悬镜像,删除一下
docker rmi $(docker images -f "dangling=true" -q)
echo '删除空悬镜像dangling image'
# 查找docker容器,停止并销毁他
docker ps -a -f "name=^${WEB_NAME}-container" --format="{{.Names}}" | xargs -r docker stop | xargs -r docker rm
echo '销毁旧容器'
docker run -d -p ${PORT}:80 -v /usr/docker_nginx_data/${WEB_NAME}/conf.d:/etc/nginx/conf.d -v /usr/docker_nginx_data/log:/var/log/nginx --name ${WEB_NAME}-container ${WEB_NAME}-image:latest
echo '以新镜像创建的容器运行并挂载本地的nginx配置文件, 80端口映射到所主机PORT端口'
tips: docker run 的 -v是 宿主机目录:docker环境目录, 挂载的是目录!!
package.json:
{
"name": "webhook",
"version": "1.0.0",
"description": "",
"main": "deploy.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"koa": "^2.13.1",
"koa-router": "^7.4.0",
"koa-body": "^4.1.0"
}
}
最后用pm2 start index.js --watch 启动服务
这样我们建立了一个服务监听github webhook的推送, 触发脚本执行更新代码、对应目录创建、复制配置文件、基于dockerfile创建docker镜像,创建docker容器,运行。项目打包部署在docker的虚拟环境中,映射在我们宿主机的对应端口,并且我们利用-v 将docker容器中的nginx配置引用目录改为了宿主机的自定义目录,从此前端只管愉快的的写代码->提交代码,告别手工打包上传部署项目的时代!