Contents

初探GitLab CI/CD - 如何撰寫.gitlab-ci.yml

使用 CI/CD 建構應用程式

開始

CI/CD 是軟體開發其中一個continuous method,你可以在這方法中持續的建構、測試、部署、監測迭代的程式碼異動。

這種方式有助於降低在有誤的前一個版本中開發新程式,GitLab CI/CD在開發早期階段就能抓錯,確保部署到正式環境的程式碼符合所建立的程式碼標準,而這個程序是更大的workflow之中的一個環節:

Plan Create Verify Secure Release Monitor
Manage your organization Learn Git Use CI/CD to build your app Secure your app Deploy and release your app Monitor app performance
Organize work with projects Manage your code Manage infrastructure Monitor GitLab Runner usage
Plan and track work Analyze GitLab usage
1. 首先建立.gitlab-ci.yml

在project根目錄建立 .gitlab-ci.yml,定義在CI/CD pipeline中所要執行的 stages, jobs, script。

另外也會定義variables,不同jobs之間的依賴關係,並且指定每個job什麼時候會被執行,應該如何被執行。

沒有硬性規定要怎麼命名,但.gitlab-ci.yml是這個 CI/CD configuration file 最常見的名字。

2. 接著尋找或新建 runners

Runners 是替你執行 jobs 的 agents,可以在實體機或者虛擬instances上run jobs。在 yml 檔可以指定你run jobs的時候,要指定使用的container image。Runner會把image載下來,clone你的project,並且在本地或者container裡面 run the job

如果使用的是 GitLab.com,那就已經可以使用 runners on Linux, Windows and macOS,也可以在GitLab.com註冊你自己的runners。

如果不是使用 GitLab.com,那可以: (1) 在自己管理的instance,使用已經註冊的runner或者註冊一個runner (2) 在本機新建一個runner

3. 定義你的 pipelines

pipeline 就是定義在 yml 檔的東西,即檔案內容在 runner 上執行時會發生的事情,是由 jobs 跟 stages 組成:

  • Stages 定義執行的順序,通常包含build, test, deploy
  • Jobs 指定在每個 stage 所要執行的 tasks,例如編譯或測試程式的 job

pipeline 可以藉由不同類型的事件觸發,例如 commits, merges 或者依排程執行

4. 使用CI/CD variable作為 jobs 其中一部分

GitLab CI/CD variable 鍵值對應,是用來儲存『配置設定以及機敏資訊』(例如pcode或者API keys),並且傳進 pipeline jobs

有兩種 variables: custom variable 跟 predefined variable

  • Custom variable: 由 user 定義,在 yml 檔、GitLab UI或者API建立/管理
  • Predefined variable: 由 GitLab 自動設定,提供了關於當前job、pipeline以及enviroment相關的資訊
5. 使用 CI/CD components

CI/CD component 可以重複使用的 pipeline 配置單位,一個 pipeline 可以由一個甚至多個CI/CD component組建而成。

可以用include:component關鍵字來把 CI/CD component 加進 pipeline 設定檔。

優點: 可以減少重複,改善維護性,使整個project有更好的一致性

比較複雜的 pipeline

這一個TUTORIAL挺不錯的,跟著做會得到一個用Docusaurus產出的網站 連結在這裡 自己的 github 是用 hugo,但 gitlab + docusaurus 是前公司共筆的做法,很好奇,找天來做看看

  • image: 告訴 runner 這個 job 要以哪一個 image 的 docker container 來 run,這裡 runner 負責:
    1. 下載 container image 並且啟動它
    2. 把你的 GitLab project CLONE 進 running container
    3. 執行 script 裡面列的命令 (一次一行)
  • artifacts: jobs各自獨立,不會跟其他 jobs 共享資源,如果要讓某個 job 產出的檔案能夠為後續的 jobs 所用,必須要把它另存成 artifacts,這樣後續的 jobs 就能接收到 artifacts 並使用產出的檔案
    build-job:
      image: node
      script:
        - npm install
        - npm run build
      artifacts:
        paths:
          - "build/"
    
  • allow_failure: 會斷斷續續失敗,或者預期會失敗的 job 可能會降低生產力或者難以排除錯誤。使用這個關鍵字可以讓 job 失敗時不至於造成 pipeline 執行的停擺
    • allow_failure: true
  • dependencies: 藉由列出 artifacts 的取得來源 jobs,來管控個別的 jobs 要下載的 artifacts
    • don’t fetch any artifacts: dependencis: []
    • fetch artifacts from dependencies list:
      1
      2
      
        dependencies:
          - build-job
      
  • rules: 在每個jobs裡面加上rules,以配置這些jobs應該在那些 pipeline 執行,例如 merge request pipeline、scheduled pipelines。Rules是由高到低來評估,如果其中一個規則 rules 吻合,這 job 就會被加進 pipeline
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    build-job:
      stage: build
      image: node
      script:
        - npm install
        - npm run build
      artifacts:
        paths:
          - "build/"
      rules:
        - if: $CI_PIPELINE_SOURCE == 'merge_request_event'  
          # Run for all changes to a merge request's source branch
        - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH       
          # Run for all changes to the default branch
    

