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