別怕!.gitlab-ci.yml 勇敢寫下去!

身為一個研發人,我有著閱讀官方文件的習慣,但當我打開了 GitLab CI/CD 的官方文件後...
好的,資訊量爆炸!!!

我始終堅信著,每個研發人都應該要有的信念:

科技應要讓使用者專注在心中想解決的問題,而不是在處理使用該科技所碰到的問題。

by Arren Ping

同樣地,我對 GitLab CI/CD 的期望亦是如此。不過,本來也清楚 CI/CD 不是件簡單的事情,文件數量多,反而是好事,不怕碰到困難解不了。

我這麼說服自己,研發人的心臟很強健,不屈不撓,勇敢向前,沒在怕的!

多看了幾份文件後,我理解到,GitLab CI/CD 是透過設定在專案根目錄底下的 .gitlab-ci.yml 這個檔案來驅動,而在經歷了多次的改版,已經有了很多方便使用的措施,其中一個就是 範本,對初學者來說真的是大大大大大......幫助! 找不到的捧油請在專案首頁尋找一個叫做 "Set up CI/CD" 的功能,勇敢地按下去,就會出現可以編輯 .gitlab-ci.yml 的界面,裡頭就有範本可以選。
我所需要的 maven 的範本自然也被包括在內,如下:

# This file is a template, and might need editing before it works on your project.
# Build JAVA applications using Apache Maven (http://maven.apache.org)
# For docker image tags see https://hub.docker.com/_/maven/
#
# For general lifecycle information see https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html

# This template will build and test your projects
# * Caches downloaded dependencies and plugins between invocation.
# * Verify but don't deploy merge requests.
# * Deploy built artifacts from master branch only.

variables:
  # This will suppress any download for dependencies and plugins or upload messages which would clutter the console log.
  # `showDateTime` will show the passed time in milliseconds. You need to specify `--batch-mode` to make this work.
  MAVEN_OPTS: "-Dhttps.protocols=TLSv1.2 -Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true"
  # As of Maven 3.3.0 instead of this you may define these options in `.mvn/maven.config` so the same config is used
  # when running from the command line.
  # `installAtEnd` and `deployAtEnd` are only effective with recent version of the corresponding plugins.
  MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version -DinstallAtEnd=true -DdeployAtEnd=true"

# This template uses jdk8 for verifying and deploying images
image: maven:3.3.9-jdk-8

# Cache downloaded dependencies and plugins between builds.
# To keep cache across branches add 'key: "$CI_JOB_NAME"'
cache:
  paths:
    - .m2/repository

# For merge requests do not `deploy` but only run `verify`.
# See https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
.verify: &verify
  stage: test
  script:
    - 'mvn $MAVEN_CLI_OPTS verify'
  except:
    - master

# Verify merge requests using JDK8
verify:jdk8:
  <<: *verify

# To deploy packages from CI, create a ci_settings.xml file
# For deploying packages to GitLab's Maven Repository: See https://docs.gitlab.com/ee/user/project/packages/maven_repository.html#creating-maven-packages-with-gitlab-cicd for more details.
# Please note: The GitLab Maven Repository is currently only available in GitLab Premium / Ultimate.
# For `master` branch run `mvn deploy` automatically.
deploy:jdk8:
  stage: deploy
  script:
    - if [ ! -f ci_settings.xml ];
        then echo "CI settings missing\! If deploying to GitLab Maven Repository, please see https://docs.gitlab.com/ee/user/project/packages/maven_repository.html#creating-maven-packages-with-gitlab-cicd for instructions.";
      fi
    - 'mvn $MAVEN_CLI_OPTS deploy -s ci_settings.xml'
  only:
    - master

