都9012年了,你还在手动部署代码吗

背景

公司从当初的SVN代码版本控制,FTP手动上传项目代码zip压缩包,到如今的git代码版本控制,jenkins一键打包部署环境,已经初步完成了手动到自动的大跃进.回过头来看看自己的项目,还处在本地仓库修改代码 -> 提交远程github仓库 -> 自己上服务器手动pull最新分支代码的原始阶段.不能忍

OK,接下来让我们开始我们的进化偷懒之旅,大家一起跟随我的心路历程一起进化.

目标

当我们本地仓库修改完成push远程仓库之后, 服务器能够自动拉取最新分支代码,自动完成项目部署.

前置条件(废话)
  1. 有个本地仓库能够连接到远程仓库,能够push代码
  2. 服务器仓库能够从远程仓库pull代码
  3. 远程仓库有webhooks功能

行动

工欲善其事必先利其器,开始行动前有必要理解一波webhooks钩子自动部署原理;

webhooks自动部署原理
1
本地仓库 -> (push提交代码) -> 远程仓库(webhooks钩子) -> (发送带有key的post请求) -> 测试/生产服务器(执行部署脚本)

知悉了原理之后我们来看看我们需要准备些什么:

  • 带有webhooks的远程仓库(gitlab,github,gitee等等)
  • 能够接收post请求的服务和测试/生产服务器
  • 部署脚本(.sh)
远程仓库webhooks设置

远程仓库的webhooks设置你只需要找到具体位置点进去:

  1. URL: 设置post请求的地址(即服务器服务地址)

  2. key: 这个key可以设置也可以不设置,建议设置,防止他人随意请求服务器接口然后自动疯狂拉代码部署,相当于一个验签

部署脚本auto_build.sh

部署脚本(.sh)就自由发挥,自己平时怎么手动部署的就咋写就完事了,创建文件auto_build.sh.Linux下创建目录使用mkdir 目录名,创建文件使用touch 文件名.

1
2
3
4
5
6
7
8
9
10
PROJECTNAME_PATH = '/usr/local/src/项目目录';

echo "Starting deployment"
cd $PROJECTNAME_PATH
git checkout .
git pull
npm i
gulp release
pm2 start ecosystem.config.js
echo "Finished"

执行脚本的时候注意一下用户权限的问题以及基本命令的全局安装.

编写服务deploy.js

接下来的重头戏就是构建起一个能够接收远程仓库post请求的服务,这同样也很简单.你可以借助插件github-webhook-handler 的帮助,快速建立起这样一个服务,创建文件deploy.js.

ps: 这里的secret就是上面webhooks设置中的key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
var http = require('http')
var createHandler = require('github-webhook-handler')
var handler = createHandler({ path: '/webhook', secret: 'myhashsecret' })

http.createServer(function (req, res) {
handler(req, res, function (err) {
res.statusCode = 404
res.end('no such location')
})
}).listen(7777)

handler.on('error', function (err) {
console.error('Error:', err.message)
})

handler.on('push', function (event) {
console.log('Received a push event for %s to %s',
event.payload.repository.name,
event.payload.ref)
})

handler.on('issues', function (event) {
console.log('Received an issue event for %s action=%s: #%d %s',
event.payload.repository.name,
event.payload.action,
event.payload.issue.number,
event.payload.issue.title)
})

ps: 还可以设置当有人给自己仓库提issues时发邮件提醒自己23333333

如果服务无法启动,报错类似Error: Cannot find module 'github-webhook-handler',可是依赖包明明已经全局安装过了.确认方法:

1
2
3
4
5
npm root -g							// 查看npm全局安装路径
=> /root/.nvm/versions/node/v9.10.1/lib/node_modules

cd /root/.nvm/versions/node/v9.10.1/lib/node_modules
ll // 查看目录文件确认依赖是否安装

确认安装后可以通过以下步骤解决:

  1. 进入deploy.js所在目录
  2. 执行以下命令
1
npm link github-webhook-handler

现在,你需要做的是将auto_build.shdeploy.js结合起来.

