我發現一旦手頭的項目變多,且隨着項目複雜度的提高,原本編碼就已是個夠頭痛的問題,再加上部署到生產環境就更心累了 😵。javascript
以前在公司實習時,有一個依據用戶輸入網址進行截屏的項目,同時包含了 React 應用和 Node 應用。css
部署 React 應用比較方便,只要經過 scp 將 build 後的 dist 目錄放置在服務器上。前端
而 Node 應用則較爲複雜:java
在項目初期,版本迭代很是快,我天天都要反覆執行以上步驟數次,waste time!node
況且,在標準的開發流程中,咱們還需引入 單元測試、覆蓋率報告、代碼風格檢測 ……,並將應用部署到 不一樣環境的服務器(開發、測試、生產)中,這無疑是一項繁瑣的工做,本着 不想當運維的前端不是一個好全棧 的核心思想,我迫切須要解放個人雙手。git
TIP:結尾有源碼連接github
所謂前人栽樹,後人乘涼,個人訴求早就在開發領域中被定義爲兩個專有名詞:npm
聽起來很高大上,我嘗試經過一張圖來解釋:json
一個完整項目的迭代須要經歷:編碼 ➡️ 打包構建 ➡️ 測試 ➡️ 新代碼和原有代碼正確地集成在一塊兒。安全
這一過程稱爲集成,而 持續集成強調了開發人員提交了新代碼(git push)以後,馬上進行以上步驟,無需人爲干預。
同理,持續部署在持續集成的基礎上,加了一個步驟: 將應用自動部署到指定環境(服務器)。
試想,當你提交代碼後,CI/CD 服務會按照你的預設命令自動化以上步驟,那是多美妙的一件事!
爲了提升軟件開發的效率,咱們有必要使用 CI/CD,而市面上熟知的 CI/CD 服務有:Jenkins、gitlab,不過它們的使用成本很高。
我要推薦的是 Travis CI,它綁定 Github 上面的項目,只要有新的代碼,就會自動抓取。而後,提供一個運行環境,執行測試,完成構建,還能部署到服務器。
爲了確保你能順利進行實踐部分,請作好如下準備工做:
我會從零開始搭建一個用於 API 服務的 NodeJS 應用,並引入單元測試和 ESLint,最終實現 CI/CD。
整個思路以下:
爲了簡便,我將本機稱爲 local,遠程服務器稱爲 remote
萬事開頭難,首先意識到如下兩點:
因爲 Travis CI 和 PM2 Deployment 在運行時不提供交互式界面,它只會按照預設的腳本命令去依次執行,當須要你輸入密碼時就會卡住,因此咱們須要 SSH 無密登陸,達到如下所示關係:
local => GitHub;
remote => GitHub;
local => remote;
複製代碼
首先生成 ssh key 私鑰公鑰對,一路回車,無需 passphrase.
$ ssh-keygen -t rsa -b 4096 -C "<your-github-email>"
複製代碼
在 local 的 ~/.ssh 目錄下,會生成如下文件:
├── id_rsa
├── id_rsa.pub
複製代碼
id_rsa
是私鑰文件,表明 🔑;id_rsa.pub
是公鑰文件,表明 🔒.
只有私鑰才能打開公鑰。
打開 github.com/settings/ke…,點擊 New SSH key,複製 id_rsa.pub
中的內容。
以後選擇你的任一倉庫,點擊 clone or download && Clone with SSH,若是能成功 clone,說明實現了 local 和 GitHub 的 SSH 鏈接。
ssh-copy-id 命令會默認將以前生成的公鑰:id_rsa.pub
複製到 remote 中。
⚠️ 換成你本身的 remote IP.
$ ssh-copy-id root@47.106.87.3
複製代碼
❗️ 若是 win 系統沒法識別該命令,請使用 git bash.
查看 remote 的 ~/.ssh 目錄,id_rsa.pub
中內容與 authorized_keys 一致。
├── authorized_keys
複製代碼
嘗試鏈接 remote.
$ ssh root@47.106.87.3
複製代碼
若是無需輸入密碼,則說明實現了 local 和 remote 的 SSH 鏈接。
思路和 local 鏈接 GitHub 一致,因爲咱們已經在 GitHub 上存放了公鑰,咱們只需將私鑰:id_rsa
上傳到 remote 便可。
上傳完畢後,remote 的 ~/.ssh 目錄存在如下文件:
├── authorized_keys
├── id_rsa
複製代碼
同理,你能夠嘗試在 remote 上使用 Clone with SSH 下載 GitHub 倉庫來驗證是否鏈接成功。
至此,咱們實現了三者的 SSH 互通。
先在 GitHub 上新建一個倉庫,隨後 Clone 到本地。
因爲該應用基於 koa 框架來實現 API 服務,因此進行一些初始化配置:
$ yarn init -y
$ yarn add koa
複製代碼
爲了後續編碼,你應該擁有如下目錄:
├── lib
│ ├── app.js
├── server.js
├── package.json
└── yarn.lock
複製代碼
開始編寫代碼:
// lib/app.js
const Koa = require("koa");
const app = new Koa();
app.use(ctx => {
if (ctx.method == "GET" && ctx.path == "/user") {
ctx.body = "hello, friend";
}
});
module.exports = app;
複製代碼
// server.js
const app = require("./lib/app");
app.listen("8888", () => {
console.log("server is running at http://localhost:8888");
});
複製代碼
啓動 Node 應用:
$ node server.js
複製代碼
我建立了一個最最簡單的 API 服務,當用戶訪問 http://localhost:8888/user 時,返回 "hello, friend".
在這以前,你須要建立 Travis CI 的配置文件,在根目錄下新建 .travis.yml
# 構建環境
language: node_js
# node_js 版本
node_js:
- 12
after_success:
- echo 'I successfully done'
複製代碼
⚠️ Travis CI 默認會執行 install、script 這兩個生命週期,即便沒有顯式在配置文件中定義。
就當前的配置文件而言,啓動構建後,Travis CI 將執行 install ➡️ script ➡️ after_success.
而按照官方文檔 Building a JavaScript and Node.js project:
npm install
npm test
而且,若是 Travis CI 檢測到 yarn.lock
的存在,則分別替換命令爲 yarn
和 yarn test
.
因此,咱們還需提供測試(test)腳本,在 package.json 中添加:
"scripts": {
"test": "echo just test it",
},
複製代碼
最後,確保你在 /account/repositories 中,開啓了對該倉庫的監聽。
一切就緒,只需將修改後的代碼推送到遠程倉庫,來觸發 Travis CI。
$ git push
複製代碼
來到 travis-ci/dashboard,在 Active repositories 面板中選擇 travis-test,能夠看到如下信息:
查看下方日誌信息,關鍵的地方我用文字標註了:
持續集成已經跑通,但感受少了點什麼?對,訪問 remote 的命令還未添加。
因爲 Travis CI 至關於開啓了一個虛擬化容器來執行整個構建過程,因此有必要將私鑰:id_rsa
傳遞給它,來支持 remote 的 SSH 鏈接。那也總不能直接將 id_rsa
放到咱們的倉庫中吧,那豈不是泄露了私鑰,後果很是嚴重!
Travis CI 早就想到了這一點,它提供了針對私鑰的加密方案。
加密私鑰文件須要使用 travis 這個命令行工具,它是一個 ruby 包,使用 gem 安裝:
$ gem install travis
$ travis login
複製代碼
若是你安裝 travis 失敗,能夠查閱 github.com/travis-ci/t….
輸入帳號密碼登陸成功後,使用 travis encrypt-file 加密:
$ travis encrypt-file ~/.ssh/id_rsa --add
# Detected repository as B2D1/travis-test, is this correct? |yes| yes
# Overwrite the config file /root/travis-test/.travis.yml with the content below? (y/N) y
# Make sure to add id_rsa.enc to the git repository.
# Make sure not to add /root/.ssh/id_rsa to the git repository.
# Commit all changes to your .travis.yml.
複製代碼
上面命令執行完後,會生成一段解密命令並添加到 .travis.yml 中:
before_install:
- openssl aes-256-cbc -K $encrypted_9b2d7e19d83c_key -iv $encrypted_9b2d7e19d83c_iv
-in id_rsa.enc -out ~/.ssh/id_rsa -d
複製代碼
而且提示 ❗️,必定要把加密後的 id_rsa.enc
複製到倉庫中,必定不要把未加密的 id_rsa
複製到倉庫中。
有可能你生成的是 -out ~\/.ssh/id_rsa -d
,切記改爲 -out ~/.ssh/id_rsa -d
before_install
階段發生在 install
階段以前,這段代碼的意思是:用 encrypted_9b2d7e19d83c_iv
和 encrypted_9b2d7e19d83c_key
這兩個環境變量,對倉庫中的 id_rsa.enc
進行解密,並在虛擬容器中的 ~/.ssh 目錄下生成私鑰:id_rsa
你能夠在 travis-ci.org ➡️ 你的倉庫 ➡️ More options ➡️ settings 中找到這對環境變量:
基本完成對 remote 的鏈接工做,但還有一些坑要填:
更改後的 .travis.yml 配置以下:
# 構建環境
language: node_js
# node_js 版本
node_js:
- 12
# 將遠程服務器加入信任列表
addons:
ssh_known_hosts: 47.106.87.3
# 解密 id_rsa.enc,並修改 id_rsa 權限
before_install:
- openssl aes-256-cbc -K $encrypted_9b2d7e19d83c_key -iv $encrypted_9b2d7e19d83c_iv
-in id_rsa.enc -out ~/.ssh/id_rsa -d
- chmod 600 ~/.ssh/id_rsa
# 鏈接遠程服務器,並打印系統版本
after_success:
- ssh root@47.106.87.3 'cat /etc/issue'
複製代碼
提交代碼(git push),查看構建結果:
成功打印了我遠程服務器的版本信息:
在上一節,爲了快速經過測試命令(yarn test),只是簡單使用了 echo 命令。
如今要正式爲 NodeJS 應用添加單元測試,建議選擇 Jest + SuperTest 來實現。
Jest 是 Facebook 的一套開源的 JavaScript 測試框架,它自動集成了斷言、JSDom、覆蓋率報告等開發者所須要的全部測試工具,是一款幾乎零配置的測試框架。
SuperTest: HTTP assertions made easy via superagent.
安裝 npm 包:
$ yarn add jest supertest --dev
複製代碼
更改 package.json:
"scripts": {
"test": "jest"
},
複製代碼
在根目錄下新建 __test__/app.test.js
,並編寫測試代碼:
const app = require("../lib/app");
const supertest = require("supertest");
const server = app.listen();
const request = supertest(server);
test("GET /user", async done => {
const res = await request.get("/user");
expect(res.status).toBe(200);
expect(res.text).toBe("hello, friend");
done();
});
afterAll(done => {
server.close();
done();
});
複製代碼
執行測試腳本:
$ yarn test
複製代碼
測試經過:
還能夠經過 --coverage
參數來提供覆蓋率報告:
這一節,繼續完善 NodeJS 應用,爲它添加 ESlint.
ESLint 是一個插件化而且可配置的 JavaScript 語法規則和代碼風格的檢查工具。ESLint 可以幫你輕鬆寫出高質量的 JavaScript 代碼
安裝 npm 包:
$ yarn add eslint eslint-config-google --dev
複製代碼
更改 package.json:
"scripts": {
"lint": "eslint .",
"test": "jest",
"pretest": "yarn run lint"
},
複製代碼
❗️pretest 腳本會在 yarn test 以前自動執行。
在根目錄下建立配置文件 .eslintrc.json
{
"extends": ["eslint:recommended", "google"],
"env": {
"node": true
},
"parserOptions": {
"ecmaVersion": 6
},
"rules": {
"eqeqeq": 2
},
"ignorePatterns": ["ecosystem.config.js", "__tests__"]
}
複製代碼
這裏採用了預設的 lint 規則:recommended & google.
並新增一條規則:非嚴格相等符(==)的存在,會致使程序退出(0 表明關閉,1 表明警告,2 表明錯誤)。
其餘的配置項爲:設置代碼環境、ECMA 版本、指定哪些文件不參與檢查。
執行 lint 命令:
$ yarn run lint
複製代碼
發生瞭如下錯誤:
能夠嘗試運行 yarn run lint --fix
命令, ESlint 會自動修復錯誤。對於不能自動修復的,需手動修改。
通過上述步驟,已經基於 Travis CI 實現了 CI(持續集成)。
只差最後一步:將 NodeJS 應用部署到遠程服務器上。
參照官方文檔 PM2 Deployment,咱們只需建立配置文件便可,剩下的交給 PM2 來作。
在根目錄下建立 ecosystem.config.js
module.exports = {
apps: [
{
// PM2 應用名稱
name: "travis-test-deploy",
// node 啓動文件
script: "server.js",
},
],
deploy: {
// "prod" 是環境名稱
prod: {
// 私鑰目錄
key: "~/.ssh/id_rsa",
// 登陸用戶
user: "root",
// 遠程服務器
host: ["47.106.87.3"],
// 自動將 github 加入遠程服務器的信任列表
ssh_options: "StrictHostKeyChecking=no",
// git 分支
ref: "origin/master",
// git 倉庫地址(ssh)
repo: "git@github.com:B2D1/travis-test.git",
// 項目在遠程服務器的存放路徑
path: "/root/travis-test-deploy",
// PM2拉取最新分支後,安裝 npm 包,並啓動(重啓)NodeJS 應用
"post-deploy":
"source ~/.nvm/nvm.sh && yarn install && pm2 startOrRestart ecosystem.config.js",
},
},
};
複製代碼
同時修改 .travis.yml
# 構建環境
language: node_js
# node_js 版本
node_js:
- 12
# 將遠程服務器加入信任列表
addons:
ssh_known_hosts: 47.106.87.3
# 解密 id_rsa.enc,並修改 id_rsa 權限
before_install:
- openssl aes-256-cbc -K $encrypted_9b2d7e19d83c_key -iv $encrypted_9b2d7e19d83c_iv
-in id_rsa.enc -out ~/.ssh/id_rsa -d
- chmod 600 ~/.ssh/id_rsa
# PM2 deploy
after_success:
- npm i -g pm2 && pm2 deploy ecosystem.config.js prod update
複製代碼
⚠️ 在首次部署時,咱們須要先在遠程服務器初始化項目。
$ pm2 deploy ecosystem.config.js prod setup
複製代碼
❗️ 若是 win 系統出錯,請使用 git bash.
隨後提交代碼(git push),等待 Travis CI 構建 和 PM2 部署完畢。
訪問 Travis CI 顯示構建成功,登陸遠程服務器,輸入 pm2 list,如圖所示:
訪問 http://<your remote ip>:8888/user
,顯示 "hello,friend".
這個 NodeJS 應用雖然簡單,但涉及的知識點很是之多:建立 API 服務、單元測試、ESLint、CI/CD、SSH、Linux 運維,須要掌握必定的實踐能力。
因爲篇幅有限,還有不少坑、細節來不及去講,若有錯誤請聯繫我 📧.