規劃遷移到 GitLab CI/CD

from Bamboo, GitHub actions, Jenkins, a maven build from Jenkins, CircleCI …

CI/CD 範例

使用 Dpl部署, end-to-end, npm, php

CI/CD YAML 句法參考

關鍵字分成三種類型: Global keyword, Header keyword, Job keyword (另外有 Deprecated keyword) CI/CD configuration file syntax reference

GitLab Runner 這應用程式跟 GitLab CI/CD 協作在pipeline run jobs,有兩種 runners:

  1. GitLab-hosted runners

所有 GitLab.com 或 GitLab Dedicated 上面建立的 project 預設都有啟用這個 ,如果有owner角色的話,可以停用 runners

  1. Self-managed runners

如果要建自有的runner,首先要安裝GitLab Runner,再到GitLab.com註冊自有的runner

若是你的組織有一整組 runners,擴增縮減的監控調校方式可以參考這裡

GitLab Runner TAGS

註冊完 runner 之後,可以加上 tags。這個 tag 會映射到每個 jobs 設定tags:底下的內容

Example: 以下的 job 執行時會使用掛ruby tag的 runner

job:
  tags:
    - ruby
  • 使用 config.toml 設定 runner,這檔案是在 runner installation 的時候就載下來的。可以用來調整個別指定runner或者所有runners的設定。可以調 logging, cache, concurrency, memory, CPU limits 等其它設定
  • 使用 Prometheus 監控 runners,可以檢視當前運行中 jobs 的數量,以及這些 runners 用了多少 CPU

Runner execution flow

以下文章解釋 GitLab, GitLabRunner 還有 Executor 之間的關係很易懂,就不在此贅述了

Pipelines

Pipeline 是持續整合、持續交付、持續部署之中,最高層級的component

Pipeline 包括了:

  • Jobs: 定義了要做什麼事情,例如編譯code的job、測試code的job
  • Stages: 定義什麼時候要run這些jobs,例如在compile stage之後才到run tests stage

Job 是由 runners 執行,如果concurrent runner數量足夠的話,同一個stage的多個jobs會被平行處理。

如果一個stage的所有jobs都執行成功,pipeline才會執行到下一個stage。

如果一個stage有任何一個job fails,通常不會執行下個stage,pipeline會提前結束。

通常 pipeline會自動執行,一旦建立了就不需要其它的介入。但有時候你也可以手動跟pipeline做交互。

典型的pipeline會包含四個stages,依序以下列的順序執行:

  • build stage: 包含一個compilejob
  • test stage: 通常會有一至多個test jobs
  • staging stage: 包含一個名為deploy-to-stage的job名稱
  • production stage: 包含一個名為deploy-to-prod的job名稱

可以用多種不同方式來配置pipelines

  • basic pipelines: 在每個stage中,並行執行所有的jobs,後接下個stage

  • directed acyclic graph pipeline (DAG): 根據job之間關係,run的速度通常比basic pipelines還要快 (可以避免不必要的等待)

  • merge request pipelines: 只有 merge request 執行時才會用的 pipeline

  • merged result pipelines: 在實際合併分支之前測試合併之後的結果

  • merge trains: 利用 merged results pipelines,讓merges的動作一個接著一個執行

  • parent-child pipelines: 將複雜的pipeline拆解成一個parent pipeline,這個parent pipeline可以觸發多個 child sub-pipelines,parent & children 都run在相同一個project,使用一樣的SHA,這種類型的pipeline架構常常出現在 mono-repos

  • multi-project pipelines: 將負責不同projects的pipelines合併起來

pipeline architecture

pipeline配置設定

可以用 CI/CD yaml 配置 jobs 以及 stages,也可以用 GitLab UI 配置特定層面的 pipelines