我想,或許不少人跟我一樣看到範本後呈現呆滯狀(汗~

碰到困難就想辦法解決

面對這種狀況,我的做法是:

  1. 不要心生恐懼
  2. 思考自己期望達成的效果
  3. 勇敢再看一次

身為一個 CI/CD 的初心者,我想要的效果其實很簡單,在每次 merge 之後只要有人能幫我打包好,讓我能方便的下載包好的結果就好了!
這個幫忙打包的人,我希望是 GitLab CI/CD。(其實不要是我就可以,是誰做我都 OK!溜~
CI 就是持續地整合,打包就是其中一個過程;CD 就是持續地部署,打包的結果被放置到特定的位置,就是部署的一種形式。


先講個題外話,原本在我的想像裡,我以為 GitLab CI/CD 會嚴謹的定義 CI 與 CD 的每個步驟,以及在每個步驟中可以做的事情,最好是提供了種種的外掛可以讓使用者開心地挑選、使用,然後 Just Work!!!在我充分瞭解之後,以上純屬想像,不是 GitLab 的做法。
GitLab 提供的方案一樣是 Just Work,但有哪些步驟及步驟中該做哪些事情的決定權,完全交給了 Developer 本人,給了開發者最大的彈性可自由揮灑,擁有無限多種可能!是的,只要寫好自己想要的 .gitlab-ci.yml 就可以!


發揮研發人的想像力

回過頭來看範本的結構,可以發現 .gitlab-ci.yml 中有縮排,有些詞帶有 : 結尾,我們先從第一級(無縮排)的這些詞開始看:

  • variables
  • image
  • cache
  • .verify
  • verify:jdk8
  • deploy:jdk8

發揮想像力的時間到了,來猜猜這些詞的用途或目的是什麼:

  • variables => 應該是宣告變數吧
  • image => 感覺在 GitLab 這種純 command line 的執行環境裡一定不是影像,應該是指映像檔
  • cache => 快取,跑不掉了
  • .verify => 唔... 前面有個點,是什麼鬼,跳過(逃~
  • verify:jdk8 => 驗證 JDK 8??
  • deploy:jdk8 => 部署 JDK 8??

接著,我們依照前面的做法補上第二級:

  • variables => 應該是宣告變數吧
    • MAVEN_OPTS:
    • MAVEN_CLI_OPTS:
  • image => 感覺在 GitLab 這種純 command line 的執行環境裡一定不是影像,應該是指映像檔
  • cache => 快取,跑不掉了
    • paths
  • .verify => 唔... 前面有個點,是什麼鬼,跳過(逃~
    • stage
    • script
    • except
  • verify:jdk8 => 驗證 JDK 8??
    • <<
  • deploy:jdk8 => 部署 JDK 8??
    • stage
    • script
    • only

好的,一樣,有 耐心 地發揮想像力,但請將第一級的推測也加入一起考慮,來理解第二級可能的意思:

  • variables => 應該是宣告變數吧
    • MAVEN_OPTS => 這個名稱和 maven 定義的環境變數相同,應該就是給 maven 使用的設定值
    • MAVEN_CLI_OPTS => 有個 CLI 感覺像是執行時期使用的
  • image => 感覺在 GitLab 這種純 command line 的執行環境裡一定不是影像,應該是指映像檔
  • cache => 快取,跑不掉了
    • paths => 路徑,應該是快取資料的來源位置,或者是要把快取資料存放的位置
  • .verify => 唔... 前面有個點,是什麼鬼,跳過(逃~
    • stage => 絕對不是舞台(溜~,應該是階段吧!
    • script => 要執行的 script
    • except => 要排除什麼東西??
  • verify:jdk8 => 驗證 JDK 8??
    • << => 迷樣符號,跳過(逃~
  • deploy:jdk8 => 部署 JDK 8??
    • stage => 第二次出現,應該是階段!
    • script => 要執行的 script
    • only => 只能有什麼東西??

當這些推測都做完了之後,我們可以開始看解釋 .gitlab-ci.yml 的官方文件了,請參考這份 GitLab CI/CD Pipeline Configuration Reference
有些人喜歡從上到下閱讀整份文件,而我的習慣是,部分理解後若有必要再補全整體知識,所以,我先從關鍵字下手!

確立正確的知識

關鍵字理解了之後,再重新更新一次:

  • variables => 宣告變數
    • MAVEN_OPTS => 給 maven 環境使用的設定值
    • MAVEN_CLI_OPTS => 使用者定義的變數,在 script 期間可以選擇代入使用
  • image => 指定要使用的 docker 映像檔,映像檔可以在 Dock Hub 上找
  • cache => 設定要使用快取
    • paths => 快取的路徑,要被快取的資料來源路徑
  • .verify => 自訂的 anchors,在執行時期時,會將引用此 anchors 的位置替換為 anchors 宣告的內容
    • stage => 執行階段
    • script => 要執行的 script
    • except => 在哪些情況下不執行
  • verify:jdk8 => 不是保留字,是個使用者自訂的 job。配合註解來看,應該是想要採用 jdk8 執行 maven 的驗證工作
    • << => 引用 anchors
  • deploy:jdk8 => 不是保留字,是個使用者自訂的 job。配合註解來看,應該是想要採用 jdk8 執行 maven 的驗證工作
    • stage => 執行階段
    • script => 要執行的 script
    • only => 只在哪些情況下執行

至此,整個輪廓變得清晰了許多,要特別提醒 務必理解 幾個我覺得最重要的問題:

  1. stage 是什麼?
  2. stage 是怎麼定義的?
  3. job 是做什麼用的?
  4. 為什麼需要 cache?
stage 是什麼?
  • stage 是工作階段
  • 一個 stage 中可以包含多個 job,job 的執行不保證順序
  • stage 之間是有順序相依的,必須確保前一個 stage 完成後,才會執行下一個 stage
  • 一般情況下 stage 中的 job 都必須正確無誤完成後,才能執行下一個 stage
stage 是怎麼定義的?
  • 在使用者沒有定義的情況下,預設提供三個 stage => build、test 和 deploy
  • 使用者可以透過 stages 定義自己想要的階段及順序
stages:
  - build
  - test
  - deploy
job 是做什麼用的?
  • job 定義了要完成的工作
  • GitLab CI/CD 會安排 Runner 也就是派遣工來承接 job,依照 job 中的定義來執行工作
  • GitLab CI/CD 的派遣工有個缺點 => 有失憶症,就算來幫忙執行 job 的是同一人,他也完全不記得之前做過些什麼
  • 就算是被歸類為同一個 stage 的 job,也不保證誰先執行、誰後執行,彼此之間也互不認識
為什麼需要 cache?
  • 其實沒有 cache 也可以,有了或許可以加速 job 的執行速度
  • 有些工作需要一些資源才能執行,例如:相依的函式庫,但因為派遣工每次都會失憶症發作,在沒有設定 cache 的情況下,就會重新獲取所有需要的資源,這樣很慢且很貴(Runner 若非開放原始碼專案是有總執行時間上限的),設定了 cache 後,就可以避免這個問題,而是直接從 cache 中取得

回歸需求,大膽執行!

現階段我的需求是 => 在每次 merge 之後只要有人能幫我打包好,讓我能方便的下載包好的結果。

換言之,

  1. 不需要測試
  • 因為我已經人工本地測完了
  1. 沒有特殊的部署
  • 沒有要發佈到 production 環境的需求
  • 也沒有要版本庫
  • 只要能有個地方下載檔案就好
  1. 別忘了,是用 maven + JDK 11

基於以上,第一版 .gitlab-ci.yml 如下:

# 請上 Docker Hub 找適合自己的
image: maven:3.6.3-jdk-11

stages:
  - build

# 完全引用 template 中的設定
variables:
  MAVEN_OPTS: "-Dhttps.protocols=TLSv1.2 -Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true"
  MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version -DinstallAtEnd=true -DdeployAtEnd=true"

build:
  stage: build
  only:
    - master
  script: 
    - mvn $MAVEN_CLI_OPTS package
  # 產生出的成果會被保留供下載
  artifacts:
    # GitLab 提供的環境變數,意思是產生出的成果下載時的檔名將會是專案名稱
    name: "$CI_PROJECT_TITLE"
    # 要保留哪些成果的路徑
    paths:
      - target/*.jar

做到這只差最後一步 => 請勇敢的將 .gitlab-ci.yml commit & push!

真的完全不需要擔心,GitLab CI/CD 會先驗證檔案的正確性,且 Runner 並不會因為這樣就壞掉。
頂多只是 job 有誤,流程沒有成功跑完而已。
所以,
恭喜跨出了 GitLab CI/CD 的第一步,請愉悅地享受 CI/CD 的效果及成就感吧!