Gitlab動(dòng)態(tài)子流水線實(shí)踐
Gitlab動(dòng)態(tài)子流水線
Gitlab的多項(xiàng)目流水線支持由一個(gè)項(xiàng)目的流水線觸發(fā)另一個(gè)項(xiàng)目的流水線,并可以在一個(gè)可視化整個(gè)流水線及流水線間的相互依賴關(guān)系,解決了項(xiàng)目間協(xié)同的問題。
Gitlab從12.7版本開始引入了父子流水線特性,在12.9版本引入動(dòng)態(tài)子流水線特性。子流水線可以根據(jù)階段順序自由地執(zhí)行,不用等待父流水線不相干的工作,配置被拆分得更小,減少了理解整體配置的認(rèn)知負(fù)擔(dān),同時(shí)由于導(dǎo)入在子流水線完成,也減少了命名空間沖突的可能,界面體驗(yàn)也有改善。
Gitlab子流水線
項(xiàng)目背景
項(xiàng)目組有一個(gè)專門用于生成自定義容器鏡像的Gitlab項(xiàng)目,該項(xiàng)目下很多目錄,每個(gè)目錄對(duì)應(yīng)于一種自定義鏡像,目錄下有Dockerfile及相關(guān)文件,通過運(yùn)行目錄下的名稱類似buildXXX.sh這樣的腳本來構(gòu)建和上傳鏡像。
為了有一個(gè)具備權(quán)威性、穩(wěn)定性、方便性的地方來構(gòu)建這些鏡像,想到了利用Gitlab動(dòng)態(tài)子流水線的特性,通過搜索項(xiàng)目里所有鏡像構(gòu)建shell腳本文件,并提取腳本里的鏡像名稱和標(biāo)簽,再生成以各鏡像名稱和標(biāo)簽命名的動(dòng)態(tài)步驟,最后由人員手動(dòng)觸發(fā)構(gòu)建鏡像。
這可能不是一個(gè)很好的方案,比如會(huì)生成很多并不需要的Job、沒有全部自動(dòng)化、缺少對(duì)鏡像間依賴關(guān)系的處理等,但也不失為一種思路,而且這些問題也可以通過進(jìn)一步分析Dockerfile之間的關(guān)系、本次變更涉及的文件等優(yōu)化解決,下面兩張圖為最終呈現(xiàn)的效果。
流水線
Job列表
整體結(jié)構(gòu)
父流水線和Gitlab官網(wǎng)提供的案例一樣,由generate-config、child-pipeline兩個(gè)Job(名稱可自定)構(gòu)成,前一個(gè)名為generate-config的Job會(huì)生成名為generated-config.yml的Gitlab流水線定義文件,該文件包含了需要?jiǎng)討B(tài)運(yùn)行的Job,通過artifacts機(jī)制傳遞給后續(xù)名為child-pipeline的Job,并觸發(fā)它。
為了演示清楚,生成generated-config.yml文件Shell腳本在下面的代碼中暫時(shí)省略掉了,下文將補(bǔ)上。
stages:
- prepare
- image
generate-config:
stage: prepare
script:
- 這里會(huì)生成generated-config.yml,暫時(shí)省略
artifacts:
paths:
- generated-config.yml
child-pipeline:
stage: image
trigger:
include:
- artifact: generated-config.yml
job: generate-config
模板
由于動(dòng)態(tài)生成的每個(gè)Job的定義都是一樣的,所以在生成的generated-config.yml中利用Gitlab流水線模板機(jī)制定義了一個(gè)Job模板,以復(fù)用和精簡(jiǎn)代碼。
下面的代碼片段來自generated-config.yml文件的最前面部分,我們可以看到定義了一個(gè)名為build_image的模板,該模板定義的job將手動(dòng)觸發(fā),并啟動(dòng)一個(gè)帶有docker、oc命令的鏡像來運(yùn)行構(gòu)建腳本,構(gòu)建腳本中首先為腳本授予執(zhí)行權(quán)限,然后進(jìn)入腳本所在目錄,最后運(yùn)行該腳本。具體腳本文件將由SCRIPT_PATH變量在每個(gè)具體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
生成配置
在生成動(dòng)態(tài)子流水線的腳本中,首先生成模板部分,創(chuàng)建并輸出到文件。
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
加下來生成每個(gè)Job,依次為查找build*.sh文件、搜索docker push并提取鏡像名稱和Tag、改為用制表符分割為兩列、遍歷每行并讀取到file和image遍歷、在循環(huán)中用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
完整定義
下面是完整的項(xiàng)目流水線定義:
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