基於 GitLab CI 的前端工程CI/CD實踐

giscafer發表於2019-06-17

CI/CD 是 Gitlab 提供的一整套持續整合、持續交付解決方案。

概念:「持續整合(Continuous Integration)」、「持續交付(Continuous Delivery)」和「持續部署(Continuous Deployment)」,概念理解詳細見文章:簡單理解持續整合、持續交付、持續部署 談談持續整合,持續交付,持續部署之間的區別

近期在抽空把團隊工程化這塊做好,CI/CD只是其中的九牛一毛。在運維文開同學協助配合下,以公司某專案前端工程做試驗,實現了 CI 的過程,本質上CD也是支援了的,主要是看CD這個過程怎麼做更好。自動觸發了構建操作,還是直接使用構建後的 artifacts 直接部署,走不走Jenkins後續方案等……下邊簡單介紹一下。

GitLab 的CI配置

前提:伺服器部署配置了 Runner 。 如圖,搞了一個共享型的 Runner,十幾個前端工程都可以基於此 Runner 執行CI指令碼。因為Runner是共享的,所以gitlab-ci.yml 中的 docker 映象 image 建議保證每個專案不一致,這樣就可以共同使用一個 Runner 來並行執行多個專案CI,本質上在不同的 docker 映象中執行指令碼,這樣就不會衝突了。Runner: gitlab-runner

shore-runner

下邊是舉例 Angular 前端工程 在 Gitlab 上實踐的 CI 指令碼,目前只做了程式碼檢查和自動構建過程檢測。實現自動化檢查程式碼是否規範,前端限制了一些程式碼拼寫規範、console.log禁用、alert禁用tslint的約束,這些都可以在工程下自定義維護規則。aot構建是為了進一步檢查一些編譯問題。只要兩者通過,Jenkins 構建100%是無差錯的。

配置:


# GitLab CI/CD 前端 Angular 持續整合實踐 : https://github.com/giscafer/front-end-manual/issues/27
# 因為共享Runner,這裡不建議一樣的版本號,避免同時執行的時候,相同docker映象會出問題
image: node:latest
# image: node:10.4.1

# 變數定義
# https://docs.gitlab.com/ee/ci/variables/#using-predefined-environment-variables
variables:
  NODE_MODULES_VERSION: 'ng-starter-web-1.0.0' # node_modules版本號,每次升級依賴改一下這裡的數值
  CURRENT_BRANCH: $CI_COMMIT_REF_NAME

# 快取目錄檔案
# key是唯一值,重名會覆蓋上一次的快取
cache:
  key: '$NODE_MODULES_VERSION'
  paths:
    - node_modules/

stages:
  - init
  - lint
  - build
  # - deploy

install_packages:
  stage: init
  cache:
    key: '$NODE_MODULES_VERSION'
    paths:
      - node_modules/
  script:
    # 列印一下當前是什麼分支而已
    - echo "NODE_MODULES_VERSION=$NODE_MODULES_VERSION"
    - echo "CURRENT_BRANCH=$CURRENT_BRANCH"
    # 設定 npm 的源,會快一些
    - npm config set registry http://registry.npm.taobao.org/
    # 安裝所有依賴,也就是 node_modules
    - npm install --silent

lint_code:
  stage: lint
  # 定義快取
  cache:
    key: '$NODE_MODULES_VERSION'
    # 下面的配置指示,我們當前只拉取快取,不上傳,這樣會節省不少時間
    policy: pull
    # 指定要快取的檔案/資料夾
    paths:
      - node_modules/
  script:
    - npm run lint
  only:
    - /^dev.*$/ # dev分支下只做lint語法檢查

