一、前言
本文介紹的內(nèi)容包含:
- 理解CI/CD及其必要性
- gitlab-runner安裝與注冊
- gitlab-ci配置說明
- ssh免密登錄
- rsync部署文件
- 多環(huán)境發(fā)布與回滾
- 高頻出現(xiàn)的問題并解決
閱讀本文需要你:
- 有一定的ssh知識基礎,一份SSH操作指南
- gitlab ci/cd概念基礎
- 花10min左右的時間
二、CI/CD科普
2.1 CI與CD
<font color="#ff502c">持續(xù)集成(Continuous Integration)</font>指開發(fā)人員在特性分支(頻繁)提交代碼,立即執(zhí)行構建和單元測試,代碼通過測試標準后集成到主干的過程。強調(diào)的是分支代碼的提交、構建與單元測試,這個過程的產(chǎn)出是單元測試報告。
說明:這里的 test 是指unit test(圖片來源見文末參考鏈接)
<font color="#ff502c">持續(xù)交互(Continuous Delivery)</font>是在持續(xù)集成的基礎上,將構建的代碼部署到「類生產(chǎn)環(huán)境」,完成QA測試之后手動部署到生成環(huán)境的過程。強調(diào)代碼部署,這個過程產(chǎn)出測試報告。
說明:這里的 test 是真的test
<font color="#ff502c">持續(xù)部署(Continuous Deployment)</font>是持續(xù)交互的下一步,強調(diào)部署生產(chǎn)環(huán)境代碼的過程自動化,同時可以處理上線通知等操作。
說明:與持續(xù)交互主要就是手動跟自動的區(qū)別。
2.2 CI/CD的必要性
以一言概之的話我想應該是:機械的事情讓機器做。一個開發(fā)團隊,沒有CI/CD,我想可能是這樣子的:無法管理代碼多人多地協(xié)作(git repository也是CI的一部分),系列的shell需要人工處理,代碼的發(fā)布需要登錄服務器等等;相反,擁有CI/CD,這些事情都交給機器去完成,騰出的碎片時間去做更有意義的事情(比如摸魚放松下)。
2.3 理想的CI/CD開發(fā)流應該是怎樣的?
我認為理想的CI/CD開發(fā)流應該包含三個階段:build、deploy和notify。build階段專注做代碼構建與單元測試,deploy階段專注做test/gray/prod環(huán)境的代碼部署,notify階段專注做上線通知,如下圖;
以下內(nèi)容圍繞build和deploy兩個階段完成從<font color="red">0到1的部署</font>。筆者的系統(tǒng)環(huán)境:<font color="red">Ubuntu 18.04.1 LTS</font>
三、Gitlab-Runner安裝并注冊
3.1 安裝runner
sudo apt-get install gitlab-runner
官網(wǎng)文檔 install gitlab-runner
3.2 注冊runner
sudo gitlab-runner register
之后是QA式操作,按照提示語輸入信息即可,可參考官網(wǎng)操作,需要注意:
①. gitlab host和token在你的gitlab項目上找,頁面路徑是:Settings >> CI/CD >> Runners
②. runner執(zhí)行器選擇 docker,image(鏡像)輸入 node:8.11.2-stretch
注冊成功后,我們就能在:Settings >> CI/CD >> Runners下看到我們注冊的runner
四、Gitlab-Runner配置
在項目根目錄下新建 .gitlab.yml文件,加入如下內(nèi)容:
unit_test、compile和deploy_test是自定義的job名字,另外幾點配置說明:
-
cache:
cache設置緩存文件,這里緩存node_module依賴包,提高job構建效率,定義在全局,對所有的job生效; -
stage:
設置build和deploy兩個階段 -
artifacts:
下載文件。定義compile產(chǎn)出的dist文件夾緩存到gitlab服務器,提供下載(gitlab web頁面下載)或者在同一個stage的各job之間共享; -
only:
定義job的觸發(fā)條件,可以指定分支名、tags(打tag時觸發(fā))等(這些條件是或的關系,滿足其中一個即觸發(fā)); -
when:
定義job的觸發(fā)時機,值可以為:on_success、always、manual等; -
dependencies:
定義當前job所要依賴的job; -
environment:
定義當前job所屬的環(huán)境,對<font color="red">回滾操作非常有用</font>,后面詳述; - deploy_test這個job的
before_script有很長一段內(nèi)容,這里的作用是配置ssh免密登錄,后面詳述。
五、ssh免密登錄
5.1 為什么需要免密登錄?
- 構建產(chǎn)出的dist文件要傳輸?shù)侥繕朔掌鳎y試機/生成機),要么基于http網(wǎng)絡協(xié)議、要么基于ssh協(xié)議(或其他文件傳輸協(xié)議?)
- 基于http需要寫文件接收接口,這里直接使用基于ssh傳輸文件的rsync,簡單、安全!
- runner內(nèi)定義的一系列script是在一個docker容器內(nèi)執(zhí)行的,無法人工干預,那么登錄服務器就要做成免密。
先在本機(注冊runner所在的機器)配一遍免密登錄服務器的流程:
5.2. 生成一對公私鑰
使用rsa作為非對稱加密方式:
ssh-keygen -t rsa -C "$(whoami)@$(hostname)-$(date -I)"
說明:一路enter就好了,切記 Enter passphrase 時直接enter,這樣就是 no passphrase。如果你非要加個password,對不起,沒救了!
5.3. 定義ssh config內(nèi)容
在~/.ssh/config文件寫入以下內(nèi)容(文件不存在直接創(chuàng)建):
Host any_name
Port your_port
HostName server_ip
User user
IdentityFile ~/.ssh/id_rsa
說明:定義ssh的config文件是為了快捷訪問,就像你配置host一樣,沒有hostname,你只能訪問ip。配置后你就可以通過 ssh any_name 登錄服務器了。當然,不出意外,會要求你輸入服務器的登錄密碼。
5.4. 免密登錄
免密登錄的精髓就是:<font color="red">把本機的公鑰存儲到目標服務器的authorized_keys文件內(nèi)</font>(該文件服務器上不存在可以直接創(chuàng)建。)
ssh-copy-id -i ~/.ssh/id_rsa.pub username@ip
特別地:如果你的端口不是默認的22端口,則加上端口號 -p PORT
5.5. 驗證登錄
ssh any_name
不出意外,你應該可以直接登錄服務器了。那么,我們回到gitlab的配置上~
六、gitlab上定義ssh配置信息
我們進入gitlab頁面位置:Settings >> CI/CD >> Environment variables下定義 .gitlab.yml 上出現(xiàn)的幾個變量:
-
SSH_PRIVATE_KEY:把本機(runner所在機器)的私鑰復制過來:~/.ssh/id_rsa -
TEST_CONFIG:把剛才ssh config定義的信息復制過來:~/.ssh/config -
TEST_KNOWN_HOST:定義這個變量是為了讓ssh對服務器進行身份確認(不然會被ssh認為是一個不被信任的環(huán)境),變量值使用以下命令生成:
ssh-keyscan -p PORT IP
七、使用rsync傳輸文件
rsync -rve ssh dist/ user@hostname:project_path/dist
說明:hostname就是你在ssh config定義的Host值。rsync操作指南
八、定義environment
設置environment的好處是可以對各發(fā)布環(huán)境進行管理,特別是線上發(fā)布,出現(xiàn)bug可以及時操作回滾。
在gitlab web頁面位置:Operations >> environments 可以查看當前項目下的environments,點擊其右側(cè)的預覽按鈕即可<font color="red">查看</font>對應環(huán)境的<font color="red">發(fā)布</font>效果
點擊其中一個
environment:test_env,可以查看當前環(huán)境下的所以發(fā)布記錄,右側(cè)的按鈕可以執(zhí)行<font color="red">回滾操作</font>。九、踩坑小記
在整個搭建過程,很多都是關于ssh登錄服務器的問題,擇幾個高頻出現(xiàn)的問題說明下:
9.1. Host key verification failed
當我們初次使用ssh登錄服務器的時候,ssh會要求驗證遠程服務器的身份,通過身份驗證之后才允許連接。解決該問題有兩種方式:
-
設置免身份認證:
在ssh config配置中加一段StrictHostKeyChecking no,如此.gitlab.yml配置中就可以去掉關于known_hosts的設置了; - 通過遠程服務器的公鑰指紋進行身份認證:
ssh-keyscan -p PORT IP
將腳本輸出的結果保存在~/.ssh/known_hosts文件中(<font color="#ff502c">@gitlab上定義ssh配置信息</font>部分有提及),這樣ssh在登錄之前會從該文件中拿到目標服務器的公鑰指紋進行身份確認。
9.2. Permission denied, please try again
出現(xiàn)這個問題是沒有配置「ssh免密登錄」,配置操作見<font color="#ff502c">@免密登錄</font>部分
9.3. rsync: Failed to exec a: No such file or directory
這個問題一般情況下并不會出現(xiàn),但卻是個實實在在的坑。我在deploy的job里面通過rsync將構建生成的dist目錄上傳至服務器,拋出不存在該目錄的錯誤。
我在compile這個job執(zhí)行后,list出根目錄下的文件/夾(gitlab-runner的輸出):
在本機查看根目錄下的文件/夾:
可以看見,gitlab-runner執(zhí)行構建后實實在在是生成了dist目錄,但進入下一個job的時候卻提示不存在!問題出在:
dist目錄并不是由構建直接生成的文件夾,而是
release-[timestamp]目錄的<font color="red">軟鏈接</font>(筆者用的是Ubuntu,在webpack配置里面設置了個騷操作:每次構建產(chǎn)出一個release-[timestamp]目錄,同時建立一個軟鏈。軟鏈不是一個目錄,它的內(nèi)容就是目標文件夾的地址)。在我的
.gitlab.yml配置里面,artifacts緩存的是dist,沒有把實際的文件夾release-[timestamp]緩存,那么進入下一個job的時候,自然就提示不存在該目錄了。解決辦法是:
cp dist public
在compile這個job里面,構建之后復制一份dist目錄,再將public目錄交由artifacts緩存。