1. 程式人生 > >利用 Github 網路鉤子實現自動化部署

利用 Github 網路鉤子實現自動化部署

GitHub 的網路鉤子(webhook)功能,可以很方便的實現自動化部署。本文記錄了使用 Node.js 的開發部署過程,當專案的 master 分支被推時,將在伺服器進行自動部署,完整程式碼見 GitHub

新增網路鉤子

  1. 在 GitHub 的相應專案首頁,點選右上角選單 Setting, 點選左側選單 Webhooks,點選右上角按鈕 Add webhook

  2. 設定 Payload URL 為接收事件的地址,Content type 建議選擇 applicaiton/jsonSecret 可選填任意字串,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 為 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 和 crypto 模組
  • Git 的 post-merge Shell 鉤子
  • profile 的自動變數設定和 pm2 工具