build:
  stage: build
  cache:
    key: '$NODE_MODULES_VERSION'
    policy: pull
    paths:
      - node_modules/
  script: npm run aot:test
  # artifacets 是打包你指定的檔案或者資料夾,然後你可以通過 gitlab 的頁面進行下載的
  artifacts:
    # artifacets 的名字
    name: '$CI_COMMIT_REF_NAME-dist'
    # artifacets 的過期時間,因為這些資料都是直接儲存在 Gitlab 機器上的,過於久遠的資源就可以刪除掉了
    expire_in: 60 mins
    # 制定需要打包的目錄,這裡我把 dist 目錄給打包了,準備釋出到伺服器
    paths:
      - dist/
  only:
    - master
#
##  部署任務
# deploy:
#   stage: deploy
#   # 該命令指定只有 master 分支才能夠執行當前任務
#   only:
#     - master
#   # 部署指令碼,在下面的程式碼中,我用到了很多類似 ${AMAZON_PEM} 的變數,由於我們的私鑰、Ip 都算是不宜公開顯示的資訊,
#   # 所以我用到了 Gitlab 的變數工具,在 repo 的 Setting > CI/CD > Secret variables 中,這些變數值只有專案管理員才有許可權訪問
#   script:
#     - 'ls -la'
#     - 'ls -Rl dist'
#     - 'echo "${AMAZON_PEM}" > amazon.pem'
#     - 'chmod 600 amazon.pem'
#     - 'scp -o StrictHostKeyChecking=no -i amazon.pem -r dist/* ${AMAZON_NAME_IP}:/usr/share/nginx/html/'


複製程式碼

Angular gitlab-ci.yml 配置還可以參考:stackoverflow.com/questions/4…

配置中有幾個關鍵點需要了解,如:

變數 variables

使用者可以自定義變數或者讀取Gitlab系統自帶的變數,用來動態在指令碼中獲取,也可以根據變數寫一下if語句來執行不同的邏輯,如下:

build:
  stage: build
  script:
    - |
      if [ "$CI_COMMIT_REF_NAME" = "$ci_defined_secret_variable_deploy_branch" ]; then
        echo "build ran and conditional was true"
      fi
  except:
    - master

stagetwo:
  stage: deploy
  script:
    - |
      echo "stage two ran"
  only:
    variables:
      - $CI_COMMIT_REF_NAME == $ci_defined_secret_variable_deploy_branch
複製程式碼

下圖是某工程下的構建配置

namespace

階段 stages

定義 stage,stage 可以簡單的理解為“步驟”,會順序執行,如果上一步錯了,那不會繼續執行下一步,比如像上邊定義的,第一步先 lint 檢查程式碼規範,第二步構建。完整的階段劃分應該為:第一步先初始化,第二步檢查程式碼規範,第三步進行單元測試,第四步構建,第五步就直接將專案部署到伺服器

快取 cache

GitLab CI/CD提供了一種 快取機制,可用於在執行作業時節省時間。

定義全域性的快取策略,如上所說,每個不同的 stage,CI 都會重新啟動一個新的容器,所以我們之前 stage 中的檔案都會消失,在前端開發中,就意味著每個 stage 都要重新完整裝一次 node_modules,這樣的時間和網路成本都不低,所以我們選擇將這些檔案快取下來。

但是,快取也要講究實效性,例如我在第二次的提交中增加了一個庫,那第二次的 CI 就不能再重複使用上一次的 node_modules 快取了,在 .gitlab-ci.yml 中,我們通過設定 cachekey 來區分不同的快取,從配置中可以看到,通過自定義變數 NODE_MODULES_VERSION 來標識 node_modules 的版本,決定是否下載新的依賴,每次工程修改依賴版本或者新增模組時,維護一下這個NODE_MODULES_VERSION 版本號就可以了。可以通過監聽package.json 檔案版本更新,然後指令碼自動修改NODE_MODULES_VERSION版本號。如指令碼:compare-pk.js

# 變數定義
# https://docs.gitlab.com/ee/ci/variables/#using-predefined-environment-variables
variables:
  NODE_MODULES_VERSION: 'ng-starter-web-1.0.0' # node_modules版本號,每次升級依賴改一下這裡的數值
  CURRENT_BRANCH: $CI_COMMIT_REF_NAME
複製程式碼

任務 Job

