GitHub 的網絡鈎子(webhook)功能,可以很方便的實現自動化部署。本文記錄了使用 Node.js 的開發部署過程,當項目的 master 分支被推時,將在服務器進行自動部署,完整代碼見 GitHub
添加網絡鈎子
-
在 GitHub 的相應項目首頁,點擊右上角菜單
Setting
, 點擊左側菜單Webhooks
,點擊右上角按鈕Add webhook
-
設置
Payload URL
為接收事件的地址,Content type
建議選擇applicaiton/json
,Secret
可選填任意字符串,Which events would you like to trigger this webhook?
設為Just the push event.
,勾選Active
,點擊下方的Add webhook
按鈕
開發處理請求
接收請求
使用 Node.js 建立一個 http 服務器,接收 POST 請求並處理其提交數據
const { createServer } = require('http');
const port = process.env.GITHUB_WEBHOOK_PORT || '3000';
const server = createServer((req, res) => {
if('POST' === req.method){
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
});
}
})
server.listen(port, () => {
console.log(`Listening on ${port}`);
});
如果需要更改默認端口 3000,可以先運行以下命令添加環境變量(NUMBER 為任意端口)
export GITHUB_WEBHOOK_PORT=NUMBER
解析 Body
在 req 的 end 事件處理器中,把字符串 body 解析成對象
req.on('end', () => {
try{
body = JSON.parse(decodeURIComponent(body).replace(/^payload=/, ''));
}catch(e){
console.log(e)
}
如果 Content type
設置為 applicaiton/json
,只需要 body = JSON.parse(body)
即可,以上代碼兼容了 Content type
設置為 application/x-www-form-urlencoded
的情況
拉取更新
根據 body 的 push 負載,提取項目和分支信息,如果是 master 分支,則執行進入對應項目,拉取分支的命令
if('object' === typeof body){
if('refs/heads/master' === body.ref){
const { exec } = require('child_process');
const command = `cd ../${body.repository.name} && git pull origin master`;
exec(command, (error, stdout, stderr) => {
});
注意這里的項目所在的目錄,與此應用所在的目錄,是在同一個父目錄下的,如果不是可以相應調整命令的進入路徑
驗證密鑰
以上步驟已經實現了自動拉取更新,不過存在安全性的問題,因為不僅僅 GitHub 可以發送這樣的請求,所以最好設置 Secret 以進行安全驗證
const secret = process.env.GITHUB_WEBHOOK_SECRET || '';
...
req.on('end', () => {
if('' !== secret){
const { createHmac } = require('crypto');
let signature = createHmac('sha1', secret).update(body).digest('hex');
if(req.headers['x-hub-signature'] !== `sha1=${signature}`){
console.log('Signature Error');
res.statusCode = 403;
res.end();
return;
}
}
運行應用前,先運行以下命令增加密鑰變量(STRING 為任意字符串)
export GITHUB_WEBHOOK_SECRET=STRING
- 設置了 Secret 后,GitHub 在發送請求時,會在請求頭增加 x-hub-signature 為 sha1=SIGNATURE, 其中 SIGNATURE 為 body 的 密鑰為 Secret,算法為 sha1 的 HMAC 16 進制值
- 通過對 Secret 的檢驗,可以確保只有知道了 Secret,才能發送正確的帶 x-hub-signature 頭的請求,否則將拒絕請求
- 以上代碼兼容了不設置 Secret 的情況,即如果沒有增加變量 GITHUB_WEBHOOK_SECRET,則按原有邏輯處理,不會進行檢驗
本地鈎子構建
如果項目在拉取更新后需要構建,那么可以 command 變量后面加上構建命令,例如 && npm run build
,但是不同項目的構建命令有可能是不一樣的,而且有的項目的構建命令可能還比較復雜,這些情況下可以通過設置 git 的本地鈎子進行處理
cd /PATH/TO/PROJECT/.git/hooks
nano post-merge
#!/bin/sh
SHELL_SCRIPT
chmod +x post-merge
- 其中 /PATH/TO/PROJECT/ 為項目的目錄位置,SHELL_SCRIPT 可以為任意 Shell 腳本
- 因為 git pull 是 git fetch 和 git merge 的組合,所以拉取更新會觸發 post-merge 鈎子
- 默認新增的文件是沒有執行權限的,所以需要通過
chmod
增加x
位
部署應用上線
應用部署上線需要實現持久化和自動化,即項目應該一直在運行,如果服務器重啟,項目應該自動啟動
變量自動創建
/etc/profile.d/ 里的變量創建腳本會在服務器重啟時自動運行,所以添加一個設置腳本進去
nono /etc/profile.d/github-webhook.sh
export GITHUB_WEBHOOK_PORT=NUMBER
export GITHUB_WEBHOOK_SECRET=STRING
運行以下命令可以使變量創建馬上生效
source /etc/profile
pm2 運行應用
pm2 可以確保 Node 應用的持續運行,並可通過配置實現監控和熱更新等功能
npm install pm2 -g
pm2 start app.js --name github-webhook
重啟自動運行
pm2 還內置支持配置自啟動原有應用,通過以下命令實現
pm2 startup
pm2 save
pm2 startup
會創建並開啟開機自動運行的服務, pm2 save
會保存當前的 pm2 運行應用,作為重啟后的恢復內容
總結
在基於 GitHub webhook 的自動化部署中,主要使用了以下技術:
- Node.js 的 http,child_process 和 crypto 模塊
- Git 的 post-merge Shell 鈎子
- profile 的自動變量設置和 pm2 工具