1. 程式人生 > >【nodejs原理&原始碼賞析(9)】用node-ssh實現輕量級自動化部署

【nodejs原理&原始碼賞析(9)】用node-ssh實現輕量級自動化部署

目錄

  • 一. 需求描述
  • 二. 預備知識
    • IP+埠訪問
    • 域名訪問
  • 三. Nodejs應用的手動部署
  • 四. 基於nodejs的自動部署
    • 4.1 package.json中的scripts
    • 4.2 自動化釋出指令碼deploy.js
    • 4.3 遠端指令碼deploy.sh
  • 五. 小結

示例程式碼託管在:http://www.github.com/dashnowords/blogs

部落格園地址:《大史住在大前端》原創博文目錄

華為雲社群地址:【你要的前端打怪升級指南】

一. 需求描述

前端工程出包後實現簡易的自動化部署。

二. 預備知識

網站的建設可以使用任何自己熟悉的框架,三大框架都有自己的官方Cli工具,從程式碼編寫到生成可用於生產環境部署的包基本都有自動化命令,各個打包工具也在零配置的追求上做了很多工作。本篇中從得到一個生產環境的包以後開始,對站點部署的相關知識進行一些介紹。

首先你需要一個Web伺服器,常見的有:

  • Nginx
  • Tomcat
  • Apache或相關整合環境
    • XAMMPApache+MySQL+PHP+PERL
    • LAMPLinux+Apache
      +MySQL+PHP
  • nodejs或相關框架+守護程序
    • Express
    • Koa2

以上任何一種在伺服器上執行起來後都可以擔任Web伺服器的角色,只是具備的擴充套件功能和應用場景有區別,Nginx基本上是正式環境部署的首選方案。常見的基本部署方案如下:

IP+埠訪問

使用訪問,可直接訪問對應埠的服務,部署方式相對簡單:

域名訪問

使用域名訪問時,通常會使用A記錄進行解析,它只能對映到80埠(https時對映到443),這時就需要使用反向代理將80埠的請求分發到本地不同的內部埠來訪問對應服務:

本例中使用域名+IP的方式進行部署。

三. Nodejs應用的手動部署

Express

為例,步驟如下:

  1. 首先通過yarn global add express-generatornpm install express-generator -g全域性安裝腳手架
  2. 完成後在工作目錄通過命令列express mydemo --ejs生成一個使用ejs作為模板渲染引擎的express工程
  3. 命令列輸入cd mydemo && yarncd mydemo && npm install安裝依賴
  4. /bin/www檔案中修改埠號為期望的埠號(自動生成的是80埠),例如3001
  5. 將前端工程build出的包整體複製貼上到/public目錄中
  6. 此時在本地工程根目錄下輸入npm start後,在瀏覽器中http://localhost:3001就可以訪問到網站了
  7. 使用FTP工具(如FlashFxpFileZilla Client等)連線到部署機器,將mydemo目錄壓縮為zip包後上傳到伺服器指定目錄。
  8. 使用SSH工具(如XshellMobaXter)登入遠端機器,假設為linux系統,輸入unzip mydemo.zip解壓壓縮包,然後cd mydemo進入服務端工程,輸入npm start即可在伺服器上開啟Web服務,通過ip地址:3001就可以訪問到網站。
  9. 但是如果此時SSH工具斷開連線,就會發現express應用無法繼續訪問了,所以還需要一個守護程序來維持應用的啟動狀態,在服務端通過npm install pm2 -g來安裝nodejs應用的部署管理模組,它可以實現多應用管理、Hook更新、自動重啟等等許多常用功能,詳細資訊可以訪問 【PM2官方網站】。
  10. 最後,在工程根目錄輸入pm2 start ./bin/www即可以後臺模式執行應用。

四. 基於nodejs的自動部署

4.1 package.json中的scripts

瞭解了手動部署的過程後,就可以通過自動化指令碼來實現後續的更新和部署。nodejs工程的自動化是依賴於package.json檔案中的scripts配置項來實現的,例如使用vue-cli搭建的工程中就會帶有:

{
    ...
    "scripts": {
        "serve": "vue-cli-service serve",
        "build": "vue-cli-service build",
        "lint": "vue-cli-service lint"
      },
    ...
}

在專案根目錄下開啟命令列,輸入npm run [script-key]或者yarn [script-key][script-key]指上面示例中的serve,build,lint這些鍵名),就會執行對應的scripts[key]對應的命令。我們先新增一條用於自動部署的指令碼指令:

{
    ...
    "scripts": {
        "build": "vue-cli-service build",
        "deploy" "node ./scripts/deploy/deploy.js"
      },
    ...
}

當輸入npm run deployyarn deploy時,實際上就相當於用node去執行./scripts/deploy/deploy.js這個指令碼,其中就編寫了自動化釋出的指令。scripts還提供了生命週期鉤子,比如你對接的是一個測試環境,希望每次build後自動釋出,就可以使用post鉤子來實現:

{
    ...
    "scripts": {
        "build": "vue-cli-service build",
        "postbuild":"npm run deploy",
        "deploy" "node ./scripts/deploy/deploy.js"
      },
    ...
}