剩下就是 Job 來定義指令碼了,以上的東西都是給 Job 來使用的。下邊舉例詳細說明:

# 這個是某個任務的名稱,你可以隨意起名
install_packages:
  # 指定該任務所屬的步驟,每到一個步驟,該步驟所對應的所有任務都會並行執行
  stage: init
  # 指定要快取的檔案以及資料夾
  cache:
    # 這個屬性是 gitlab 比較新版本里面加的特性,意思是在這一步,我只上傳這個快取,我不會拉取該快取
    policy: push
    # 指定快取的內容,在下面我快取了 node_modules 這個資料夾,你還可以在下面繼續新增檔案或者資料夾
    paths:
      - node_modules/
  # 該任務要執行的指令碼,順序執行
  # 都是 bash 命令
  # 預設當前目錄就是 repo 的根目錄
  script:
    # 列印一下當前是什麼分支而已
    - echo "CURRENT_BRANCH=$CURRENT_BRANCH"
    # 設定 npm 的源,會快一些
    - 'npm config set registry "https://registry.npm.taobao.org"'
    # 安裝所有依賴,也就是 node_modules
    - "npm install"
複製程式碼

有快取和無快取CI速度對比

無快取做一個lint檢查需要約 8 分鐘

uncache_job_time

有快取則約一分半

cache_node_modules

更多配置項介紹見官方文件yaml/README

開發分支

PR: pull request 或 merge request

每次提交或者 PR 都會自動觸發 job:lintPR 在程式碼lint或者測試沒有通過的情況下,是預設無法合併的(按鈕禁用,許可權大的使用者才能跳過檢查,但不建議,除非你想出錯)。

更方便的是,PR 可以設定為指令碼執行通過後自動合併,當然如果需要 CR (code review) 的話,可以設定為手動合併。 如果連測試都沒有通過的程式碼,就沒有必要 CR 了。

TIM截圖20190410144849

master 分支

可以在 master 分支做持續交付操作(CD), 主要就是自動化構建;將構建成功結果物,通過指令碼來部署即可。如果還有後期的自動化介面或者元件測試,部署後執行測試,如果失敗則回滾。按理是測試成功的程式碼,部署後就一般沒有問題,除非是環境和資料引起的問題。

因為 master 分支是 dev 或者 test 分支 PR 合併過來的,所以他們的測試和程式碼檢查一般都通過了的,當然,合併之前也會重新執行一次程式碼檢查和測試,最後才會走構建的job。

image

Pipeline

Gitlab 的 Pipeline 下可以看到每次提交觸發Job的執行狀態,可以對執行日記檢視,對應job執行成功或失敗都可以發生通知給開發者。

image

image

持續交付(Continuous Delivery)

基於 GitLab CI 的前端工程CI/CD實踐

持續交付在持續整合的基礎上,將整合後的程式碼署到更貼近真實執行環境的「類生產環境」中。比如,我們完成單元測試後,可以把程式碼部署到連線資料庫的 Staging 環境中更多的測試。如果程式碼沒有問題,可以繼續手動部署到生產環境中。

從頻繁提交程式碼、自動化測試(保證測試覆蓋) -> 執行本地測試 -> 伺服器執行測試 -> 部署到測試環境 -> 交付管理

而這些都應該是自動的,所以你需要知道的東西有: 如何編寫測試(Junit、Qunit、BDD、TDD..)、自動化測試(Selenium..)、版本管理(git)、配置(feature toggle)、依賴管理、部署指令碼等等。

從0起做好持續交付並不容易,涉及很多東西,從簡單的做起吧。自動觸發了構建操作,目前如何自動部署,走不走 Jenkins 後續方案討論再定。可以保留 Jenkins 手動構建(出問題可以規避),也可以有自動化構建部署兩種方案都有

後邊又嘗試了Gitlab Pages的CI/CD,構建後上傳到遠端伺服器:

image: node:10.4.1

