Gitlab動態子流水線實踐
Gitlab動態子流水線
Gitlab的多項目流水線支持由一個項目的流水線觸發另一個項目的流水線,并可以在一個可視化整個流水線及流水線間的相互依賴關系,解決了項目間協同的問題。
Gitlab從12.7版本開始引入了父子流水線特性,在12.9版本引入動態子流水線特性。子流水線可以根據階段順序自由地執行,不用等待父流水線不相干的工作,配置被拆分得更小,減少了理解整體配置的認知負擔,同時由于導入在子流水線完成,也減少了命名空間沖突的可能,界面體驗也有改善。
Gitlab子流水線
項目背景
項目組有一個專門用于生成自定義容器鏡像的Gitlab項目,該項目下很多目錄,每個目錄對應于一種自定義鏡像,目錄下有Dockerfile及相關文件,通過運行目錄下的名稱類似buildXXX.sh這樣的腳本來構建和上傳鏡像。
為了有一個具備權威性、穩定性、方便性的地方來構建這些鏡像,想到了利用Gitlab動態子流水線的特性,通過搜索項目里所有鏡像構建shell腳本文件,并提取腳本里的鏡像名稱和標簽,再生成以各鏡像名稱和標簽命名的動態步驟,最后由人員手動觸發構建鏡像。
這可能不是一個很好的方案,比如會生成很多并不需要的Job、沒有全部自動化、缺少對鏡像間依賴關系的處理等,但也不失為一種思路,而且這些問題也可以通過進一步分析Dockerfile之間的關系、本次變更涉及的文件等優化解決,下面兩張圖為最終呈現的效果。
流水線
Job列表
整體結構
父流水線和Gitlab官網提供的案例一樣,由generate-config、child-pipeline兩個Job(名稱可自定)構成,前一個名為generate-config的Job會生成名為generated-config.yml的Gitlab流水線定義文件,該文件包含了需要動態運行的Job,通過artifacts機制傳遞給后續名為child-pipeline的Job,并觸發它。
為了演示清楚,生成generated-config.yml文件Shell腳本在下面的代碼中暫時省略掉了,下文將補上。
stages:
- prepare
- image
generate-config:
stage: prepare
script:
- 這里會生成generated-config.yml,暫時省略
artifacts:
paths:
- generated-config.yml
child-pipeline:
stage: image
trigger:
include:
- artifact: generated-config.yml
job: generate-config
模板
由于動態生成的每個Job的定義都是一樣的,所以在生成的generated-config.yml中利用Gitlab流水線模板機制定義了一個Job模板,以復用和精簡代碼。
下面的代碼片段來自generated-config.yml文件的最前面部分,我們可以看到定義了一個名為build_image的模板,該模板定義的job將手動觸發,并啟動一個帶有docker、oc命令的鏡像來運行構建腳本,構建腳本中首先為腳本授予執行權限,然后進入腳本所在目錄,最后運行該腳本。具體腳本文件將由SCRIPT_PATH變量在每個具體Job中提供,比如從第11行開始的名為redis-cluster:5.0.7-ocp的job,引入了build_image模板,提供了SCRIPT_PATH變量的值。
.job_template: &build_image
stage: build
when: manual
image: nexus.yourcompany.com/tools/docker-18.09.7/oc:4.6.21
script:
- chmod +x ${SCRIPT_PATH}
- cd ${SCRIPT_PATH%/*}
- /bin/sh ./${SCRIPT_PATH##*/}
redis-cluster:5.0.7-ocp:
variables:
SCRIPT_PATH: /tmp/TBzsZA42/0/docker-images/redis-cluster/build-openshift.sh
<<: *build_image
生成配置
在生成動態子流水線的腳本中,首先生成模板部分,創建并輸出到文件。
generate-config:
stage: prepare
script:
- |-
echo -e "
.job_template: &build_image
stage: build
when: manual
image: nexus.yourcompany.com/tools/docker-18.09.7/oc:4.6.21
script:
- chmod +x \${SCRIPT_PATH}
- cd \${SCRIPT_PATH%/*}
- /bin/sh ./\${SCRIPT_PATH##*/}
" > generated-config.yml
加下來生成每個Job,依次為查找build*.sh文件、搜索docker push并提取鏡像名稱和Tag、改為用制表符分割為兩列、遍歷每行并讀取到file和image遍歷、在循環中用echo命令append到文件。
generate-config:
stage: prepare
script:
- 生成模板的腳本
- |-
find -iname 'build*.sh' \
| xargs grep -ios ".*docker\s*push\s*.*/[^/\\\$]*" \
| sed 's|\.sh:|.sh\t|g' \
| sed -e 's|\([^\t]*\)\t.*\/\([^\/]*\)|\1\t\2|g' \
| while read file image; do SCRIPT_PATH="$CI_PROJECT_DIR${file:1}"; \
echo -e "
$image:
variables:
SCRIPT_PATH: "${SCRIPT_PATH}"
<<: *build_image
" \
>> generated-config.yml ; \
done
下面是生成的generated-config.yml片段。
.job_template: &build_image
stage: build
when: manual
image: nexus.yourcompany.com/tools/docker-18.09.7/oc:4.6.21
script:
- chmod +x ${SCRIPT_PATH}
- cd ${SCRIPT_PATH%/*}
- /bin/sh ./${SCRIPT_PATH##*/}
redis-cluster:5.0.7-ocp:
variables:
SCRIPT_PATH: /tmp/TBzsZA42/0/docker-images/redis-cluster/build-openshift.sh
<<: *build_image
redis-cluster:5.0.7:
variables:
SCRIPT_PATH: /tmp/TBzsZA42/0/docker-images/redis-cluster/build.sh
<<: *build_image
完整定義
下面是完整的項目流水線定義:
stages:
- prepare
- image
generate-config:
stage: prepare
script:
- |-
echo -e "
.job_template: &build_image
stage: build
when: manual
image: nexus.yourcompany.com/tools/docker-18.09.7/oc:4.6.21
script:
- chmod +x \${SCRIPT_PATH}
- cd \${SCRIPT_PATH%/*}
- /bin/sh ./\${SCRIPT_PATH##*/}
" > generated-config.yml
- |-
find -iname 'build*.sh' \
| xargs grep -ios ".*docker\s*push\s*.*/[^/\\\$]*" \
| sed 's|\.sh:|.sh\t|g' \
| sed -e 's|\([^\t]*\)\t.*\/\([^\/]*\)|\1\t\2|g' \
| while read file image; do SCRIPT_PATH="$CI_PROJECT_DIR${file:1}"; \
echo -e "
$image:
variables:
SCRIPT_PATH: "${SCRIPT_PATH}"
<<: *build_image
" \
>> generated-config.yml ; \
done
artifacts:
paths:
- generated-config.yml
child-pipeline:
stage: image
trigger:
include:
- artifact: generated-config.yml
job: generate-config