GitLab CI执行Docker Push随机报权限拒绝问题求助
GitLab CI Docker Push随机权限拒绝问题(并发Job触发)
问题场景
使用自托管GitLab或gitlab.com搭配自托管Runner,通过以下.gitlab-ci.yml配置构建并推送镜像:
build-prod: stage: build before_script: - cat $PROD_BACKEND_TG_ENV_FILE >> .env - docker login ${CI_REGISTRY} --username ${CI_REGISTRY_USER} --password ${CI_REGISTRY_PASSWORD} script: - make build-image - make push-image only: - prod tags: - builder-vm
当合并请求合并触发流水线,且同一Runner同时处理两个推送镜像的Job时,会随机出现如下权限拒绝错误,重新运行Job即可恢复:
$ make push-image docker push git.optochka.com:5050/fmcg/backend-tg:prod The push refers to repository [git.optochka.com:5050/fmcg/backend-tg] 8d4aa99cfdfc: Preparing 45e1587c781c: Preparing 016b1f09ba3b: Preparing b1859dd19446: Preparing 4978e78c2de8: Preparing 365ccd918307: Preparing 1bba629c69e9: Preparing 139c1270acf1: Preparing 4693057ce236: Preparing 365ccd918307: Waiting 1bba629c69e9: Waiting 139c1270acf1: Waiting 4693057ce236: Waiting denied: requested access to the resource is denied make: *** [Makefile:10: push-image] Error 1 Cleaning up project directory and file based variables 00:00 ERROR: Job failed: exit status 1
原因分析
如果你的Runner使用的是Shell Executor,多个并发Job会共享Runner主机的Docker上下文(包括~/.docker/config.json中的登录凭证)。当两个Job同时执行docker login和docker push时,后一个Job的登录操作会覆盖前一个Job的认证信息,导致前一个Job推送镜像时使用了错误的(或已被覆盖的)凭证,最终触发权限拒绝错误。
解决方案
1. 改用Docker-in-Docker(DinD)隔离环境
使用Docker Executor让每个Job在独立的Docker容器中运行,各自维护独立的Docker登录状态,彻底避免凭证冲突。
修改后的CI配置:
build-prod: stage: build image: docker:latest services: - docker:dind before_script: - cat $PROD_BACKEND_TG_ENV_FILE >> .env - docker login ${CI_REGISTRY} --username ${CI_REGISTRY_USER} --password ${CI_REGISTRY_PASSWORD} script: - make build-image - make push-image only: - prod tags: - builder-vm-dind # 对应配置了DinD的Runner标签
同时需要在Runner的config.toml中配置:
[[runners]] name = "DinD Runner" url = "https://your-gitlab-instance/" token = "your-runner-token" executor = "docker" [runners.docker] tls_verify = false image = "docker:latest" privileged = true # 必须开启,支持DinD disable_entrypoint_overwrite = false oom_kill_disable = false disable_cache = false volumes = ["/cache"] shm_size = 0
2. 为Shell Executor配置独立Docker目录
通过设置DOCKER_CONFIG环境变量,让每个Job使用自己的临时Docker配置目录,避免共享凭证文件。
修改后的CI配置:
build-prod: stage: build before_script: - export DOCKER_CONFIG=$(mktemp -d) # 创建临时目录存储当前Job的Docker配置 - cat $PROD_BACKEND_TG_ENV_FILE >> .env - docker login ${CI_REGISTRY} --username ${CI_REGISTRY_USER} --password ${CI_REGISTRY_PASSWORD} script: - make build-image - make push-image only: - prod tags: - builder-vm after_script: - rm -rf $DOCKER_CONFIG # 清理临时目录
3. 限制Runner并发数
如果暂时无法调整Executor或配置,可直接限制Runner同一时间只处理一个Job,避免并发冲突。在Runner的config.toml中修改:
concurrent = 1 # 全局并发数,或针对单个runner设置limit=1 [[runners]] # ...其他配置 limit = 1 # 该Runner最多同时执行1个Job
此方法会降低Runner利用率,仅适合资源有限或临时应急场景。
内容的提问来源于stack exchange,提问作者Sherzod
相关产品推荐
相关产品推荐