variables:
  NODE_MODULES_VERSION: 'wiki-web-1.0.0' # node_modules版本號,每次升級依賴改一下這裡的數值
  CURRENT_BRANCH: $CI_COMMIT_REF_NAME

# 快取目錄檔案
# key是唯一值,重名會覆蓋上一次的快取
cache:
  key: '$NODE_MODULES_VERSION'
  paths:
    - node_modules/

stages:
  - build
  - deploy

build:
  stage: build
  cache:
    paths:
      - node_modules/

  script:
    - npm install  --silent
    - npm run build

  artifacts:
    name: 'dist'
    expire_in: 60 mins
    paths:
      - dist
      # - docs/.vuepress/dist

  only:
    - master

deploy:
  stage: deploy
  environment:
    name: Production
  before_script:
    # - sed -i '/jessie-updates/d' /etc/apt/sources.list
    # https://superuser.com/questions/1423486/issue-with-fetching-http-deb-debian-org-debian-dists-jessie-updates-inrelease/1424377#1424377
    - printf "deb http://archive.debian.org/debian/ jessie main\ndeb-src http://archive.debian.org/debian/ jessie main\ndeb http://security.debian.org jessie/updates main\ndeb-src http://security.debian.org jessie/updates main" > /etc/apt/sources.list
    - apt-get update -qq && apt-get install -y -qq sshpass
  script:
    - cd dist/
    - ls
    - sshpass -V
    - export SSHPASS=$USER_PASS
    - sshpass -e scp -P  -o stricthostkeychecking=no -r . root@IP:/data/git_cd
    # - sshpass -e scp -o stricthostkeychecking=no -r . username@host.com:/var/www/html
    # - rsync -avz --delete -e"ssh -p 埠" ./ root@ip:/data/git_cd
  when: on_success
  only:
    - master

複製程式碼

是一個基於 vuepress 的工程,CI 自動構建後,會將打包後的 dist資料夾 上傳到 artifacts , CD 操作的時候從這裡那就好了。

TIM截圖20190410102035

構建生成的附件可以通過 sshpassrsync 將附件上傳到遠端伺服器。相關文章可以參考:

App CI

Android 和 IOS CI 環境搭建參考:

以上是網友的分享文章,前半部分工作主要是搭建 Runner 和 docker 環境,有更快速的方式來搭建。我在搭建 android 環境時,使用了共享性 Runner,image選用的是開源社群的 react-native-community/ci-sample docker 映象,然後配置對應的執行指令碼即可。越過了繁瑣的 android 環境搭建,這就是 docker 的好處了。

android

TIM截圖20190509194049

CI 工具集

常用的有以下幾種:

  • Jenkins :藉助 Jenkins 配合 gitlab 的 webhook 來做CI/CD
  • Circle CI : 強大,對 Github 友好
  • Travis CI:強大,對 Github 友好
  • Gitlab CI:Gitlab 自帶 CI 環境,一樣比較好用.(本文全都是在 gitlab ci 實踐)。

詳情瞭解:dockone.io/article/817…

Jenkins CI/CD 流程圖

分享兩個來自 ProcessOn 網友分享的 Jenkins CI/CD 流程圖:

CICD後端

Jenkins CI

總結

把Runner搞成共享型的,前端的工程就不需要重複配置Runner了,後續逐步的改善即可。一個完整的ci配置應該包含這些過程:第一步先初始化,第二步檢查程式碼規範,第三步進行單元測試,第四步構建,第五步就直接將專案部署到伺服器

時間軸

整合 DevOps,CI/CD 實現是必須的,目前市場和工具方案都特別成熟,個人認為,小團隊或者大團隊都有必要去學習掌握,以便改善團隊的效能問題。一切能指令碼自動化的工作,都不應該人工參與。無論如何,頻繁部署、快速交付以及開發測試流程自動化都將成為未來軟體工程的重要組成部分。

歡迎討論~


推薦:


Author: @giscafer,原文地址:front-end-manual/基於 GitLab CI 的前端工程CI/CD實踐 , 歡迎討論

相關文章