You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GitLab CI构建推送阶段随机失败:镜像标签不匹配问题排查

问题描述

我有4个结构相似的GitLab CI配置文件(.gitlab-ci.yml),其中一组任务的配置如下:

build database:
  stage: build
  retry: 2
  script:
    - echo 'Building the database image...'
    - docker pull ${IMAGE_PREFIX}/db:latest || true
    - docker build
        --file database/docker/Dockerfile
        --build-arg IMAGE_REGISTRY=${IMAGE_REGISTRY}
        --pull
        --cache-from ${IMAGE_PREFIX}/db:latest
        --tag ${IMAGE_PREFIX}/db:${CI_COMMIT_SHA}
        .
  rules:
    - if : '$CI_COMMIT_BRANCH == "main"'

push database:
  stage: push
  retry: 2
  script:
    - docker push ${IMAGE_PREFIX}/db:${CI_COMMIT_SHA}
    - docker rmi  ${IMAGE_PREFIX}/db:latest || true
    - docker rmi  ${IMAGE_PREFIX}/db:${CI_COMMIT_SHA}
  rules:
    - if : '$CI_COMMIT_BRANCH == "main"'

在GitLab流水线中,所有构建阶段的任务都能成功完成,但推送阶段会随机出现tag does not exist错误。添加docker images命令调试后,发现失败时本地镜像的标签与预期不符:

$ docker images
REPOSITORY                TAG                                        IMAGE ID       CREATED          SIZE
db                        d5490eeb56483b580c06f1d7a028c9a39d28eff4   a8955a972823
$ echo ${IMAGE_PREFIX}/db:${CI_COMMIT_SHA}
db:c9eaa8109b4d79d012244fccebd69c964e1469b3

已确认构建阶段使用的是正确的c9eaa8109b4d79d012244fccebd69c964e1469b3标签,但推送阶段找不到对应镜像。

问题原因分析

随机失败的核心原因是GitLab Runner的并发执行导致镜像标签冲突/覆盖,具体场景如下:

  1. 当多个流水线(或同一流水线内的多个同类任务)在使用Shell Executor的Runner上并发执行时,Runner会复用本地Docker环境。
  2. 如果多个任务的镜像名称(如db)一致,且使用不同的Commit SHA作为标签,当后一个任务的构建操作完成后,若前一个任务的推送操作延迟执行,可能出现两种情况:
    • 若Runner的工作目录未被正确清理,后续任务拉取的新代码会覆盖前一个任务的构建上下文,导致前一个任务的构建结果意外使用了新Commit的代码,标签却还是旧Commit的SHA;
    • 多个任务的镜像标签被重复使用或覆盖,导致推送时目标标签已被指向其他镜像,原镜像失去标签变为<none>(或被其他操作删除)。
  3. 另外,配置中rules部分的&quot;是HTML实体而非YAML原生双引号,可能导致规则解析异常,非目标分支的任务被意外触发,进一步加剧镜像冲突。
解决方案

针对上述问题,可通过以下步骤彻底解决:

1. 确保Runner工作目录隔离

修改GitLab Runner的配置(config.toml),启用工作目录清理:

[[runners]]
  executor = "shell"
  clean_build_dir = true

这会确保每个任务执行前,Runner都会清理并重新拉取代码,避免不同任务的上下文互相干扰。

2. 避免镜像名称冲突

为每个服务的镜像添加唯一标识,避免不同任务使用相同的镜像名:

  • 例如,在IMAGE_PREFIX中加入服务名称或项目标识,确保每个任务的镜像名唯一:
    variables:
      IMAGE_PREFIX: "registry.example.com/my-project/database"
    
  • 或者直接在标签中加入任务唯一标识,比如:
    --tag ${IMAGE_PREFIX}/db:${CI_COMMIT_SHA}-${CI_JOB_ID}
    
    这样即使并发执行,每个任务的镜像标签也不会重复。

3. 修复YAML规则中的语法错误

将rules中的HTML实体&quot;替换为YAML原生双引号:

rules:
  - if: '$CI_COMMIT_BRANCH == "main"'

避免规则解析异常导致非预期任务触发。

4. 绑定构建与推送任务到同一Runner

通过tags指定构建和推送任务在同一台Runner上执行,确保推送任务能访问到构建生成的镜像:

build database:
  # ... 其他配置 ...
  tags:
    - docker-runner

push database:
  # ... 其他配置 ...
  tags:
    - docker-runner

5. 推送前验证镜像存在性

在推送脚本中添加镜像存在性检查,避免无意义的推送失败:

push database:
  script:
    - |
      if ! docker inspect ${IMAGE_PREFIX}/db:${CI_COMMIT_SHA} > /dev/null 2>&1; then
        echo "Error: Image ${IMAGE_PREFIX}/db:${CI_COMMIT_SHA} does not exist"
        exit 1
      fi
    - docker push ${IMAGE_PREFIX}/db:${CI_COMMIT_SHA}
    - docker rmi ${IMAGE_PREFIX}/db:latest || true
    - docker rmi ${IMAGE_PREFIX}/db:${CI_COMMIT_SHA}

内容的提问来源于stack exchange,提问作者J Agustin Barrachina

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 14:52:04