深圳幻海软件技术有限公司 欢迎您!

Gitlab动态子流水线实践

2023-02-28

Gitlab动态子流水线Gitlab的多项目流水线支持由一个项目的流水线触发另一个项目的流水线,并可以在一个可视化整个流水线及流水线间的相互依赖关系,解决了项目间协同的问题。Gitlab从12.7版本开始引入了父子流水线特性,在12.9版本引入动态子流水线特性。子流水线可以根据阶段顺序自由地执行,不

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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

模板

由于动态生成的每个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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

生成配置

在生成动态子流水线的脚本中,首先生成模板部分,创建并输出到文件。

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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

加下来生成每个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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

下面是生成的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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

完整定义

下面是完整的项目流水线定义:

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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.