這樣每次build執行完畢後,就會自動執行npm run deploy,也就是執行釋出的指令碼。

4.2 自動化釋出指令碼deploy.js

自動化釋出指令碼需要完成這樣幾個任務:

  • 將打包出的dist壓縮為zip
  • 使用SSH連線部署伺服器,將zip包發上去
  • 上傳完畢後,啟動事先寫好後續任務並放在伺服器上的shell指令碼來完成剩餘的工作

涉及的幾個模組包括實現SSH連線的node-ssh模組(底層是ssh2模組,這個模組是一個Promise封裝),用於製作zip壓縮包的archiver模組。node-ssh提供了上傳本地目錄的方法,但實際使用過程中發現並不穩定,從告警資訊來看是node-stream模組在傳送時將不同格式的檔案轉換為流時可能會出現異常,實測大約有一半概率觸發,嘗試修改了一些配置引數並未解決,所以採用archiver模組先壓縮為單個檔案後再進行上傳。

參考程式碼如下:

const path = require('path');
const archiver =require('archiver');
const fs = require('fs');
const node_ssh = require('node-ssh');
const ssh = new node_ssh();
const srcPath = path.resolve(__dirname,'../../dist');
const configs = require('./config');

console.log('開始壓縮dist目錄...');
startZip();

//壓縮dist目錄為public.zip
function startZip() {
    var archive = archiver('zip', {
        zlib: { level: 5 } //遞迴掃描最多5層
    }).on('error', function(err) {
        throw err;//壓縮過程中如果有錯誤則丟擲
    });
    
    var output = fs.createWriteStream(__dirname + '/public.zip')
     .on('close', function(err) {
         /*壓縮結束時會觸發close事件,然後才能開始上傳,
           否則會上傳一個內容不全且無法使用的zip包*/
         if (err) {
            console.log('關閉archiver異常:',err);
            return;
         }
         console.log('已生成zip包');
         console.log('開始上傳public.zip至遠端機器...');
         uploadFile();
     });

    archive.pipe(output);//典型的node流用法
    archive.directory(srcPath,'/public');//將srcPach路徑對應的內容新增到zip包中/public路徑
    archive.finalize();
}

//將dist目錄上傳至正式環境
function uploadFile() {
    ssh.connect({ //configs存放的是連線遠端機器的資訊
        host: configs.host,
        username: configs.user,
        password: configs.password,
        port:22 //SSH連線預設在22埠
    }).then(function () {
        //上傳網站的釋出包至configs中配置的遠端伺服器的指定地址
        ssh.putFile(__dirname + '/public.zip', configs.path).then(function(status) {
                console.log('上傳檔案成功');
                console.log('開始執行遠端指令碼');
                startRemoteShell();//上傳成功後觸發遠端指令碼
          }).catch(err=>{
             console.log('檔案傳輸異常:',err);
             process.exit(0);
          });
    }).catch(err=>{
        console.log('ssh連線失敗:',err);
        process.exit(0);
    });
}

//執行遠端部署指令碼
function startRemoteShell() {
    //在伺服器上cwd配置的路徑下執行sh deploy.sh指令碼來實現釋出
    ssh.execCommand('sh deploy.sh', { cwd:'/usr/bin/XXXXX' }).then(function(result) {
        console.log('遠端STDOUT輸出: ' + result.stdout)
        console.log('遠端STDERR輸出: ' + result.stderr)
        if (!result.stderr){
            console.log('釋出成功!');
            process.exit(0);
        }
    });
}

4.3 遠端指令碼deploy.sh

當釋出包上傳至遠端伺服器後,剩餘的工作在遠端來完成就可以了,你只需要將後續的工作寫進shell指令碼並放在對應的目錄裡就可以了,本例中deploy.sh放在了服務端專案目錄/mydemo中。示例如下(由於是自用系統,不考慮灰度釋出等,直接暴力刪除靜態目錄public,然後替換為新的包):

#!/bin/bash
cd /usr1/AAA/mydemo
#刪除原靜態資源目錄
rm -rf public
cd /usr1/AAA
#解壓新的包
unzip public.zip
#將解壓出的public目錄移動到服務端程式目錄BBB中
mv public ./mydemo

提示:

如果指令碼檔案是在windows下編寫的,請注意將編輯器中的回車換行改為LF,windows下通常預設是CRLF,這可能會導致指令碼在linux機器上無法正常執行。

至此,一個簡易的自動化部署就做完了。你只需要在本地輸入npm run deploy,後續的工作就會自動執行。

五. 小結

本篇只是一個簡易的自動化部署流程,由於部署環境沒有外網所以暫時無法藉助通用的自動化流水線實現全自動的DevOps流程。PM2實際上還有非常多實用的功能,可以管理多個不同的應用例項,以叢集模式執行例項,或者預設釋出流程,可以直接響應Web Hook並對接指定的程式碼倉,在根目錄下建立ecosystem.config.js配置檔案就可以新增更多配置來指定pm2的表現,感興趣的讀者可以研究一