很早之前我就在用 Travis CI 做持續整合了,雖然只是停留在 zhuang bi 的階段,但或多或少也保證了程式碼的提交質量。最近在寫一個 《JavaScript API 全解析》系列的 Book,需要經常把文章部署到伺服器上,手動部署實在是煩,索性花了一天時間研究了一下自動化部署。這篇文章是對 Travis CI 持續整合和自動化部署的總結,以饗社群。
前戲
Travis CI 目前有兩個網站,一個是 travis-ci.com,另一個是 travis-ci.org. 前者用於企業級和私有倉庫,後者用於開源的公有倉庫。實際上 free plan 也可以使用 travis-ci.com,但優先順序很低,跑個自動化動輒兩個小時,因此我們使用 travis-ci.org.
首先開啟 Travis CI 官網,並用 GitHub 賬號登入,授權後 Travis CI 會同步你的倉庫資訊。接下來把需要做自動化的工程授權給 Travis CI.

最好有一臺 Linux 的伺服器,我的是 Cent OS 7.6.x 64bit.
我們點開一個工程,再切到設定,可以看到在 push 程式碼和 PR 時都會觸發持續整合,當然可以根據需求手動配置。

持續整合
為了讓持續整合像那麼回事兒,我們先在 master 上切一個 develop 分支,再在 develop 上切一個 featur/ci 分支。
接著我們再用 Jest 寫幾個測試用例,注意如果專案中沒有測試指令碼而 .travis.yml
檔案裡面包含 yarn test
,自動化 一定 報錯。關於 Jest 這裡不詳細說,只貼出幾個示例程式碼。
import * as utils from '../utils/util';
test('should get right date', () => {
expect(utils.formatJSONDate('2019-03-10T04:15:40.629Z')).toBe(
'2019-03-10 12:15:40',
);
});
test('should get right string', () => {
expect(utils.upperFirstLetter('AFTERNOON')).toBe('Afternoon');
expect(utils.upperFirstLetter('YANCEY_LEO')).toBe('Yancey Leo');
});
複製程式碼
然後我們在工程的根目錄下新建一個檔案 .travis.yml
,並複製下面的程式碼。
language: node_js
node_js:
- 8
branchs:
only:
- master
cache:
directories:
- node_modules
install:
- yarn install
scripts:
- yarn test
- yarn build
複製程式碼
簡單解釋一下,工程使用 Node.js 8.x,並且只在 master
分支有變動時觸發 自動化部署(正常的提交、PR 都會正常走持續整合),接著將 node_modules 快取起來(你懂的),最後安裝依賴、跑測試指令碼、在沙箱部署。
因此,理論上只要跑通這套流程,我們就可以放心的部署到真實環境了。
提交一下程式碼,並 pull request 到 develop 分支。在此過程中我們觸發了 push 和 PR,所以會跑兩個 CI。待到兩個都成功跑完後,我們就可以放心的合到 develop 分支了。(這裡我還做了程式碼質量檢測,有興趣可以戳 Codacy)

最後我們回到 Travis CI 的官網,可以看到一套完整的構建流程:安裝依賴 -> 測試 -> 沙箱部署