如果是用VS Code編輯GitLab CI/CD配置文件,可以安裝GitLab Workflow VS code extension幫助你驗證configuration並檢視pipeline status

在manual pipeline預先填變數

可以使用descriptionvalue關鍵字來定義 pipeline-level(global)的變數,這些變數在手動run pipeline時可以預填內容。description 主要是用來解釋這個variable用來做什麼,允許的值是什麼。

job-level variable 無法填預設內容。

如果configuration檔案的pipeline-level variable有列description,但沒有定義value的話,就會是空白值(blank value),例如:

variables:
  DEPLOY_CREDENTIALS:
    description: "The deployment credentials."
  DEPLOY_ENVIRONMENT:
    description: "Select the deployment target. Valid options are: 'canary', 'staging', 'production', or a stable branch of your choice."
    value: "canary"
  • DEPLOY_CREDENTIALS 沒有預先填value,所以每次手動run pipeline的時候都要定義值
  • DEPLOY_ENVIRONMENT 預設值是 canary,在description區塊解釋了其它可用的選項
配置一個可擇的預填變數清單

在手動run pipeline時,也可以定義一個陣列讓執行pipeline的user做選擇,用options關鍵字列清單,value關鍵字列預設選項。注意放在 value 的字串也必須包含在 options 清單中,這個清單在Run pipeline UI頁會變成一個下拉選單。

variables:
  DEPLOYMENT_ENVIRONMENT:
    value: "staging"
    option:
      - "production"
      - "staging"
      - "canary"
    description: "The deployment target, Set to 'staging' env by default."
run a pipeline by URL query string

可以使用一個查詢字串來預填 Run Pipeline頁,例如:

  • .../pipelines/new?ref=my_branch&var[foo]=bar&file_var[file_foo]=file_bar
    • Run for field: my_branch
      • 指明要用哪一個branch來預填 Run for field
    • Variables:
      • variable:
        • 指明Variable類型variable
        • Key: foo
        • Value: bar
      • file:
        • 指明File類型的variable
        • Key: file_foo
        • Value: file_bar
Add manual interaction
  • Manual jobs:
    • 定義: 除非user啟動之,否則這個job永遠不會run,通常用在 deploy to production 的情境中
    • 設定: 在這個job之下加上關鍵字when: manual,這樣pipeline啟動時就會被skipped掉
Start all manual jobs in a stage
  • 如果某一個stage裡面全部都是manual jobs,可以透過Run all manual 來同時啟動所有jobs (但如果這stage有包含non-manual job就不會顯示此按鈕)
Skip a pipeline
  • 如果要commit但不想要觸發pipeline,則在commit message加上ci skip或者skip ci (大小寫不分)
  • 另外,在 Git 2.10 或更後期的版本,使用 ci.skip 作為 git push 的選項,通常並不會跳脫不執行merge request pipeline
    git push -o ci.skip 
    # 
    
    • -o: 這裡指的是 –push-option
    • 使最近的一筆push不會產生一個CI/CD pipeline (只會skip branch pipeline,merge request pipeline如果有的話還是會執行)
    • 另外這個command也不會跳過CI/CD整合相關的 pipeline (例如Jenkins)
Delete a pipeline

擁有該 project 的 Owner 角色的 user 有權限刪除 pipeline,但刪除一個pipeline並不會自動刪掉它的child pipelines

刪除一個 pipeline 會使所有 pipeline caches 逾期,並且立刻刪除所有相關物件,例如jobs, logs, artifacts, triggers (刪除後就無法復原了)

Pipeline security on a protected branches
  • protected branch

    • 可以設定哪些user可以merge, 哪些user可以push,那些能夠force push, 那些user能透過 Commits API 修改branch
    • 如果pipeline在這類branch執行,會實行嚴格的 security model
  • 如果某 user 有權限 merge 或 push 進這個 branch,就可以

    • 使用 Web UI 或 pipelines API,run manual pipeline
    • run 有排程的 pipeline
    • 觸發 pipeline
    • 跑動態應用程式安全測試(DAST, as dynamic application security test)
    • 在既有的 pipeline 觸發手動的action
    • 使用 Web UI 或 pipelines API,重試或取消既有的jobs
  • protected variable:

    • 只有當user存取機敏資料 (例如 deployment credentials, deployment token) 的權限,才適合分配 merge code to protected branches 的權限給此 user
    • protected branches的pipeline,其下的jobs可以存取protected variables
  • protected runner:

    • 受保護的runner只能在受保護的分支run jobs,以避免未受信任的程式碼在受保護的runner上執行,也避免部署相關的機敏資料被意外取得
    • 為了確保要在受保護的runner執行的jobs不在regular普通runners執行,這些jobs需要加上關鍵字 <job_name>: tags: reference job tags