阅读上面代码,你会发现handler监听到push事件调用对应的函数,所以你要做的就是在函数中执行auto_build.sh命令,你需要在deploy.js添加以及更改如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 新增runCmd函数
funciton runCmd(cmd, args, callback) {
var spawn = require('child_process').spawn; // node子进程
var child = spawn(cmd, args);
var response = '';

child.stdout.on('data', buffer => response += buffer.toStirng());
child.stdout.on('end', () => callback(response));
}

// 修改push监听事件 我这里auto_build.sh和deploy.js位于同一目录文件中
handler.on('push', function(event) {
runCmd('sh', ['./auto_build.sh'], function(text) { console.log(text) });
});

ps: 可以通过console.log()在相应的步骤输出相应提示,方便查错

运行服务deploy.js

我们希望deploy服务能够一直运行在服务器上,当远程仓库发送post请求提示我们有新代码push的时候能够正常执行部署脚本.这时我们需要以守护进程的方式来启动deploy.js服务,当服务意外崩溃时能够重启服务,彻底解放我们的双手.

这里提供两种方法供大家选择,都可以通过npm安装:

  1. forever就是保证进程退出时,应用会自动重启。
1
2
3
4
5
6
forever start deploy.js		   				  // 启动服务进程
forever list // 列出所有进程
forever logs id // 查看进程输出日志
forever stop id // 停止服务进程
forever restart id // 重启服务进程
forever -m 5 deploy.js // 最多重启次数为5
  1. pm2功能强大,除了重启进程以外,还能实时收集日志和监控。
1
2
3
4
5
6
pm2 start deploy.js		   				  // 启动服务进程
pm2 list // 列出所有进程
pm2 logs id // 查看进程输出日志
pm2 stop id // 停止服务进程
pm2 restart id // 重启服务进程
pm2 delete id // 删除服务进程
Nginx反向代理

因为我的服务器在腾讯云上面,7777端口并未放开,所以通过一个Nginx反向代理到服务器安全组开放端口.

在远程仓库发送post测试请求前一定要确认

自己服务器安全组端口已放开!!!

自己服务器安全组端口已放开!!!

自己服务器安全组端口已放开!!!

重要的事情说三遍! 下面是我nginx配置

1
2
3
4
5
6
7
8
server {
listen 8080;
server_name localhost;

location /webhook {
proxy_pass http://127.0.0.1:7777;
}
}

ps: Linux下重启nginx,进入nginx的sbin目录运行命令./nginx -s reload

Bug

Nginx启动 ! deploy服务启动 ! 远程仓库webhook设置完毕 ! 点击测试按钮 !

然后……

报错!!! (强颜欢笑and笑容逐渐消失.jpg

1
2
3
{
"error": "No X-Hub-Signature found on request"
}

看了半天……才想起来我的项目代码远程仓库是码云gitee.com,因为github私人private仓库2019年2月之前都是需要付费的,所以涉及私人的项目代码我都选择了码云作为远程仓库,然而我的插件是github-webhook-handler!!!!能通才有鬼 TAT

没关系,小问题.都到这一步了,男人怎么能说不行!

先去github上搜下有没有对应gitee的webhook插件,要是没有就forkgithub-webhook-handler下来自己改下改成适配gitee码云的.果然让我搜到了gitee-webhook-middleware,然后deploy.js改一改

1
2
var createHandler = require('gitee-webhook-middleware');
var handler = createHandler({ path: '/webhook', token: '你的key' });

重启服务,点击测试 !

1
2
3
{
"ok": true
}

完美 !

上服务器一看项目代码还是旧的,妈耶还有坑…后来发现gitee的post请求事件是Push Hook

1
2
// handler.on('push', function() {});
handler.on('Push Hook', function() {});

至此,重新测试,项目代码更新成功 !

结果

偷懒成功! 偷懒果然是工程师第一生产力

现在每当我本地仓库push代码到远程仓库,服务器就会拉取最新版本代码自动部署.

这只是我一次简单尝试,我们完全可以扩展自动化测试,自动化部署于一体,完成多人协作开发时的CI/CD,大大减少人力成本,减少人为错误的发生,提高大家的工作效率.