GitLab Runner从一个 Job 一个 Runner的错误用法纠正
环境:自建 GitLab、GitLab Runner 19.3.1、Linux、Docker Executor / Shell Executor 关键词:GitLab CI/CD、Runner、Tag、Docker、Shell、release、unregister、403 Forbidden、config.toml
一、背景:最初的 Runner 设计
为了让不同任务相互隔离,我最初采用了一个 Runner 对应一个 Tag,每个 Job 指定对应 Tag的方式。Java、Vue 的构建与部署,以及 AI Review,都配置了独立 Runner。
执行 gitlab-runner list,可以看到本地注册配置(已省略认证 Token):
java-deploy Executor=shell
build-runner-maven Executor=docker
vue-build Executor=docker
vue-deploy Executor=shell
ai-review Executor=docker
java-build Executor=docker
vue-test Executor=docker可以把最初的映射关系理解为:
表格中的 Tag 是为了说明旧设计的示例名称,实际 Tag 请以 GitLab 网页 Runner 配置为准。
gitlab-runner list只展示本地配置,不能证明各 Runner 的实际 Tag。
对应 .gitlab-ci.yml 可以像这样配置(示意,省略具体构建步骤):
stages:
- build
- test
- deploy
java-build:
stage: build
tags:
- java-build
script:
- mvn -B clean package -DskipTests
vue-test:
stage: test
tags:
- vue-test
script:
- npm ci
- npm run test
java-deploy:
stage: deploy
tags:
- java-deploy
script:
- echo "这里执行实际的部署脚本"这样做的好处是任务分工清晰,不同 Executor 可以按需准备环境,例如 Docker Executor 用于构建、Shell Executor 用于部署。但随着 Runner 数量增多,会出现注册项多、维护成本高、配置重复等问题。
二、理解 Runner、Tag、Job 的关系
Runner:实际领取并执行 CI/CD Job 的执行器注册对象。
Executor:Job 在哪里、以什么方式运行,比如 Docker 或 Shell。
Tag:GitLab 调度 Job 时使用的匹配条件,不是一个独立运行环境。
Job:
.gitlab-ci.yml中定义的具体任务。
一个 Runner 不必只配一个 Tag;一个 Tag 也不必只有一个 Runner。 一个 Runner 可以服务多个 Job,多个具备同样 Tag 的 Runner 也可以共同承接任务。
例如 Runner 配了 docker、java、build 三个 Tag,则 Job 指定 java、build 时可以匹配,因为 Runner 含有 Job 所要求的全部 Tag;如果 Job 还要求 gpu,则不能匹配。
需要特别注意:Tag 只是调度标签,不会改变 Executor,也不会让 Docker 构建容器自动获得宿主机部署权限。run untagged jobs 选项也会影响无 Tag Job 是否能被领取。
三、故障:在 GitLab 删除 Runner 后,unregister 返回 403
这次我准备清理已经不再使用的三个 Runner:
vue-deploy
java-build
vue-test先在 GitLab 网页中删除了对应 Runner,随后在服务器上执行:
gitlab-runner unregister --name vue-deploy java-build vue-test返回:
ERROR: Unregistering runner manager from GitLab forbidden
status=DELETE .../api/v4/runners/managers: 403 Forbidden
FATAL: failed to unregister runner为什么报错?
gitlab-runner unregister 不只是删除本地的一行配置,它会向 GitLab API 发起注销请求。对于使用 GitLab UI/API 创建、再由 Runner 认证 Token 注册的 Runner,注销操作通常涉及删除 Runner Manager。
这次服务端 Runner 已经被删除,但 /etc/gitlab-runner/config.toml 仍留有对应的本地配置。客户端继续尝试通过 API 注销,就可能得到 403,导致本地配置未被自动移除。
不过,403 本身并不能单独证明一定是服务端已删除导致;权限、认证 Token 状态等也可能触发。这里是结合“刚在 GitLab 网页删除”的操作顺序,判断本地残留配置是首要排查方向。
还有一点:上面的 --name 用法也不对。--name 只接受一个 Runner 名称,不能追加三个名称来一次注销。正常应逐条调用:
gitlab-runner unregister --name vue-deploy
gitlab-runner unregister --name java-build
gitlab-runner unregister --name vue-test但在服务端已删除的情况下,没有必要继续反复执行 unregister。
四、解决方法一:官方命令清理失效配置
首先备份本地配置:
sudo cp /etc/gitlab-runner/config.toml \
/etc/gitlab-runner/config.toml.bak确认网络连通且 GitLab 服务端运行正常后,先执行验证:
sudo gitlab-runner verifyGitLab 官方文档提供了自动清理服务端已删除 Runner 的命令:
sudo gitlab-runner verify --delete注意:该命令会修改本地 config.toml,不是按名称选择三个 Runner 删除。 执行前必须备份;如果网络异常、认证失败,先排除这些问题,以免误清理本来正常的 Runner。
验证结果:
sudo gitlab-runner list如果已经只剩下需要保留的 Runner,就完成了本地清理。-
五、解决方法二:手动精准删除指定 Runner
故障处理阶段,明确需要清理 vue-deploy、java-build、vue-test 等已在服务端删除的配置,因此手动删除对应配置块更可控。之后又进一步合并执行资源,最终只保留两种 Runner:Docker 和 Shell。
先备份:
sudo cp /etc/gitlab-runner/config.toml \
/etc/gitlab-runner/config.toml.bak打开配置文件:
sudo nano /etc/gitlab-runner/config.toml配置结构大致如下:
concurrent = 4
[[runners]]
name = "java-deploy"
url = "http://192.168.20.111"
executor = "shell"
# 其他配置(含 token)省略
[[runners]]
name = "vue-deploy"
url = "http://192.168.20.111"
executor = "shell"
# 其他配置(含 token)省略只删除目标 Runner 的整个 [[runners]] 块,包括该块内部的 [runners.docker]、[runners.cache] 等子配置(如果有),不要误删其他 Runner,也不要把有效的全局配置删掉。
保存后检查:
sudo gitlab-runner list
sudo gitlab-runner verify如果仅处理最初的三个无效 Runner,阶段性可能剩下:
java-deploy Executor=shell
build-runner-maven Executor=docker
vue-build Executor=docker
ai-review Executor=docker注意:这不是最终架构。 后续还要把构建任务、测试任务、AI Review 合并到同一个 Docker Runner,把发布任务统一交给 Shell Runner。完成迁移后再清理多余 Runner,最终以两个 Runner 为准。
Runner 通常会自动检测配置变化;为方便现场验证,也可以在确认当前没有关键 Job 运行时重启服务:
sudo systemctl restart gitlab-runner
sudo systemctl status gitlab-runner如果编辑失误,可以从备份还原,然后再检查:
sudo cp /etc/gitlab-runner/config.toml.bak \
/etc/gitlab-runner/config.toml
sudo systemctl restart gitlab-runner六、最终架构:两个 Runner,按执行方式分工
经历最初的“一个 Runner 对应一个 Tag、每个 Job 都指定专属 Tag”后,我最终把配置收敛为 两个 Runner:
这里的 Runner 名称是便于理解的逻辑名称,实际名称以你的最终 GitLab 配置为准。按你的最终设计,Docker Runner 使用
dockerTag;Shell Runner 使用shell与release两个 Tag。
架构如下:
GitLab CI/CD Pipeline
│
├── build / test / ai-review
│ │
│ tag: docker
│ │
│ Docker Runner (docker executor)
│
└── release / deploy
│
tag: release
│
Shell Runner (shell executor)6.1 为什么只需要两个 Runner?
多个 Job 可以共用一个 Runner,并不要求“一 Job 一 Runner”。只要 Runner 拥有 Job 需要的全部 Tag,并且运行环境可用,就能够领取该 Job。比如 Java 构建和 Vue 构建虽然使用不同镜像,但均可由同一个 Docker Executor Runner 执行。
要注意: tags: [shell, release] 表示 Runner 必须同时具有这两个 Tag,并非二选一。如果发布 Job 只写 release,同样能匹配具有 shell 和 release 的 Shell Runner。
6.2 GitLab Runner 配置示意
/etc/gitlab-runner/config.toml 中最终只需要保留两个 [[runners]] 块。以下是结构示例,不是可直接覆盖的完整生产配置:
concurrent = 2
[[runners]]
name = "docker-runner"
url = "http://192.168.20.111"
executor = "docker"
# token 等敏感配置省略
[runners.docker]
image = "maven:3.9-eclipse-temurin-8"
[[runners]]
name = "shell-runner"
url = "http://192.168.20.111"
executor = "shell"
# token 等敏感配置省略Runner Tag 通常在 GitLab 网页的 Runner 设置中配置。 config.toml 里的 name、executor 并不能替代 GitLab 上的 Tag 配置,也不能根据这份示例推断实际 Token 或 Runner ID。
6.3 .gitlab-ci.yml 配置示例
下面是一份展示“Docker 构建 + Shell 发布”分工的简化 CI 配置:
stages:
- build
- release
java-build:
stage: build
tags:
- docker
image: maven:3.9-eclipse-temurin-8
script:
- mvn -B clean package -DskipTests
artifacts:
paths:
- target/*.jar
vue-build:
stage: build
tags:
- docker
image: node:20-alpine
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
java-release:
stage: release
tags:
- release
dependencies:
- java-build
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
when: manual
script:
- echo "在受保护的发布 Runner 上执行部署命令"
# 正式环境请替换为经过审查的部署脚本Java 构建示例使用 Java 8 镜像;maven:3.9-eclipse-temurin-8 和 node:20-alpine 都是 Docker 镜像,不是 Java 8 自带组件。Vue 构建镜像版本应以项目真实的 Node.js 要求为准。示例为说明调度,省略了具体部署、制品校验及回滚逻辑。
注意一个容易踩的坑: 上面的 image: 对 Docker Executor 有效,但 Shell Executor 不会因此在容器内运行,它直接使用宿主机环境。Shell Runner 需要事先安装好部署脚本依赖、Docker CLI 等工具,并配置最小必要权限。
6.4 最终方案的好处与注意事项
配置少: 不再为 Java 构建、Vue 构建、测试、AI Review 分别注册 Runner。
职责明确:
docker负责容器化 CI 任务,release负责发布部署。镜像隔离: Docker Executor 的各个 Job 可以按需指定 Maven 或 Node 镜像。
避免 Tag 膨胀: 新增同类型 Job 直接复用现有 Tag,不需要重新注册 Runner。
生产安全: Shell Executor 能接触宿主机,
releaseRunner 应限制到可信项目和受保护分支/Tag,并妥善保护部署密钥。容量限制: 两个 Runner 不代表只能同时运行两个 Job;具体并发还取决于
concurrent、Runner 的limit、机器资源和构建负载。反过来,仅一个 Docker Runner 也可能形成构建排队瓶颈,需要监控再决定是否横向扩容。
6.5 迁移时的正确顺序
在 GitLab 中确认 Docker Runner 的 Tag 为
docker,Shell Runner 具有shell、release。将旧的
java-build、vue-build、vue-test、ai-review等 Job 的tags改为docker。将 Java/Vue 发布 Job 的
tags改为release(需要时也可同时写shell)。触发流水线,确认 Job 已由预期 Executor 执行,发布权限和部署脚本正常。
确认没有 Job 依赖旧 Tag,再从 GitLab 删除冗余 Runner、清理本地配置,避免 Job 长期 Pending。
执行
gitlab-runner list与gitlab-runner verify,确认本地只保留两个正常的 Runner。
七、总结
这次故障不是 Runner 程序异常,而是GitLab 服务端 Runner 生命周期与本地 config.toml 管理没有同步完成。
gitlab-runner list:看本地登记了哪些 Runner。gitlab-runner verify:验证本地 Runner 是否能与服务端交互。gitlab-runner unregister --name xxx:尝试通过 API 注销指定 Runner。gitlab-runner verify --delete:清理已删除的失效 Runner 本地配置,执行前先备份。手动编辑
/etc/gitlab-runner/config.toml:明确只需移除少数已在服务端删除的 Runner 时,更方便精准控制。
最终架构从 7 个专用 Runner 收敛为 2 个通用 Runner:Docker(docker)和 Shell(shell、release)。最终体会:Tag 是 Job 调度条件,Runner 才是执行资源;按执行环境和权限边界组织 Runner,比机械地让每个 Job 对应一个 Runner 更容易维护。
