業界已經達成共識:CI/CD 是落地實踐 DevOps 的重要手段,其本質是將軟件交付流程中的步驟自動化運行起來,從而提高軟件交付效率。隨着企業規模擴大(人員增加、產品增多等),高效構建 CI/CD 成為研發效能提升的關鍵。
極狐GitLab CI 內置於極狐GitLab 一體化平台,提供開箱即用的 CI/CD 能力,也是受眾多用户喜愛的 CI 工具之一。極狐GitLab CI 獨特的設計機制和企業級功能特性,能夠幫助企業在大規模落地 CI/CD 實踐時,提高 CI/CD 構建效率、降低 Pipeline 維護成本,同時還能保持足夠的安全合規性。
本文從 CI/CD Pipeline 的構建入手,講述極狐GitLab CI 三大方面的使用:
- 通過
template、component來縮短 Pipeline 編寫時間、提高維護性; - 通過 Runner 的“花式”玩法來滿足不同場景下的 CI/CD Pipeline 運行需求,同時降低使用成本;
- 用合規框架來保障 CI/CD Pipeline 的合規使用。
template 引用,減少代碼冗餘,增強 CI/CD 構建擴展性
在企業內部,一種很常見的場景就是:不同團隊或者不同產品線都有自己獨有的項目,每個項目都有對應的 CI/CD 流水線,隨着項目的增多,流水線的數量也會不斷增加,一個企業內部有可能存在數百甚至上千條流水線。
因為 CI/CD 流水線是軟件交付(從編碼到上線)的自動化展現形式,因此大部分流水線之間會有比較高的相似度,甚至有一些 stage 或者 job 是完全一樣的,比如在雲原生交付場景下需要將應用程序打包成鏡像,使用極狐GitLab CI 進行構建的代碼如下:
build:
image: docker:latest
stage: build
services:
- docker:20.10.7-dind
script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:1.0.0 .
- docker push $CI_REGISTRY_IMAGE:1.0.0
此外,如果都是 java 或者 golang 項目,其編譯或者測試的命令可能也是類似的。這種“重複”會隨着流水線的增加而增加,以下問題也會隨之而來:
問題 1:代碼冗餘,低效實踐
如果每一條流水線都有一個 stage 或者 job 是相似的,大約有 10 行代碼,那麼數百上千條流水線,其重複的代碼數量就是成千上萬條。這種代碼冗餘在軟件研發領域本身就是一種低效實踐,如果不及時進行重構,隨着項目的演進,就會變成技術債。
問題 2:維護性難,工作量大
在對 CI/CD Pipeline 進行優化的過程中,需要對流水線的部分內容進行改造,比如需要升級 dind 的版本,抑或為了安全地構建鏡像,將構建方式從 dind 轉向 kaniko,那麼對應的代碼就要變成:
`services:
- docker:24.0.3-dind
`
及:
build:
stage: build
image:
name: registry.jihulab.com/jh-xiaomage-devops/go-demo/kaniko:debug
entrypoint: [""]
script:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json
- >-
/kaniko/executor
--context "${CI_PROJECT_DIR}"
--dockerfile "${CI_PROJECT_DIR}/Dockerfile"
--destination "${CI_REGISTRY_IMAGE}:1.0.0"
這時就要對所有的流水線進行改造,數百條乃至上千條流水線的改造將是一個巨大的工作量,且大量代碼的“複製粘貼”過程,很難避免不出錯。
在軟件研發領域,解決冗餘代碼的重要手段就是通過抽象 + 複用:也就是將相同(或者相似)的內容抽象成模版,將模版“存儲”在某一個地方,其他地方只需要簡單引用模版即可。
對於 CI/CD Pipeline 來説也是一樣。極狐GitLab template 就是極狐GitLab CI 內置的模版引擎功能,可以將抽象之後的模版存儲在項目倉庫中,其他項目通過 include 語法就可完成 template 的引用。
極狐GitLab template 的用法比較靈活,首先需要將“製作”模版,也就是將“重複”的代碼提取出來,保存在一個 YAML 文件中。比如上面的鏡像構建內容,可以寫到一個 docker-image-build.gitlab-ci.yml 文件中。接下來使用 include 進行引用。根據模版存儲的位置不同,include 的引用有以下四種方式:
➤ local
模版位於當前項目中,使用 local 關鍵字來引用。使用語法如下:
include:
- local: '/templates/docker-image-build.gitlab-ci.yml'
➤ file
模版和項目位於同一實例,但是不同倉庫,使用 file 關鍵字來引用。使用語法如下:
include
- project: xiaomage/templates
- ref: main
file: /templates/docker-image-build.gitlab-ci.yml
➤ remote
引用遠端倉庫中的流水線,通常是不同實例之間的引用。使用語法如下:
include:
- remote: 'https://jihulab.com/xiaomage/teamplates/raw/main/docker-image-build.gitlab-ci.yml'
➤ template
極狐GitLab 內置模版的引用。極狐GitLab 根據自身多年的經驗,沉澱了眾多可以直接複用的模版,使用 template 語法即可使用。最典型的就是極狐GitLab DevSecOps 模版的引用。極狐GitLab DevSecOps 有密鑰掃描、依賴項掃描、SAST、DAST、容器鏡像掃描、模糊測試及許可證合規檢測功能,所有功能兩行代碼即可開啓。
因此,使用 template 能帶來以下收益:
收益 1:一處修改,多處生效
如果需要對流水線的內容進行優化,比如將 dind 的版本進行升級,則只需要在模版中進行修改,其他引用的地方會隨之生效,真正實現“一處修改,多處生效”,這完全避免了“一處變更,處處修改”所帶來的重複勞動,而且流水線的冗餘度也會降低。
收益 2:高效構建,簡單便捷
template 可以實現多級嵌套,也就是模版裏面引用模版。這樣做的好處就是,可以將模版的內容細粒度化,可能是一個 stage,也可能是一個 job,比如容器鏡像構建是一個模版,容器鏡像安全掃描又是一個模版。如果要對新項目構建一條流水線,則可以直接使用多個 template 進行“搭積木”的方式就可快速完成流水線的構建,然後根據項目的實際構建流程來對參數或者流程做一些變更即可。
當然,為了高效的使用模版,還有一個問題需要注意,那就是模版中變量的覆蓋。
為了靈活使用模版,使用同一套模版,構建出多個不同的實例。關鍵在於模版中變量的使用。比如,構建容器鏡像時 tag 可能會隨着版本的不同而不同,此時可以將 tag 設置為一個變量:
variables:
IMAGE_TAG: 1.0.0
build:
image: docker:latest
stage: build
services:
- docker:20.10.7-dind
script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$IMAGE_TAG .
- docker push $CI_REGISTRY_IMAGE:$IMAGE_TAG
在引用的地方直接進行變量覆蓋即可:
variables:
IMAGE_TAG: "2.0.0"
include:
- remote: 'https://jihulab.com/xiaomage/teamplates/raw/main/docker-image-build.gitlab-ci.yml'
覆蓋之後,鏡像 tag 的值從默認的 1.0.0 就變成了 2.0.0 ,這樣就能滿足不同場景的訴求,兼具高效和靈活。
Component,打造 CI/CD Pipeline 單一可信源,簡化 CI/CD Pipeline 構建
template 的使用大大降低了用户構建 CI/CD Pipeline 的難度,通過引用 + 參數覆蓋的模式就能夠快速構建出對應場景的 Pipeline,但是目前並沒有一個 template 的單一可信源,來方便用户找到自己想用的 Pipeline,同時也無法讓願意貢獻的用户將可用的 template 貢獻出來,來共同打造 Pipeline 的繁榮生態。
為此,極狐GitLab 推出了 CI/CD Component 這一特色功能,其目的就是打造 CI/CD Pipeline 的單一可信源,通過將不同流水線(或者單獨的作業)變成不同的 component,然後發佈到 component 倉庫,其他用户可以在此倉庫中通過搜索來找到自己想要的 component,在構建流水線的時候直接引用即可,多個組件的引用就能夠快速搭建起整個完整的流水線,這極大的改變了用户使用 CI/CD 的體驗。
更為重要的是用户可以將自己認為已經得到實踐的一些優秀流水線(或者單獨的作業)以 component 的形式發佈到 component 倉庫,通過不同用户的不斷貢獻、迭代來共同打造繁榮的 CI/CD Pipeline 生態,最終構建企業內部 CI/CD Pipeline 單一可信源,提高 CI/CD Pipeline 構建效率的同時,安全性也得到了很大的提升。
注意:CI/CD Component 目前處於實驗階段。
CI/CD Component 示意圖
因此,componnt 的核心是:component 倉庫的構建、 component 的發佈及 component 的引用。
Component 倉庫的構建
通過創建一個極狐GitLab 倉庫,並且將其標記為 component 倉庫來構建一個初始的 component 倉庫。該倉庫至少需要兩個文件 README.md 和 template.yml:
README.md可以對倉庫中包含的 component 進行描述,方便用户學習使用;template.yml就是 component 的具體內容。
可以通過不同的目錄結構(分支、tag 等)來實現不同 component 的區分,比如:
├── template.yml
├── README.md
├── .gitlab-ci.yml
├── forntend/
│ └── template.yml
└── backend/
└── template.yml
上述目錄表示,在此 component 倉庫中有三個可用的 component:
- 根目錄下
template.yml表示的 component; - frontend 目錄下
template.yml表示的 component; - backend 目錄下
template.yml表示的 component。
可以通過項目 → 設置 → 通用 → 可見性、項目功能、通用 → 開啓 CI/CD 目錄資源來將一個倉庫標記為 component 倉庫。
Component 的發佈
如果需要發佈一個 component,則需要將對應的內容寫入到某個 template.yml 內,然後將該文件推送至 component 倉庫即可。以上述鏡像構建為例,將下述內容寫入到某個 template.yml 中:
spec:
inputs:
stage:
default: test
image:
default: docker:latest
tags:
default: tags
image_tag:
default: 1.0.0
component-job-build-image:
image: $[[ inputs.image ]]
stage: $[[ inputs.stage ]]
tags:
- $[[ inputs.tags ]]
script:
- docker login -u "$REGISTRY_USER" -p "$REGISTRY_PWD" REGISTRY_URL
- docker build -t dllhb/cicd-component:$[[ inputs.image_tag ]] .
- docker push dllhb/cicd-component:$[[ inputs.image_tag ]]
然後推送至 jh.instance.url/username/component-project 目錄下。參數説明:
jh.instance.url:極狐GitLab 私有化部署實例地址;username:極狐GitLab 用户名;component-project:component 倉庫名稱。
上述 component 位於倉庫根目錄下,有一個 component-job-build-image job。
Component 的引用
如果想要在其他 Pipeline 中引用上述發佈的 component,在 .gitlab-ci.yml 中用如下語法引用:
include:
- component: jh.instance.url/username/component-project@main
inputs:
stage: build
image: docker:latest
tags: cicd
image_tag: 2.0.0
stages: [build]
需要注意的地方有:
- 在
include中完整寫入 component(也就是tempate.yml存在的路徑)的路徑,通過 @ 來明確引用的是 component 的哪個版本(可以用分支、commit hash、tag 等表示); - 在
inputs中寫入具體的參數。
觸發 CI/CD Pipeline,可以看到 component-job-build-image job 執行成功:
同樣地,如果想要將 dind 的構建方式換為 kaniko,則無需替換上述 component 的內容,只需要再次發佈一個以 kaniko 為主題的 component 即可。
這有很多種做法來實現,比如將 template.yml 放在 component 倉庫的另外一個目錄(非根目錄下,因為根目錄下已經有 dind 的 component)、分支、tag 下來表示這是不同的 component;比如針對上述的 component,main 分支表示 dind component,那麼可以新建一個 kaniko 分支來存放 kaniko 對應的 component,最後在 .gitlab-ci.yml 中引用的時候指明分支即可:
include:
- component: jh-jhma.gitlab.cn/cicd-component/cicd-component-demo@kaniko
inputs:
stage: build
image: gcr.io/kaniko-project/executor:debug
tags: cicd
image_tag: 2.0.0
stages: [build]
當然,鏡像也要由原來的dind改為kinako,這隻需要修改inputs的參數即可。
運行 CI/CD Pipeline 可以得到相同的結果。
component 的引入拉開了極狐GitLab CI/CD 使用新範式的序幕。這種通過用户貢獻來打造 CI/CD Pipeline 單一可信源的做法,對於用户構建完整的 Pipeline 有着巨大幫助,不僅加速了 CI/CD Pipeline 的構建,還大大降低了用户學習繁雜 YAML 語法的成本。
以上所演示的代碼均存儲在極狐GitLab 私有化部署實例上,地址為 https://jh-jhma.gitlab.cn/cicd-component。
Runner,CI/CD 高效構建的利器
Runner 是極狐GitLab CI 的一個重要組件,它能夠幫助運行 CI/CD 流水線中所定義的 Job。當研發人員提交代碼變更後,極狐GitLab 就會“通知” Runner 去按照 .gitlab-ci.yml 定義的流水線步驟完成變更代碼的構建、測試、部署等,此過程中 Runner 會根據所選擇的 executor(比如針對 PowerShell 的 shell、針對容器的 docker 等)來針對不同環境進行 Job 的運行。關於 executor 的選擇可以參考極狐GitLab 執行器官網。
Runner 像是一個“agent”,接受“server”端(極狐GitLab 實例)的請求,因此為了滿足不同場景的需求,Runner 要能夠滿足在不同 OS、不同 CPU 架構上以不同的安裝方式來運行。
專有 + 共享,更多靈活選擇
Runner 分為專有和共享兩大類:
- 專有:指 Runner 只給指定的項目用,通常是使用項目的一些信息( Runner register token)來將 Runner 註冊到對應的項目下面;
- 共享:指 Runner 是針對整個極狐GitLab 實例的,意味着整個實例下面的所有項目都可以使用這些 Runner,至於如何用、誰先用、誰後用是由極狐GitLab 的內部調度機制來實現的。
專有 Runner 的最大優勢在於:
- 節省時間:專有 Runner 只給對應的項目運行 CI/CD 流水線,因此不用排隊去等待共享 Runner 來執行 CI/CD 流水線,且隨着項目、流水線的增加,排隊將耗費大量時間;
- 自主可控:專有 Runner 是用户自己安裝在自己可控的服務器上,在使用過程中如果要對流水線過程進行 Debug 或者對 Runner 配置進行修改,甚至想獲取某些運行過程中的數據,則可以直接登陸到對應的 Runner 中進行操作。
專有 Runner 的配置信息
共享 Runner 的好處也是顯而易見的:用户無需瞭解 Runner 的過多信息,也無需自己安裝運維等。是一種比較省事的方式。
因此,用户可以根據自身的需求,來選擇不同方式的 Runner 來完成相應的 CI/CD 流水線運行。
動態擴縮容,提高資源使用率
Runner 可以和雲資源動態伸縮的特性緊密綁定,實現 Runner 的動態伸縮:當有 CI/CD 流水線需要運行的時候,Runner 使用一些資源(CPU、內存等)來執行所有 Job,當 CI/CD 流水線運行結束(成功或失敗)後,對應的資源被釋放,對環境進行恢復。
比如説,可以使用容器來運行 Runner,最典型的就是使用 Kubernetes 來運行極狐GitLab Runner。
當執行 CI/CD 時,Kubernetes 會動態創建一個 pod,pod 會根據 .gitlab-ci.yml 文件中描述的 stage 以及對應的鏡像來生成相應的容器(所有容器在一個 pod 內,共享 pod 內部資源),流水線的運行都在容器內部,當流水線運行結束,pod 會刪除,運行過程中的數據、所需的資源會被釋放。
此外,還可以使用雲廠商提供的 serverless 產品實現 Runner 的動態擴所容,提高資源的使用率。
合規流水線,助力流水線的安全合規使用
流水線使用過程中還會遇到一個場景:某一個流程是需要所有項目的流水線都必須運行的,比如在鏡像構建結束必須進行鏡像安全掃描,如果有安全漏洞就需要終止流水線的運行。這種情況下的解決方案往往是針對所有項目在流水線中使用 include 加入容器鏡像掃描環節,但是隨着項目數量的增多(成百乃至上千),這意味着巨大的重複工作量,而且無法確保操作的精準度。
而正確的解決方案就是:合規流水線。
合規流水線是極狐GitLab CI/CD 流水線內置的一個安全功能,主要是確保羣組內的所有項目都能夠運行指定的合規作業。通過在羣組級別配置好合規框架,選擇好每個項目都必須運行的合規流水線作業,則此羣組下面的所有項目都會運行此合規作業,甚至後續在該羣組下新創建的項目也會默認執行此合規作業。
合規流水線的使用,首先需要在羣組級別進行合規框架的配置。在羣組 → 設置 → 通用 → 合規框架中,選擇新建合規框架,然後填入合規框架名稱、描述、合規流水線配置(也就是合規流水線所存儲的位置),最後選擇一個背景顏色即可。
如將此合規框架設置為羣組的默認合規框架,則此羣組下面新建的項目都會默認使用此合規框架,默認運行此合規流水線,而且在項目頁面會有合規框架的標籤生成。
接着需要將合規流水線寫入到此羣組下面的一個項目(比如 Compliance-Pipeline)中。以容器鏡像構建和掃描流水線為例,在 .gitlab-ci.yml 文件中寫入如下內容:
include:
- remote: 'https://jihulab.com/xiaomage/teamplates/raw/main/docker-image-build.gitlab-ci.yml'
- template: Security/Container-Scanning.gitlab-ci.yml
後續所有的新建項目都會執行容器鏡像構建和容器鏡像掃描這兩個作業,而不是項目自帶的流水線。
如果想要項目自帶的流水線也被執行,只需要將合規流水線的內容和項目自帶流水線的內容進行合併即可。比如自帶流水線需要使用 cosgin 對打包的容器鏡像進行簽名和驗證,防止鏡像被篡改:
stages:
- singature
- verfication
image-singature:
stage: singature
tags:
- cosign
image:
name: dllhb/cosign:1.0.0
entrypoint: [""]
before_script:
- mkdir ~/.docker
- cat "$DOCKER_CRED_FILE" > ~/.docker/config.json
- cat "$COSIGN_KEY" > /tmp/cosign.key
- export COSIGN_PASSWORD="$COSIGN_PASSWORD"
script:
- cosign sign --key /tmp/cosign.key $CI_REGISTRY_IMAGE:1.0.0
image-verfication:
stage: verfication
tags:
- cosign
image:
name: dllhb/cosign:1.0.0
entrypoint: [""]
before_script:
- cat "$COSIGN_PUB" > /tmp/cosign.pub
- export COSIGN_PASSWORD="$COSIGN_PASSWORD"
script:
- cosign verify --key /tmp/cosign.pub $CI_REGISTRY_IMAGE:1.0.0
需要將上述流水線引入到合規流水線當中:
include:
- project: 'Compliance-Pipeline-Group/regular-pipeline'
file: '.gitlab-ci.yml'
最終該羣組下的其他項目的流水線都會執行容器鏡像的打包、掃描、簽名及驗證這四個步驟。
因此合規框架的選擇,是為了標記某些項目必須滿足某些合規要求或者需要額外的監督,然後通過執行合規流水線來完成合規工作的完成。