檢視pipeline

  • 可以用來過濾pipeline list的條件有: trigger author, branch name, status, tag, source
使用 stage 或者 needs 配置來分組 jobs

如果用needs關鍵字配置jobs,則會有兩種在pipeline明細頁分組jobs的選項:group jobs by stage, group jobs by Job dependencies

  • needs: [] : 不依賴於任何其它jobs

設定依賴的方式:

  1. 在jobs yml設定檔裡面加 needs: ["<the_job_name_to_depend_on>"]
    • jobs array 上限數量50個
    • 設為 empty array,則這個 job 在此 pipeline 創建時就會開始作業了
    • 其它 needs syntax
  2. 可以在UI檢視依賴關係: Group jobs by Job dependencies -> 打開 show dependencies

pipeline success and duration charts

點選要查看的 project -> 左側選單 -> Analyze -> CI/CD 就可以看到

Note: DORA 指標(DevOps Research and Assessment) Reference: 部署頻率,異動交付期間,服務恢復時間,更改失敗率

Jobs

Jobs是GitLab的CI/CD pipeline中,最基本的元素,在.gitlab-ci.yml用 command清單來配置jobs,以達成建構、測試、部署程式碼的目的。

基本上是export=儲存變數,符合某條件時if[...]; then ...; fi儲存變數,cd 移動到某資料夾,docker build…,docker push …,./gradlew…,查看java --version,或者aws ecr指令操作

Jobs:

  • 定義在符合哪些條件下,應該執行哪些作業
  • 是 top-level 元素,任意命名(arbitary name),至少必須包含script子句
  • 可以定義的jobs數量沒有上限

Job 由 runners 收集,在 runner environement 執行,每個 jobs 的運行都各自獨立於其它的 jobs。

在 pipeline 中檢視 jobs

可以在pipeline查看其中的jobs,點選個別的job可以查看這個job log,另外可以:

  • 取消這個job
  • 如果失敗,重試這個job
  • 如果通過,再跑一次這個job
  • 清除這個job log

查看 job failed 失敗原因

可以用滑鼠 hover 到 pipeline graph(在pipeline details檢視頁), pipeline widgets(在 merge requests&commit頁面) 或者 job views 查看失敗原因

pipeline 裡面的 job order

視pipeline graph的類型不同,有不同的job順序

  • full pipeline graph(詳細大圖): 以name排序
  • pipeline mini graph(): 先以status排序。再以name排序

Job status order:

  • failed red x
  • warning yellow !
  • pending grey circle
  • running blue two-thirds sector
  • manual black gear
  • scheduled ?
  • canceled white \
  • success green v
  • skipped gray »
  • created ?

job 命名限制

  • job名稱必須小於或等於255字元,如果一個檔案有多個相同名稱的jobs,只有一個會被加進pipeline,而且無法預判是哪一個被加進pipeline

  • 如果被included的job同名,其參數會被merging

  • 以下的關鍵字不能當作job名稱: image | services | stages | types before_script | after_script | variables cache | include | true | false | nil pages:deploy (configured for a deploy stage)

在 pipeline 裡面替 jobs 做分組

如果你有很多類似的 jobs,呈現的 pipeline graph 就會太長難以閱讀

如果 job names 是以特定格式命型,就可以自動將相似的 jobs 做分組,在 regular pipeline 就會被摺疊成單一的組別

.gitlab-ci.yml將多個jobs寫成一組,可以用數字以及以下符號擇一:

  • Slash 正斜線: slash-test 1/3, slash-test 2/3, slash-test 3/3
  • Colon 冒號: slash-test 1:3, slash-test 2:3, slash-test 3:3
  • Space 空格: slash-test 0 3, slash-test 1 3, slash-test 2 3

隱藏 hidden jobs

