GitLab CI CD:before_script和after_script

发布于:2019-12-29 | 分类:devops


本文记录使用before_scriptafter_script解决GitLab CI/CD流程中遇到的一个问题。

问题描述

在某个项目的CI/CD流程中有一个job是利用Python处理ExcelVBA,一旦原始VBA代码出现模态对话框例如MsgBox或者其他用户窗体甚至语法错误,那么整个流程就被堵塞了;接下来要么及时发现后手动取消任务,要么直至任务超时而被自动终止。

无论哪种方式终止任务,其后果都是该Excel进程没有被终止。于是在下一次提交任务时,由于进程占用而无法清理出现错误的临时文件(例如下面的~$example.xlsm)。

Checking out 56485a5a as alpha...
warning: failed to remove dist/example.xlsm: Invalid argument
warning: failed to remove dist/~$example.xlsm: Invalid argument
ERROR: Job failed: exit status 1

此时只能登陆GitLab Runner服务器,手动终止Excel进程。这显然不是一个可以接受的方案,尤其在多人协作的情况下。

解决过程

当然,釜底抽薪的方法是VBA中避免出现阻塞流程的模态对话框,或者Python脚本中检测是否发生阻塞。但前者难以百分百保证(例如VBA语法错误),后者有待进一步研究,所以这里给出一个事后的解决思路:

在已经发生因阻塞而异常终止pipeline的情况下,尝试去结束Excel进程;并且希望尽量少的改动,例如仅在.gitlab-ci.yml中增删几行。

既然是事后方案,我们将联想到before_scriptafter_script

  • 本次异常终止后立马使用after_script杀掉进程
  • 下一次正常提交后使用before_script杀掉进程

结束Excel进程

无论哪种方式,先把终止Excel进程的脚本写上。参考文章 1,在主makefilecclean命令中增加一个kill_excel

.PHONY: cclean
cclean: kill_excel
    @ echo "do clean work here ..."
    ...

.PHONY: kill_excel
kill_excel:
    @if [ -n "`TASKLIST | FINDSTR EXCEL.EXE`" ]; then \
        TASKKILL -F -T -IM EXCEL.EXE ; \
    fi

after_script

首先想到的自然是after_script——在主要脚本执行完毕之后杀掉Excel进程。

...
build:
  stage: build  
  script:
    - make build
  after_script:
    - make cclean
  tags:
    - xxxx
...

虽然正常情况下,即便任务失败也会继续执行after_script。但是,异常结束(手动取消、超时自动终止)的Pipeline并不会继续执行after_script定义的脚本

before_script

那就只能考虑before_script——在下次正常提交后、开始执行主脚本前进行杀掉Excel进程。

...
build:
  stage: build
  before_script:
    - make cclean
  script:
    - make build
  tags:
    - xxxx
...

但目测还是解决不了问题,因为本文描述的问题出现在准备阶段,尚未来得及执行script

通过输出日志直观了解一下job的执行流程:

Fetching changes ...
Checking out ...
Downloading artifacts ...
Run before_script ...
Run script ...
Run after_script ...

Git strategy

因此有必要学习和参考一下帮助文档 2中关于Runner更新工作目录代码的策略——GIT_STRATEGY变量:

  • clone 每个job都克隆一遍仓库,确保项目工作空间总是和仓库代码同步的,因此速度也是最慢的
  • fetch 重用项目工作空间的代码, 因此速度更快;分为两步:
    • git fetch重新获取上一个job到当前job之间的所有提交
    • git clean撤销上一个job的任何操作
  • none 同样重用工作空间,并且跳过所有git操作,因此代码不一定是最新的。它的作用在于操作artifacts,例如部署前置job产生的结果。

从上面的日志可以看出我的Runner默认的是GIT_STRATEGY="fetch",并且问题出在git clean这一步。

所以,解决思路为暂时不要git clean,改为在before_script中执行make cclean来达到撤销此前job操作的目的,同时也顺便杀掉Excel进程。

索性GitLab果然提供了git clean的开关参数GIT_CLEAN_FLAGS 3,将其设置为none即可跳过这一步。于是,最终的.gitlab-ci.yml(局部)为:

...
variables:
  GIT_CLEAN_FLAGS: none
...
build:
  stage: build
  before_script:
    - make cclean
  script:
    - make build
  tags:
    - xxxx
...

总结

  • Pipeline异常结束后并不会执行after_script
  • 结合GIT_CLEAN_FLAGSbefore_script可以解决本问题