持續部署
建立 rsa 對,並給予許可權
首先登入你的伺服器,一般來講我們不會直接在 root 上操作,所以這裡新增一個 caddy 的使用者 。具體怎樣在 Linux 新建使用者請自行谷歌。
接下來 cd 到 ~/.ssh,看看有沒有一對 id_rsa 和 id_rsa.pub,如果沒有就用 ssh-keygen
生成。
給予 .ssh 資料夾 700 許可權,給予 .ssh 裡的檔案 600 許可權。(看下面這張圖,你的資料夾裡可能暫時沒有 authorized_keys、 known_host、config 這三個檔案,後面會說到。)
$ sudo chmod 700 ~/.ssh/
$ sudo chmod 600 ~/.ssh/*
複製程式碼

將生成的公鑰新增到受信列表
進入到 .ssh
資料夾裡,執行下面的命令,可以看到公鑰被新增到受信列表。
$ cat id_rsa.pub >> authorized_keys
$ cat authorized_keys
複製程式碼
測試登入
在 .ssh
目錄下建立一個檔案 config
,輸入如下程式碼並儲存。
Host test
HostName 當前伺服器的IP
User 當前使用者名稱
IdentitiesOnly yes
IdentityFile ~/.ssh/id_rsa
複製程式碼
因為 authorized_keys
和 config
檔案都是新增的,它們還沒被賦予 600 許可權,所以重新執行一遍 sudo chmod 600 ~/.ssh/*
.
然後我們輸入 ssh test
,不出意外會重新登入 ssh。如果你的公鑰從來沒有被使用過,會提示 Are you sure you want to continue connecting (yes/no)?
,輸入 yes 後也會正常重新登入,並且在.ssh
資料夾下還會生成一個 known_hosts
檔案.
安裝 Ruby
因為 Travis 客戶端是用 Ruby 寫的,所以我們得先安裝 Ruby.
首先安裝需要的依賴包:
$ yum install gcc-c++ patch readline readline-devel zlib zlib-devel \
libyaml-devel libffi-devel openssl-devel make \
bzip2 autoconf automake libtool bison iconv-devel sqlite-devel
複製程式碼
接下來安裝 RVM,並載入 RVM 環境。RVM 是 Ruby 的版本管理工具,類似於 Node 的 NVM.
$ gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
$ \curl -sSL https://get.rvm.io | bash -s stable
# 載入 rvm 環境
$ source ~/.rvm/scripts/rvm
複製程式碼
安裝完之後輸入 rvm -v
做下檢查,如果有 rvm 1.29.1 (latest) by Michal Papis, Piotr Kuczynski, Wayne E. Seguin [rvm.io/] 的字樣證明安裝成功。
最後安裝 Ruby,這裡選擇 v2.4.1 版本,安裝需要一段時間,完成後記得將此版本設為預設。
$ rvm install 2.4.1
$ rvm 2.4.1 --default
複製程式碼
執行一下 ruby -v
和 gem -v
,如果和下圖差不多證明安裝成功。

安裝 Travis 客戶端
執行下面的命令以安裝 Travis 客戶端。
$ gem install travis
複製程式碼
安裝完成後執行 travis
,它會讓你安裝相應的 Shell, 輸入 yes 即可。

配置免密登入
將你的工程克隆下來,並進入到工程目錄,然後登入你的 GitHub 賬號。
$ travis login --auto
複製程式碼

執行下面這句,它會利用伺服器的私鑰加密成一個叫做 id_rsa.enc
的檔案,這個檔案被用於 travis 登入你伺服器的憑證,從而達到免密的目的。
$ travis encrypt-file ~/.ssh/id_rsa --add
複製程式碼

我們執行一下 ll
,可以看到根目錄下多出一個 id_rsa.enc
檔案來,並且 cat .travis.yml
,發現多出了 before_install
.
為了更好地組織程式碼,我們在專案的根目錄新建一個資料夾 .travis
,然後將 id_rsa.enc
放到裡面。

配置 after_success 鉤子
在寫這一小節之前,我們先看一看 Travis 的生命週期:
- before_install 安裝依賴前
- install 安裝依賴時
- before_script 執行指令碼前
- script 執行指令碼時
- after_success 或 after_failure 執行指令碼成功(失敗)後
- before_deploy 部署前
- deploy 部署時
- after_deploy 部署後
- after_script 執行指令碼後
因此 after_success
可用在成功通過測試指令碼之後執行部署相關的指令碼。當然細一點可以使用 deploy 相關的鉤子,這裡不做太複雜。
開啟 .travis.yml
檔案,直接上全部程式碼。
language: node_js
sudo: true
node_js:
- 8
branchs:
only:
- master
# 這裡填寫伺服器的ip,若埠號不是22,後面要註明埠號
addons:
ssh_known_hosts:
- 你的伺服器IP
cache:
directories:
- node_modules
before_install:
# 因為我們把 id_rsa.enc 移到了.travis 資料夾下,所以 -in 後面要改成 .travis/id_rsa.enc
# 其次,-out 後面自動生成的是 ~\/.ssh/id_rsa,要把 \ 去掉,否則會編譯失敗
- openssl aes-256-cbc -K $encrypted_XXXXXXXXXXXX_key -iv $encrypted_XXXXXXXXXXXX_iv -in .travis/id_rsa.enc -out ~/.ssh/id_rsa -d
# 開啟 ssh-agent,即允許使用 ssh 命令
- eval "$(ssh-agent -s)"
# 給予 id_rsa 檔案許可權,避免警告
- chmod 600 ~/.ssh/id_rsa
# 將私鑰新增到 ssh
- ssh-add ~/.ssh/id_rsa
install:
- yarn install
scripts:
- yarn test
- yarn build
after_success:
# 登入伺服器,執行部署指令碼,其實最好把後面一串寫成 shell 檔案
- ssh caddy@你的伺服器IP -o StrictHostKeyChecking=no 'cd /var/www/jsapi/JavaScript-APIs-Set && git pull && yarn install && yarn build'
複製程式碼
走一遍正式的流程
至此,搭建 Travis CI 持續整合和自動化部署就算完成了,可能不太嚴謹,但基本是這麼一個思路。下面我們梳理一遍流程。
-
我們先在 feature/ci 分支修改一段程式碼,提交分支,並 PR 到 develop,此時會執行兩個 CI。當兩個 CI 都跑通了,我們可以放心的 merge request 到 develop 分支。
-
接下來讓 develop PR 到 master,此時會執行兩個 CI(一個是 develop 分支,一個是測試合併到 master 的 CI)。當兩個 CI 都跑通了,我們可以放心的 merge request 到 master 分支。
-
merge request 之後會跑最後一個流程, 也就是自動部署,部署成功後線上程式碼就會更新了。
加入徽章
別忘了把 build passing 徽章新增到你的 README.md 檔案中。

最後
不知道你有沒有發現,Travis CI 支援 LGBT...

以上、よろしく。
參考
How to Encrypt/Decrypt SSH Keys for Deployment