可以用 comment out (加#,CTRL+L) 或者job_name前面打句點

前面打句點的 job 可以用來作為範本給其它 jobs 做重複使用的配置,使用方式有二:

控制 default keyword 以及 global variable 的繼承

可以用inherit:default 管控 default keywords 的繼承 可以用inherit:variables 管控 global variables 的繼承

default:
  image:
  before_script:

variables:
  DOMAIN: ywc.wednes.com
  WEBHOOK_URL: https:/wysiwyz.io

job_name_taht_inherits_all:
  inherit:
    default: true
    variables: true
  script: bundle exec inheriting all
  
job_that_doesnt_inherit_anything:
  inherit:
    default: false
    variables: false
  script: bundle exec inheriting nothing
  
job_that_doesnt_require_before_script_and_domain:
  inherit:
    default: [image]
    variables: [WEBHOOK_URL]
  script: echo this job does not inherit before script and domain
    

run manual jobs的時候指定variables

在 pipeline view 點擊要手動執行的 job 的名稱,不要點 Run ▶ 按鈕

可以看到 Variables 有 key 跟 value 的欄位可輸入內容

如果輸入的 key 已經定義在 CI/CD settings 或者 .gitlab-ci.yml 檔,那這個手動加入的變數會覆寫原有的值,新的值不會被隱碼,會被expanded(加上$參照到其它variable

延後一個job的執行

當你不想要立刻執行job,可以使用when:delayed關鍵字來使延遲 job execution 一段時間

roll out 新程式碼的時候,timed incremental rollout 尤其有利於逐步部署程式碼到生產環境

例如說,如果你要開始部署新程式碼:

  • User 沒有遇到困難,GitLab 可以自動地從0~100% 完成部署
  • User 在新程式碼遇到疑難的話,可以藉由取消pipeline並倒回到上一個穩定版本,來停止 timed incremental rollout

展開或折疊 job log sections

Job log 可區分為不同 sections,每個section可以被折疊或展開,每個section會顯示這期間的相關訊息 點擊 > 可以展開 section;點擊 v 可以折疊 section

客制化可折疊的section

可以用 manual output special codes (這是GitLab用來判斷哪一個section要摺疊的程式碼),在 job logs 裡面建立 collapsible section:

  • Marker: Section起始處 \e[0Ksection_start:UNIX_TIMESTAMP:SECTION_NAME\r\e[0K + TEXT_OF_SECTION_HEADER
  • Marker: Section結束的地方 \e[0Ksection_end:UNIX_TIMESTAMP:SECTION_NAME\r\e[0K

這兩段marker都是加在 CI configuration 的腳本區塊,使用 echo -e “starting_marker”、echo -e “ending_marker”

如果使用的是 Zsh shell,會需要跳脫字元,例如\e\\e以及\r\\r

  • UNIX_TIMESTAMP: 例如data+%s,是Unix timestamp
  • SECTION_NAME: 這個section的名稱,只能由英數字以及_,.,-這三種符號構成
  • \r\e[0K: 用來避免將section markers顯示在渲染出來的job log裡面 (在raw job log仍然看的到),切換到 raw job log 可以點 job log 右上角,選擇show complete raw (文件icon)
    • \r: carriage return
    • \e[0K: 清除 line ANSI escape sequence (一定要加0,\e[K無效)

另外也可以 用腳本改善可折疊的section display

deployment jobs 部署作業

任何使用 environment 關鍵字,以及action:start 的job,就稱之為Deployment jobs,用途是部屬程式碼到環境中

Deployment jobs 不必須在 deploy stage

deploy_job_this_is:
  script:
    - deploy-to-cats.sh
  environment:
    name: production
    url: https://this-is-a-simple-url.com
    action: start

CI/CD components

CI/CD component 是可以重複使用的單一的 pipeline 配置單位。component 可以建立成一個大型 pipeline 的其中一小部分,甚至可以構成完整的 pipeline configuration。

component可以用傳入參數的配置來動態調整行為。

CI/CD components 類似於用include關鍵字加入其它的configuration yml file (參考官方Docs) ,但使用components有以下不少優勢:

  • 可以將 components 列在 CI/CD catalog 之中 (問: 這應該是有Owner角色的使用者才能看到,因為我都看不到😅)
  • 可以發行 component 並指明使用特定一個版本
  • 多個 component 可以定義在同一個 project 並且一起定義版本編號

除了創建自有的 components,也可以在CI/CD catalog 搜尋現成且符合所需功能的已發布 components

Component project

component project 是一個 GitLab project,包含 個 repository,hosting 一至多個 components。這個 project 之中的多個 components 都一同編版本號,一個 project 上限最多可以有 30 個 components。

如果其中某一個 component 要求跟其它 components 不一樣的版本編號,那這個 component 應該被搬到另外一個專用的 project。

建立一個 component project

  1. New project with README.md (如要發布到 CI/CD catalog 的話,顯示這個component摘要的時候,會用到 description & project avatar)
  2. 依照以下的 required directory structure,替每一個 component 加上 YAML 配置檔案 需求目錄架構
    spec:
      inputs:
        stage:
          default: test
    ---
    component-job:
      script: echo job 1
      stage: $[[ inputs.stage ]]
    

這樣就可以立刻開始使用component了,使用 include 關鍵字,component 參照的格式:

<fully-qualified-domain-name>/<project-path>/<component-name>@<specific-version>:

include: 
  - component: gitlab.example.com/yws-org/security-components/secret-detection@1.0.0
    inputs:
      stage: build
  • secret-detection 指的可能是 secret-detection.yml 檔名,也可能是指某一個目錄,目錄裡面的template.yml

參考docs連結在這裡

component project 的目錄架構

  • 如果只有單一一個component

    ├── templates/
    │   └── my-component.yml
    ├── LICENSE.md
    ├── README.md
    └── .gitlab-ci.yml
    
    • 這裡的gitlab-ci.yml作用是用來寫job測試 my-component,並且發布新版本用的
    • component都放在最高層的templates/目錄底下
    • 每一個components也可以添加自己的README.md文件,再連向上層的 top-level README.md 文件
    • LICENSE.md,選擇像是 The MIT License from open source initiative 或者 Apache License, ver.2.0

      這個LICENSE是開源許可證,用來定義使用、複製、修改程式碼的條款與條件,Apache License 2.0 適合涉及專利保護相館的考量,而 The MIT License 比較簡單靈活,沒有專利條款限制,只要求保留版權聲明和許可證

  • 如果有多個components

    ├── templates/
    │   ├── my-simple-component.yml
    │   └── my-complex-component/
    │       ├── template.yml
    │       ├── Dockerfile
    │       ├── test.sh
    │       └── README.md
    ├── LICENSE.md
    ├── README.md
    └── .gitlab-ci.yml
    

Component versions:

以最高優先權排序,component version可以是:

  1. commit SHA code,例如 439e77c88be865fa99afeed0ecd4ee3b17ae4af7
  2. tag
    • 例如 1.0.0
    • 如果一個name有tag跟commit SHA同時存在,則以commit SHA優先
    • 發佈到 CI/CD 的 component 應該依照 semantic versioning 的標準加上 tag
      • semantic version 是定義該異動為 major, minor, patch 或其他類型異動的標準
      • 1.0.02.3.41.0.0-alpha 這些都是有效 semantic versions
  3. branch name分支名稱,main,如果 tag 跟 branch 皆存在,以 tag 為優先
  4. ~latest,永遠指向CI/CD catalog上面最新發行的 semantic version

建議使用有發布至 CI/CD catalog 的版本,用 commit SHA 或者 branch 名稱參照的版本可能尚未發布到 catalog,是供測試用。

避免使用 global keywords 關鍵字

避免使用會影響到整個pipeline裡面所有jobs執行的所有components,包括定義在 main .gitlab-ci.yml 或者其他 included components 的 jobs

Global keywords:

  • default: 如果job沒有定義這些keyword,就會用到default區的keyword,
    • subkey 包含 image, retry, script, services, interruptible, tags, timeout, cache, hooks,id_tokens, artifiacts, before_script, after_script etc.
  • include: 用來定義外部引入的 yaml 檔,把主要的.gitlab-ci.yml拆成多個檔案增加可讀性或減少重複程式碼
    • subkey 包含 component, local, project, remote, template, inputs, rules
  • stages: 定義在某個或某些stages所要執行的jobs
    • subkey 包含 .pre, build, test, deploy, .post
  • workflow: 用來控制 pipeline 行為
    • subkey 包含 auto_cancel:on_new_commit, auto_cancel:on_job_failure, name, rules, etc.

避免使用global keyword的替代方案:

  • Not_recommand:
    1
    2
    3
    4
    5
    6
    7
    8
    
    default:
      image: ruby:3.0
    
    rspec-1:
      script: bundle exec rspec dir2/
    
    rspec-2:
      script: bundle exec rspec dir2/
    
  • Solution_1: 直接在每個 jobs 裡面加 configuration
    1
    2
    3
    4
    5
    6
    7
    
    rspec-1:
      image: ruby:3.0
      script: bundle exec rspec dir2/
    
    rspec-2:
      image: ruby:3.0
      script: bundle exec rspec dir2/
    
  • 在 component 之中使用 extends 關鍵字,使用獨特字以減少 component 在 merged 進 configuration 的時候可能造成的重複命名風險 .rspec-image is hidden job, as template for reducing and extending duplicate configuration.
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    .rspec-image:
      image: ruby:3.0
    
    rspec-1:
      extends:
        - .rspec-image
      script: bundle exec rspec dir2/
    
    rspec-2:
      image: ruby:3.0
      script: bundle exec rspec dir2/
    
inputs 取代 hardcoded 值

避免在 CI/CD component 裡面寫死資料,造成後續使用者要審核component內部細節之後才能使用之

hard-coded寫死相關的問題常常發生在stage關鍵字這區,如果一個component job的stage寫死,所有使用這個component的pipeline都必須定義完全相同的stge,或者覆寫configuration,用 merging method 重新定義變數的值。

比較好的方法是,使用input關鍵字來做動態component配置,讓component使用者可以自行指定所需的值。

Example: 在 component 使用 inputs.stage 當參數,使用$[[{要動態傳入的參數}]]參照傳入參數名稱

  • 以下是component配置文件:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    spec:
      inputs:
        stage:
          default: test
    ---
    unit-test:
      stage: $[[ inputs.stage ]]
      script: echo unit tests
    integration-test:
      stage: $[[ inputs.stage ]]
      script: echo integration tests
    
  • 以下是使用上述component的project:

    1
    2
    3
    4
    5
    6
    
    stages: [verify, deploy]
    
    include:
      - component: $CI_SERVER_FQDN/ywsorg/ruby/test@1.0.0
        inputs:
          stage: verify
    
inputs 取代客製的 CI/CD variables

如果要在component使用CI/CD variable,可以先評估可否改用inputs關鍵字。如果inputs是較佳解,應該避免要求user去定義custom variable來配置component。

通常inputs是明確定義在components的spec區塊,比variable有更好的驗證性(檢查格式、可維護性、文檔化,有助於預期pipeline的可靠性和穩定性)

Example:

  • component 宣告 inputs.scanner-output 要傳入變數 (默認是 json):
    1
    2
    3
    4
    5
    6
    7
    
    spec:
      inputs:
        scanner-output:
          default: json
    ---
    my-scanner:
      script: my-scan --output $[[ inputs.scanner-output ]]
    
  • 參照到這個component的project (指定用 yaml)
    1
    2
    3
    4
    
    include:
      - component: $CI_SERVER_FQDN/path/to/project/my-scanner@1.0.0
        inputs:
          scanner-output: yaml
    

但有些情況,使用CI/CD variable會比使用inputs更適合,例如:

  • 使用預定義的變數,自動配置component以符合user’s project
  • 要求user用遮碼或受保護的CI/CD variable儲存機敏資訊

Variables

CI/CD variable 可以用來:

  • 控制 job 與 pipelines 的行為
  • 儲存想要重複使用的數值
  • 避免在 .gitlab-ci.yml hardcode 數值

可以在run manual jobs的時候幫某個特定 pipeline 覆寫變數的值,或者在 manual pipeline 預填變數

如果要在 .gitlab-ci.yml 檔案裡面建立一個 CI/CD 變數,需要用 variables 關鍵字:

variables:
  GLOBAL_JEN: "A global variable"
  
job1:
  variables:
    JOB_GEM: "A Job1 variable"
  script:
    - echo "Variables are '$GLOBAL_JEN' and '$JOB_GEM'"
    
job2:
  script:
    - echo "Variables are '$GLOBAL_JEN' and '$JOB_GEM'"
  • 如果定義在檔案最上方,那這個檔案裡面所有jobs都能夠使用
  • 如果定義在job裡面,那就只有這一個job才能使用
  • 就以上例子來看:
    • job1 會印出 'Variables are 'A global variable' and 'A Job1 variable'
    • job2 會印出 'Variables are 'A global variable' and ''
如何在單一個single job裡面 skip掉不用全域變數

如果要在某一個 job 裡面 block 掉不用全域變數的話,給 variables 設定{}

variables:
  NOSY_GLOBAL_VAR: "helloIamJennie"

job1:
  variables: {}
  script:
    - echo This job does not need any global variables

機敏資料的環境變數不適合放在 yml 檔,要透過 UI 設定,也可以透過 API endpoint 新增這些變數,例如:

project-level group-level instance-level
create /project/:id/variables /group/:id/variables /admin/ci/variables

預設而言,forked出來的專案無法存取父專案的 CI/CD variables。

CI/CD variable security

有任何異動到.gitlab-ci.yml檔的 merge request,在做以下動作之前都要先仔細審核:

  • before merging the changes
  • 為了從forked project提交的 merge request,在parent project run pipeline

像是在 job script 裡面引用到 USER, PASSWORD, SECRET_VARIABLE 這類 variable 就會造成機敏資訊暴露

預防對策:

  1. job logs中,機敏訊息的variable設定都要在mask variable checkbox打勾,再update variable
  2. 限制某些受保護的branches或者受保護的tags才能使用機敏的variables (protect variable checkbox)
  3. 或者用 3rd party secret management provider 儲存取得機敏資訊
什麼是 variable type variable, 什麼是 file type variable

Variable type variable定義:

  • 一個key-value對
  • 可以作為job裡面的環境變數

通常project, group, instance的CI/CD variable預設為variable type(variable_type of env_var),但也可以設定成file type(variable_type of file)

File type variable定義:

  • 包含一個key, value 以及 file
  • 也可依當作job裡面的環境變數,如下作用
    • key: 環境變數名稱
    • value: 要存在某一個暫存檔的值,代表文件的內容
    • path to the temporary file: 暫存文件的路徑當作環境變數的值

要注意,在 GitLab 15.6 或更早之前的版本中,文件的內容當作value,而不是檔案路徑當作value

不能將定義在 .gitlab-ci.yml 檔的 CI/CD variable 設置為檔案類型的變數,有繞過的方法:

  1. 先在 variables 設置 variable type variable
  2. 接著在 job script(CICD作業)中,把這個 variable 的值寫入一個檔案 echo "$VAR_TYPE_VAR_URL" > "transformed_file.txt"
  3. 這樣需要檔案路徑的指令列工具(mytool)就能使用了
在 job scripts 使用 CI/CD variables
  • Bash, sh 或類似的 shells: ${KEY}
  • PowerShell: ${KEY} 或者 $env:{KEY}
    • 有些情況需要加quotes,如下 "-DsosposDailyUsr=$env:SOSPOS_DAILY_USR"
  • Windows Batch*: %{KEY}%
    • 如果是在 windows 環境中跑 CI/CD pipeline
    • 這類型的腳本附檔名為.bat.cmd

另外有其他以下功能

  1. 將job_A建立的variable傳遞給後續的job_B (存在副檔名為 .env 的檔案)
  2. 控制哪些jobs不需要使用.env(dotenv)裡面的環境變數
  3. 變數的value設定多個字串,以空格隔開,再用script的迴圈達到一個變數多個值(array)的效果
  4. 另外還有覆寫variable,限制哪些人才能複寫 … 其它參考網址👉 Doc

predefined CICD variables

每個 GitLab CI/CD pipeline 都可以使用預定義的變數。

在兩個不同pipeline執行階段,可以使用預定義的變數,有些是在 GitLab 創建 pipeline 時有效,可以在 job scripts 裡面使用,或者可以用來做 pipeline 配置。另外一些則是在 runners run job 的時候才有效,只能在 job scripts 使用。

在 runner 階段才有效的預定義變數,無法在 trigger-jobs 或者以下三個關鍵字內使用:

where variables can be used

可以放在兩個地方:

  1. GitLab 端,放在 .gitlab-ci.yml 檔裡
  2. GitLab Runner 端,config.toml

GitLab 內部變數的 expansion 機制

要寫成 $variable, ${variable}, 或者%variable% 這樣的格式

Pipeline security

secret 定義:password, SSH keys, access token 或其他驗證身分用的資料

secrets storage

分成兩種

  • Secrets management providers

存在 GitLab instance 以外的空間,例如 HashiCorp Vault、Azure Key Vault、Google Cloud Secret Manager 實作細節可參考:Using external secrets in CI

  • CI/CD variables

要在 CICD pipeline 儲存重複使用 data 的話,CI/CD variables是很方便的選項,但相較於前者 secrets management providers 不安全

  • variables 存在 project, group, instances 的settings,有權限存取 settings 的 user 就能訪問 variables

    [路徑] Repository / Settings / CI/CD / Variables

  • variables 可以被覆寫(這樣就很難識別哪裡的變數被用到

  • 如果 pipeline 不小心配置錯誤的話,可能會意外暴露 variable

適合存在 variables 的資訊應該是非機敏資料(不用擔心資料暴露),如果有機敏層級比較低的資料要存在 Variables,應該要遮蔽(mask variable checkbox 打勾)或保護之(protect variable checkbox 打勾,再update variable)