GitLab CI/CD Components 介紹與使用
前言
你有沒有曾經想過,在部署程式碼之前,希望先檢查一下有沒有錯誤,或者程式碼格式有沒有符合規範?這時候,CI/CD 就可以派上用場。CI(持續整合)可以幫你自動檢查程式碼品質,而 CD(持續交付/部署)則可以自動化部署流程,讓你的程式碼從開發到上線都更順暢。
但是,如果你有很多專案,而且每個專案都需要寫一模一樣或非常相似的 CI/CD 腳本,這就會變成一件既繁瑣又浪費時間的事情。你可能會想:「有沒有辦法只要簡單放入某些內容,就可以套用特定的腳本?」
其實,GitLab CI/CD 就提供了這樣的功能。透過重複利用與共享配置,你可以「讓多個專案共用同一套 CI/CD 流程」,不但節省時間,還能確保所有專案都遵循相同的規範。
例如,你可以在 .gitlab-ci.yml
套用別人是先撰寫好的腳本:
1 | include: |
這樣,你的 stages
裡面就會載入 secret-detection
裡面所包含的所有 stages
,可以直接套用別人寫好的流程,再也不用每次都重頭寫一遍!
在後續文章中,我會進一步介紹 GitLab CI/CD Component 的用法以及最佳實踐,讓你更輕鬆地掌握自動化流程的威力!
CI/CD Component 是一種可重複使用的單一 Pipeline 配置單元。你可以利用這些組件來建構大型 Pipeline 的一小部分,甚至可以用它們來組合出完整的 Pipeline 配置。這些組件可以透過輸入參數進行配置,以實現更動態的行為。CI/CD Component 與其他使用 include 關鍵字引入的配置類似,但它具有以下幾個優勢:
- 可以在 CI/CD Catalog 中列出: 你可以方便地瀏覽和管理已發佈的組件。CI/CD Catalog 類似一個用於與他人存儲和分享 CI/CD Components 的地方。
- 可以以『特定版本』發佈和使用: 讓你能夠穩定地引用特定版本,避免因更新造成的問題。
- 同一專案中可以定義多個組件並一同版本管理: 更容易進行維護和升級。
除了自行創建組件外,你也可以在 CI/CD Catalog 中尋找已發佈、且符合你需求的組件,省去重頭開始設計的麻煩。
1 Basic Introduction
這個章節主要會介紹:
- 建立一個 Component 的架構
- 如何引用 Component
- Component 的版本控制
1.1 Directory structure
一個 Component Project 是一個 Gitlab project (就是一個 repository)裡面其中一個 component。所有在這個 project 底下的 components 是一起進行版本控制的,每個專案最多有 30 個 components。
那了解之後我們可以來看看如果要建立一個 Component Project 該怎麼做。GitLab Component 有嚴格的目錄結構,必須清晰且具有良好的可維護性。以下是必要的內容和建議的結構配置:
必須包含的檔案
README.md
: 一個最外層的README.md
檔案,用來詳細說明儲存庫中所有 Component 的內容和用途。可以提供每個 Component 的簡介及如何使用的說明。templates/
: 在專案根目錄下建立templates/
資料夾,裡面放置所有 Component 的配置檔案。
templates/
底下的結構
可以有兩種定義方式
單一檔案形式:每個 Component 各自一個 .yml
檔案,例如:
1 | templates/ |
子目錄形式:如果某個 Component 包含多個相關檔案,可以使用子目錄,並以 template.yml
作為進入點,子目錄中可以包含多個相關檔案,適合用於複雜的 Component。
- 可選的
README.md
如果某個 Component 的內容較複雜,可以在其目錄內加上README.md
,用於提供更詳細的說明。 - 這些子目錄中的
README.md
可以在頂層的README.md
中加上連結,讓使用者更容易找到相關資訊。
1 | templates/ |
1.2 Use a Component
如果要開始把一個 component 加入到自己的 CI/CD 配置中,可以使用 include
關鍵字,並指定 Component 的路徑,比較需要注意的是 Component 的路徑怎麼組成:
1 | include: |
Gitlab-FQDN
:- 你的 GitLab 伺服器的完整網域名稱,例如
gitlab.com
。 - 如果是同一包 component 專案可以使用
$CI_SERVER_FQDN
來代表。
- 你的 GitLab 伺服器的完整網域名稱,例如
project-path
- Component 專案的路徑,也就是你 component 所放置的 repository name。
- 如果是同一包 component 專案可以使用
$CI_PROJECT_PATH
來代表。 - 通常如果沒特別設置會是
username/project-name
。
component-name
- Component 的名稱,也就是你 component 的檔案名稱。
- 通常會是
template
底下的.yml
名稱,或是 folder 名稱。 - 例如
templates/secret-detection.yml
或是templates/secret-detection/template.yml
那就填寫secret-detection
。
specific-version
- Component 的特定版本,可以是
~latest
或是1.0.0
這樣的版本號。 - 是透過打 tag 來進行版本控制。
- 又或是使用分支名稱,如
master
來代表最新的版本。
- Component 的特定版本,可以是
warning:需要注意的是 Pipeline 裡面如果使用 include 載入的 component 都不是獨立處理的,他會把 component 裡面所有的 configuration, stages 等等都合併到原本的 pipeline 裡面,所以要注意 component 裡面的設定是否會影響到原本的 pipeline。像是你的 Pipeline 跟 Component 裡面有同名的 stages,那就會有衝突。
Notice: 如果元件需要使用到像是 Token 或是密碼等敏感數據才能運作,請務必審核 Component 的原始碼,確保不會有任何敏感數據外洩的風險。
1.3 Version Control
建議不要使用 branch
或是 SHA
來引入 component,因為很有可能使用的版本不存在,但是可以用於測試。另外需要注意的是,因為所有在這個 project 底下的 components 是一起進行版本控制的,如果該 project 底下的 component 如果有其他版本的依賴,請務必把該 component 移到專屬的 Component 專案中。
我們剛剛看到使用 include
指定 component 時可以選擇版本,版本可能來自於 tag 或是 branch,但是他們是有優先順序的:
- SHA
- 例如
e3262fdd0914fa823210cdb79a8c421e2cef79d8
提供的 SHA 是最高優先順序,如果指定了 SHA,則會忽略其他版本號。
- 例如
- tag
- 例如
1.0.0
如果指定了 tag,則會使用 tag 來引入 component。 - 請根據Semantic Versioning 2.0.0規則命名 tag。
- 例如
- branch
- 例如
master
如果指定了 branch,則會使用 branch 來引入 component。 - 如果存在與 tag 相同名稱的分支,則會優先使用 tag。
- 例如
- ~latest
- 會指到最新的
tag
版本 (Semantic Version),僅當您希望使用絕對最新的版本(可能包含重大更改)才使用。
- 會指到最新的
關於 Semantic Version:
- 對於 user 而言 最好的方式是可以自動的的使用 minor 或是 patch 版本,這樣可以確保不會有重大的更動,同時確保穩定性及最新的錯誤修復與補丁。
- 可以使用 major 或是 minor version 但是不使用 patch version。
- 使用 minor verion 像是
1.1
來引入 component,這樣可以確保不會有重大的更動,它包含了1.1.0
或1.1.9
,但不包含1.2.0
。 - 使用 major version 像是
1
或是1.
來引入最新版本,這包含1.0.0
或是1.9.9
,但不包含2.0.0
。
- 對於 component owner 而言 允許使用透過設置 major 版本發佈新功能,同時不會影響到舊有版本的使用者,從而有時間按照自己的節奏更新 Pipeline。
舉例來說,發布的版本順序可以像是這樣:
1.0.0
發布,major version1.1.0
發布,minor version2.0.0
發布,major version1.1.1
發布,patch version1.2.0
發布,minor version2.1.0
發布,minor version2.0.1
發布,patch version
在上面的範例中:
1
會使用1.2.0
,因為它是最新的 minor version。1.1
會使用1.1.1
,因為它是最新的 patch version。~latest
會使用2.1.0
,因為它是最新的 major version。
注意的是,如果你的版本是欲發佈版本,需要特別指定完整的版本名稱,像是 1.1.0-rc1
或是 1.1.0-beta
這樣的版本。
需要注意的是一個 CI/CD Component 具有與其他 component 的依賴性時,需要與其他 component 使用不同的版本管理時,就應該把它移到專屬的 Component 專案中。這樣做的目的是避免版本衝突,並且更方便地維護和更新。
2 Write a Component
接下來這邊會介紹一些高品質的 Component 所需遵循的最佳實踐:
2.1 Manage Dependencies
Component 裡面是可以引用其他 Component 的,但是請務必『仔細選擇依賴』應該遵循以下原則:
- 將依賴關係保持在最低限度,少量的重複比依賴好。
- 盡可能的使用
local
本地依賴,可以確保在多個檔案中使用相同的 Git SHA。 - 當某個 component 需要依賴於其他 component 時,請不要使用
~latest
,而是使用特定的版本號,這樣可以確保不會因為其他 component 的更新而導致問題。 - 請定期更新 component 的依賴,然後發部具有更新依賴的元件新版本。
2.2 Write a clear README
再來就是寫一個清晰的 README,這樣可以讓使用者更容易了解 component 的用途,以及如何使用。README 應該包含以下內容:
- 文件應該從這個 Project 的 components 主要提供什麼功能做介紹。
- 如果一個 Project 包含多個 Components 請使用 Table of Content 協助使用者快速跳轉到特定的元件。
- 添加
## Components
標題,並且底下再包含### Component A
等子部分。 - 在每個 component 的說明中應該要包含:
- 元件的功能說明
- 至少添加一個 YAML 的範例,展示他如何使用
- 如果元件需要輸入
inputs
請添加一個『表格』並且描述每一個input
的『名稱』、『類型』、『預設值』、『描述』。 - 如果元件有使用仁和『變數』或是『Secret』,也請務必記錄下來。
- 如果歡迎貢獻,建議使用
## Contribution
標題,並且提供如何貢獻的方式。
如果元件需要更多說明,請在元件目錄的 Markdown 檔中添加其他文件,並從主 README.md 檔案連結到該文件。例如:
1 | README.md # with links to the specific docs.md |
以下是一個範例:
更多鏈可以參考:Deploy to AWS with GitLab CI/CD component’s README.md
2.3 Test Component
強烈建議在開發工作流程中測試 CI/CD 元件,這有助於確保行為一致。可以直接在根目錄建立 .gitlab-ci.yml
並且在裡面引入 component 來進行測試。如果必要可以使用 Gitlab API來檢查 component 的行為。
下面是一個範例,每次的推送與提交,管道會測試元件是否已添加到管道中,然後可以檢查通過使用 tag 來創建發佈。
1 | include: |
2.4 Avoid Hard-code, Global Keyword
Hard-code
- 如果在 component 裡面使用其他的 component 請使用
$CI_SERVER_FQDN
,如果在元件中訪問 GitLab API,請使用$CI_API_V4_URL
,這樣可以確保元件可以在任何 GitLab 實例上運行。 - 請務必參考 Predefined Variables 來確保你的 component 可以在任何環境中運行。
Global Keyword
- 有些是 gitlab 的 default global keyword 是不支援的,請不要使用。像是
after_script
或是image
等等。 - 如果真的想要使用建議可以用
extends
來取代。
1 | # Not recommended |
2.5 Replace Hard-code with inputs
避免在 CI/CD 元件中使用 hard-code 的值。hard-code 可能會迫使 component user 需要查看 compoonent 的內部詳細資訊,並調整其 pipeline 以使用元件。通常常見的 hard-code 是 stage
名稱,如果一個 component 的 job 其 stage 名稱是 hard-code 的,那使用這個 component 的 pipeline 就必須定義完全相同的 stage 名稱。
建議使用 inputs
來指定 component user 需要的值。例如在 component 做以下設定:
1 | spec: |
引用方法
1 | stages: [verify, release] |
也可以使用 inputs
來定義 job name,我們也應該避免在 CI/CD 中對 job name 進行 hard-code,當 component user 可以自定義 job name 時,可以有效的防止與其管道中的現有名稱發生衝突。使用者還可以透過使用不同的 job name 來多次包含具有不同輸入選項的 component。
例如可以使用 inputs
允許 component user 定義特定的 job name 或是 job 的 prefix 前綴:
1 | spec: |
2.6 Replace CI/CD Variables with inputs
在 component 使用 CI/CD 變數時,請評估是否應該改用 inputs
來定義這些變數。
- 這樣可以讓 component user 輕鬆地設置這些變數,而避免要求使用者定義自定義變數來配置元件。
- 另外,使用
spec.inputs
的好處是,如果缺少必要的inputs
那 pipeline 會返回錯誤,但是如果CI/CD variables
沒有定義的話,pipeline 會繼續執行,不會有錯誤訊息。
1 | spec: |
component 使用
1 | include: |
有幾個例外狀況可以使用 CI/CD 變數:
- 使用 predefined_variables 這些變數是不需要使用
inputs
來定義的。 - 如果需要儲存敏感資訊,並且透過 masked 等方式設定。
3 Security Considerations
在專案中使用 component 之前,您應該仔細查看這些元件。使用 GitLab CI/CD 元件的風險由您自行承擔,GitLab 無法保證第三方元件的安全性。
- 審核和審查元件原始程式碼:仔細檢查代碼以確保它沒有惡意內容。
- 盡可能地減少對 credentials 或 tokens 的依賴性
- 確保 component’s source code 裡面所使用的敏感訊息是在你的期望與授權範圍內。
- 使用最小範圍的 token。
- 避免使用長期訪問的 token。
- 使用固定的版本:盡可能的使用發佈的版本,僅當你信任 component owner 時才使用 release 標籤,避免使用
~latest
。 - 安全儲存 secrets:不要將 secret 儲存在 CI/CD configuration 檔案裡。
- 使用 ephemeral (短暫的) 且 isolated (隔離) 的環境執行 runner
- 安全的處理 cache 和 artifacts:除非必要,否則不要將管道中其他 job 的 cache 或是 artifacts 傳遞給 component。
- Review component changes:當 component 有更新時,請確保裡面更動的內容。
- 仔細檢查 component 使用的 container images:確保裡面沒有任何惡意的內容。
4. Conclusion
要維護安全可信的 CI/CD component 並確保您交付給使用者的管道配置的完整性,請遵循以下最佳實踐:
- 使用雙重身份驗證 (2FA):確保所有 CI/CD 元件項目維護者和擁有者都啟用了 2FA,
- 使用 Protected branches 開啟以下設定
- 使用 Protected branches 來發佈版本,例如
master
- 可以使用 Wildcards rules 來保護 release branch。
*-stable
保護所有以-stable
結尾的分支。release/*
保護所有以release/
開頭的分支。
- 要求所有人對 Protected branches 使用 Merge Request 來進行更改。並且設定 Allowed to push and merge 為
No one
。 - 不允許強制推送。
- 使用 Protected branches 來發佈版本,例如
- 禁止使用
@latest
在你的README.md
中,這樣可以確保使用者不會因為不小心使用最新版本而導致問題。 - 定期更新 CI/CD component 的依賴,並且發佈具有更新依賴的元件新版本。