打造小而美的Web應(yīng)用部署方案

問(wèn)題

作為開(kāi)發(fā)者,擁有一套自己運(yùn)營(yíng)的系統(tǒng)是很一件意義的事情。你可以通過(guò)運(yùn)營(yíng),讓?xiě)?yīng)用持久穩(wěn)定的給用戶帶來(lái)價(jià)值。

然而,運(yùn)營(yíng)本身包含著很多瑣碎的事。對(duì)我而言,其中一個(gè)瑣碎的事情是,每次要發(fā)布應(yīng)用,都需要遠(yuǎn)程登錄到服務(wù)器,手工執(zhí)行一些命令。

有什么方法可以簡(jiǎn)化這種操作嗎?答案當(dāng)然是Yes。業(yè)界早就有各種持續(xù)集成(CI)/自動(dòng)部署方案,比如大名鼎鼎的 jekins 或者 teamcity 。可是對(duì)我而言,他們都太復(fù)雜。我只是運(yùn)行著一個(gè)日訪問(wèn)量不到一萬(wàn)的小小網(wǎng)站,不需要那么重的東西。

在一番探索之后,我終于搞定了一套小而美的自動(dòng)化部署方案。下面和大家分享一下實(shí)現(xiàn)過(guò)程,希望可以拋磚引玉。

背景

我的這個(gè)網(wǎng)站后臺(tái)采用Express框架實(shí)現(xiàn),運(yùn)行在nodejs上,前端采用的React+Redux技術(shù)棧。由于主要的數(shù)據(jù)存儲(chǔ)在 iCloud 中,省去了自己搭數(shù)據(jù)庫(kù)服務(wù)。

服務(wù)器端主要安裝了以下軟件:

  • Node, 用來(lái)跑Express應(yīng)用;
  • PM2,高性能的node服務(wù)器;
  • nginx,高性能的web服務(wù)器,用來(lái)作為網(wǎng)關(guān),將80/443端口的請(qǐng)求轉(zhuǎn)發(fā)到 node,同時(shí),它可以處理靜態(tài)資源的請(qǐng)求,比如HTML/JS/CSS之類的,減輕node/pm2的負(fù)擔(dān);
  • certbot,神器,自動(dòng)申請(qǐng)并維護(hù) Let ’ s Encrypt 的HTTPs證書(shū)。(光這個(gè)每年就可以節(jié)約100美刀以上!) 具體的安裝可以參考 這篇文章

自動(dòng)部署系統(tǒng)的初步實(shí)現(xiàn)

簡(jiǎn)單而言,自動(dòng)部署方案有以下幾個(gè)部分:

  • 使用Google Drive 作為中轉(zhuǎn)。Google Drive 是高效的云存儲(chǔ)服務(wù),周邊產(chǎn)品眾多。免費(fèi)的15G空間足夠應(yīng)付一般的需求。同時(shí),可以對(duì)服務(wù)器上的重要數(shù)據(jù)進(jìn)行備份。
  • 在個(gè)人開(kāi)發(fā)機(jī)器上,使用Google 出品的Back and Sync 應(yīng)用云存儲(chǔ)映射為本地一個(gè)目錄。我使用的是mac版的Back and Sync。要發(fā)布新版本時(shí),直接將它拷貝到這個(gè)目錄即可自動(dòng)上傳到云端。
  • 在Linux 服務(wù)器上,使用 rclone 拉取云端的更新到服務(wù)器發(fā)布目錄。

rclone 的安裝比較簡(jiǎn)單,不需要圖形界面,在ssh終端即可安裝。其安裝和配置步驟可以參考 官方的安裝文檔Google Drive 配置說(shuō)明

拉取通過(guò) rclone 的 sync 命令來(lái)實(shí)現(xiàn)。rclone 的同步是增量同步,每次只會(huì)抓取變更的文件。開(kāi)始的時(shí)候,我直接在linux的crontab中加入一個(gè)定時(shí)任務(wù)來(lái)做這件事:

0/5 * * * *  /usr/bin/rclone sync gdrive:web_deploy /opt/web

上面這個(gè)配置意思是,從每小時(shí)的0分,每隔5分鐘調(diào)用 rclone 的 sync 將Google Drive 中的 web_deploy 同步到 /opt/web 目錄。

所以現(xiàn)在的發(fā)布流程是,將要發(fā)布的版本扔到本地的Google Drive 目錄,然后6分鐘左右后在瀏覽器中訪問(wèn)網(wǎng)站確認(rèn)新版本。

使用機(jī)器人來(lái)提高部署效率

