ssh2.js+Shell一套組合拳下來,一年要花2080分鐘做的工作竟然節(jié)省到52分鐘~

前言

這是【Node.js實(shí)戰(zhàn)】專欄內(nèi)的第6篇文章,專欄是分享使用Node.js技術(shù)編寫實(shí)用腳本技巧。

專欄現(xiàn)有文章:

  1. 仿jsDoc寫一個最簡單的文檔生成
  2. 50+行代碼搞定一行命令更新Npm包
  3. 玩轉(zhuǎn)nodeJs文件模塊
  4. Node.js操作Dom ,輕松hold住簡單爬蟲
  5. 【Node.js】寫一個數(shù)據(jù)自動整理成表格的腳本

歡迎讀者關(guān)注【Node.js實(shí)戰(zhàn)】專欄。

進(jìn)入了新的一年,團(tuán)隊(duì)被分配了新的工作內(nèi)容——每周巡檢。

巡檢工作簡單,但需要人工重復(fù)性地登陸遠(yuǎn)程服務(wù)器、輸入重復(fù)的命令,然后將命令的結(jié)果記錄下來。每做一次估計(jì)花40分鐘,但要每周做,一年52周,一年下來就要花40*52=2080分鐘,這僅僅是團(tuán)隊(duì)一個人一年要花的時間。

不能這么玩呀,純純工具人,所以我一直在思考如何用程序幫我自動巡檢掉。這篇文章的出現(xiàn),說明我的想法方向是正確的,收益可觀一年要花2080分鐘,被我減到52 分鐘。

如果再擴(kuò)展程序幫助到團(tuán)隊(duì),這個公式將從40*52*團(tuán)隊(duì)人數(shù)****變成****1*52*團(tuán)隊(duì)人數(shù)****,時間等于金錢。

未自動巡檢:

手動連接登陸遠(yuǎn)程服務(wù)器,再輸入相應(yīng)的命令獲取結(jié)果,然后人工依據(jù)結(jié)果判斷是否異常,相當(dāng)麻煩,而且我要執(zhí)行的命令不止一條。

自動巡檢:

運(yùn)行macOS筆記本創(chuàng)建好的快捷指令,它會自動巡檢服務(wù)器,并且巡檢完成后直接打開巡檢結(jié)果表格。當(dāng)然沒有macOS依然可以,但就是沒有快捷指令這步,需要自己執(zhí)行程序。

完整源碼:blog/ssh

實(shí)現(xiàn)

實(shí)現(xiàn)難點(diǎn)

自動化巡檢思路簡單,思路如下:

本地程序連接登陸遠(yuǎn)程服務(wù)器→本地shell命令遠(yuǎn)程執(zhí)行→本地程序獲取命令結(jié)果→結(jié)果數(shù)據(jù)整理成表格

實(shí)現(xiàn)過程中主要有以下兩個難點(diǎn):

  • Node.js本地運(yùn)行程序如何連接登陸遠(yuǎn)程服務(wù)器
  • 登陸遠(yuǎn)程服務(wù)器帳號權(quán)限不足,在使用sudo命令時,如何自動輸入密碼

實(shí)現(xiàn)細(xì)節(jié)

解決Node.js本地運(yùn)行程序如何連接登陸遠(yuǎn)程服務(wù)器:

社區(qū)已有的方案ssh2,它是用純JavaScript為Node.js編寫的SSH2客戶端和服務(wù)器模塊??梢允褂盟B接到遠(yuǎn)程服務(wù)器,并且ssh2提供了方法可以執(zhí)行shell命令。

ssh2官方案例:

//...
const { Client } = require('ssh2');
const conn = new Client();
conn.on('ready', () => {
  console.log('Client :: ready');
  //執(zhí)行uptime
  conn.exec('uptime', (err, stream) => {
    if (err) throw err;
    stream.on('close', (code, signal) => {
      console.log('Stream :: close :: code: ' + code + ', signal: ' + signal);
      conn.end();
    }).on('data', (data) => {
      //監(jiān)聽數(shù)據(jù)
      console.log('STDOUT: ' + data);
    }).stderr.on('data', (data) => {
      console.log('STDERR: ' + data);
    });
  });
})
//...

官方案例僅執(zhí)行一條shell命令,當(dāng)按照順序依次執(zhí)行一條以上的命令,官方的這個寫法會非常麻煩。例如:首先執(zhí)行docker ps -a -q獲取所有docker容器id,然后再docker logs --tail 200 id

 //...
 // 獲取docker所有容器ID
 conn.exec('docker ps -a -q', (err, stream) => {
    if (err) throw err;
    stream.on('close', (code, signal) => {
      /**
        docker ps -a -q命令執(zhí)行完成
        再執(zhí)行docker logs -f --tail 200 id
      */
      conn.exec(`docker logs  --tail 200 ${id}`,(err,stream)=>{
         if (err) throw err;
          stream.on('close', () => {
          //如果命令再復(fù)雜點(diǎn),還需要繼續(xù)這樣寫下去
          
          }).on('data', (data) => {
            console.log( data);
          }).stderr.on('data', (data) => {
            console.log(data);
          });
      })
      
    }).on('data', (data) => {
      console.log('STDOUT: ' + data);
    }).stderr.on('data', (data) => {
      console.log('STDERR: ' + data);
    });
  });
 //...

要想寫法整潔點(diǎn),我們需要再給 exec方法用Promise包一層。

execFn.js:

module.exports = (c = conn) => {
    return (command) => {
        return new Promise((resolve, reject) => {
            c.exec(command, (err, stream) => {
                if (err) {
                    reject(err)
                    return
                }
                let result = ''
                stream.on('close', () => {
                    resolve(String(result))
                }).on('data', (data) => {
                    //data數(shù)據(jù)是Buffer類型,需要轉(zhuǎn)化成字符串
                    result += data
                })
            })
        })
    }
}

包一層后,再執(zhí)行命令:

const execFn = require('./execFn.js')

module.exports = (config, conn) => {
    conn.on('ready',async ()=>{
      const exec = execFn(conn)
      const result = await exec('docker ps -a -q')
      //...
      exec(`docker logs --tail 200 ${id}`)
    })
    //...
}

這樣代碼會顯得更整潔點(diǎn),使用也更方便。

解決登陸遠(yuǎn)程服務(wù)器帳號權(quán)限不足,在使用sudo命令時,如何自動輸入密碼,可行方案有兩種:

  • 簡單粗暴,直接使用root帳號密碼進(jìn)行登陸,這樣即可不用考慮如何跳過密碼輸入的交互
  • 使用shell管道命令echo '密碼' | sudo -S 命令

root帳號密碼團(tuán)隊(duì)不能給到我,所以我采用了后者來解決。

shell實(shí)現(xiàn)自動輸入密碼方法不只有使用管道命令echo '密碼' | sudo -S 命令,還有其他的方法,但它在自動巡檢的場景中是最合適的,它不需要額外要求服務(wù)器下載其他工具包,像expect指令它就需要安裝expect包。巡檢不只巡檢一臺服務(wù)器,如果每臺都安裝expect包,這工作量也煩人。

未自動輸入密碼:

自動輸入密碼:

至此,自動化巡檢難點(diǎn)之處已解決,下面的工作就是以執(zhí)行shell命令返回的結(jié)果判斷服務(wù)器狀態(tài)是否正常,如:團(tuán)隊(duì)巡檢文檔規(guī)定當(dāng)執(zhí)行docker info |grep -A 5 "WARNING"時,如果有返回結(jié)果則為異常。

//...
const before = `echo "${config.password}" | sudo -S `
exec(before + 'docker info |grep -A 5 "WARNING"').then((content) => {
    if (content) {
        rol[2] = '異常'
    }
})
//...

該部分邏輯以團(tuán)隊(duì)巡檢文檔內(nèi)容為準(zhǔn),不過多贅述,該部分代碼在sshServer.js文件。

為了做到巡檢多臺服務(wù)器的目的,巡檢相關(guān)的邏輯代碼使用函數(shù)進(jìn)行包裹并從sshServer.js文件中導(dǎo)出。

sshServer.js:

const execFn = require('./execFn.js')
//...
module.exports = (config, conn) => {
    return new Promise((resolve, reject) => {
        const exec = execFn(conn)
        conn.on('ready', async (err) => {
            if (err) reject(err)
            console.log('連接成功');
            //省略
            
        }).connect({
            ...config,
            readyTimeout: 5000
        });
    })

}

所有的服務(wù)器帳號密碼均放置在config.json文件中:

[
    {
        "host": "xx",
        "port": "xx",
        "username": "xx",
        "password": "xx"
    }
    //...
]

config.json文件涉及到服務(wù)器信息需要保密,config.json文件不會被提交至倉庫。

目錄結(jié)構(gòu)如下:


最后,將巡檢的結(jié)果數(shù)據(jù)整理成表格,如何將數(shù)據(jù)導(dǎo)出表格已有對應(yīng)的文章實(shí)現(xiàn)說明【Node.js】寫一個數(shù)據(jù)自動整理成表格的腳本

思路是一樣的。

index.js

const { Client } = require('ssh2');
const configs = require('./config.json')
const sshServer = require('./sshServer.js');
const fs = require('fs');
const path = require('path');
const nodeXlsx = require('node-xlsx')

const promises = []

//表格數(shù)據(jù) 二維數(shù)組
const tables = [
    ['服務(wù)器ip', 'docker是否正常運(yùn)行', 'docker遠(yuǎn)程訪問', 'Docker日志是否有報(bào)錯信息']
]

configs.forEach((config) => {
    const conn = new Client();
    promises.push(sshServer(config, conn))
})

Promise.all(promises).then((data) => {
    data.forEach((d) => {
        if (Array.isArray(d)) {
            tables.push(d)
        }
    })
    //生成xlsx表格
    const buffer = nodeXlsx.build([{ name: '巡檢', data: tables }])
    const file = path.join(__dirname, '/server.xlsx')
    fs.writeFileSync(file, buffer, 'utf-8')
})

巡檢結(jié)果統(tǒng)一暫存于tables數(shù)組中,以便導(dǎo)出。

實(shí)現(xiàn)快捷指令巡檢

使用命令行巡檢還是太累了。 最好是鼠標(biāo)點(diǎn)下自動觸發(fā)自動巡檢。

我們可以借助Mac快捷指令自定義再簡化下。

快捷指令可以運(yùn)行Shell。這樣只需要編寫一個名字叫做【巡檢服務(wù)器】的快捷指令。

運(yùn)行Shell后,以WPS打開server.xlsx文件。

快捷指令添加至訪達(dá)。

這樣就可以輕松實(shí)現(xiàn)自動巡檢服務(wù)器功能了。

總結(jié)

文章靈感來源于工作,通過使用Node.js+Shell+ssh2做到自動連接登陸遠(yuǎn)程服務(wù)器,運(yùn)行相關(guān)Shell命令,檢查服務(wù)器程序運(yùn)行是否正常等情況。

對于程序員來說,懶,才是第一生產(chǎn)力?。?!

如果我的文章對你有幫助,你的??就是對我的最大支持_。

本文由mdnice多平臺發(fā)布

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容