上面的方案已經(jīng)不錯(cuò),不過(guò)還有兩個(gè)問(wèn)題:

  • 因?yàn)橐蕾囉诙〞r(shí)任務(wù),會(huì)有一個(gè)延時(shí);
  • 不夠強(qiáng)大,不夠靈活,比如不能自動(dòng)幫忙重啟node/pm2服務(wù),部署出現(xiàn)錯(cuò)誤的時(shí)候不能及時(shí)反應(yīng)等;
    所以,我決定用機(jī)器人 (bot) 來(lái)改善部署。

現(xiàn)在有很多即時(shí)通信工具都提供了bot服務(wù)。我選用了Telegram的bot。

首先, 需要?jiǎng)?chuàng)建一個(gè)bot賬號(hào)。在Telegram上創(chuàng)建bot比較簡(jiǎn)單,你只需要在telegram上找到botfather ,然后給他發(fā)一個(gè)/newbot命令,它就開(kāi)始了創(chuàng)建bot的過(guò)程,會(huì)讓你給你的bot起一個(gè)名字和賬號(hào),賬號(hào)必須以bot結(jié)尾。創(chuàng)建完后botfather會(huì)給你一個(gè)token。

接下來(lái),我們需要?jiǎng)?chuàng)建一個(gè)bot程序。你可以使用任何你喜歡的開(kāi)發(fā)語(yǔ)言來(lái)完成這個(gè)程序。我選擇nodejs。
主要程序只有3個(gè)文件。
package.json 定義了項(xiàng)目的基本信息,包括依賴的包等:

{
  "name": "appguardbot",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "author": "marknote",
  "license": "MIT",
  "dependencies": {
    "telebot": "^1.2.3"
  }
}

config.js 中是配置信息:

module.exports = {
    config:function(){
        return (
            {
                adminUsers:[3000000], //這里是admin用戶的telegram ID,多個(gè)用戶用西文逗號(hào)隔開(kāi)
                botToken: '430000000:XXXXXXXXXXXXXXXXXXX',//這里是chatbot的token
                deployCmd:'/usr/bin/rclone sync',   //要執(zhí)行的Linux命令
                deployCmdOptions:['sync', 'gdrive:web_deploy', ' /opt/web'], //命令參數(shù),是一個(gè)數(shù)組

            }
        );
    }
};

index.js 主要邏輯實(shí)現(xiàn):

const config = require('./config').config();
const TeleBot = require('telebot');
const bot = new TeleBot(config.botToken);
const { spawn } = require('child_process');
const adminUsers = config.adminUsers;


bot.on(['/deploy', '/d'], (msg) => { //響應(yīng)deploy/d命令
    const id = msg.from.id;

    if (adminUsers.indexOf(id) < 0) {//如果當(dāng)前用戶不是admin則拒絕發(fā)布
        msg.reply.text('你沒(méi)有權(quán)限!');
        return;
    }
    msg.reply.text(`開(kāi)始執(zhí)行部署任務(wù)...`);
    const shell = spawn(config.deployCmd, config.deployCmdOptions);

    shell.stdout.on('data', (data) => {//顯示執(zhí)行的輸出
        msg.reply.text(`stdout: ${data}`);
    });

    shell.stderr.on('data', (data) => {//顯示錯(cuò)誤
        msg.reply.text(`stderr: ${data}`);
    });

    shell.on('close', (code) => {
        msg.reply.text(`報(bào)告主人,任務(wù)執(zhí)行完畢,任務(wù)代碼 ${code}`);
    });
}
);
bot.on('text', (msg) => {
    const id = msg.from.id;
    console.log('當(dāng)前用戶ID:' + id);
    msg.reply.text(msg.text)
}
);

bot.start();

不到百行代碼,一個(gè)功能完備的bot程序就完成了。將它部署到服務(wù)器上,之后就可以用它來(lái)部署了。

效果

現(xiàn)在,每次要發(fā)布新版本,我只需要將發(fā)布包放到Google Drive 對(duì)應(yīng)目錄,然后對(duì)bot喊一聲/d,機(jī)器人就會(huì)開(kāi)始執(zhí)行任務(wù)。

為了簡(jiǎn)單起見(jiàn),這里的代碼只顯示了從Google Drive同步部分。其實(shí)機(jī)器人可以做的遠(yuǎn)遠(yuǎn)不止這些,使用類似的代碼,執(zhí)行任何命令都不是難事。

自從用上了自動(dòng)部署系統(tǒng),套一下廣告詞的說(shuō)法:腰不酸了,腿也不疼了!枯燥的發(fā)布過(guò)程,變成了享受。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容