4. GitLab Runner部署与弹性伸缩

Runner 是 GitLab CI/CD 真正执行作业的“工人”。Pipeline 里写的 script 最终由某个 Runner 拉取代码、启动执行环境、运行命令并回传日志与产物。因此,Runner 的部署形态与 Executor 选型,直接决定了构建隔离性、并发能力、资源成本和运维复杂度。本节围绕三类主流 Executor 的取舍、Runner 注册与标签路由,以及基于 Kubernetes 的自动扩缩容展开。

一、三种 Executor 的定位与选型

Executor 决定“作业在什么环境里跑”。GitLab Runner 支持多种 Executor,本节聚焦最常用的三种。

Shell Executor 直接在 Runner 所在主机上以某个系统用户身份执行脚本,不创建任何隔离环境。它的优点是零额外依赖、启动最快、调试最直观,适合构建机本身已装好全部工具链的场景。缺点是作业之间共享同一台机器的文件系统、环境变量和已安装软件,一个作业污染环境会影响后续作业,且无法做到资源隔离与并发安全。它通常只用于单机、可信、低频的构建,或作为排障时的临时执行方式。

Docker Executor 为每个作业启动一个容器,在容器内执行脚本。镜像即环境,image: 指定作业镜像,services: 可挂载数据库等辅助容器。它隔离性好、环境可复现、迁移方便,是大多数中小团队的首选。代价是每次作业都要拉取镜像、启动容器,冷启动有开销;同时需要 Runner 主机安装 Docker,并注意容器内构建产物如何回传(通常通过 artifacts 或挂载卷)。

Kubernetes Executor 把每个作业调度为一个 Pod,在 K8s 集群中运行。它天然具备弹性:作业多时自动创建 Pod,空闲时回收,资源按需分配,适合大规模、波动明显的构建负载。代价是架构更复杂,需要维护集群、配置 RBAC、处理镜像拉取与缓存,且 Pod 启动比本地容器更慢。它适合已有 K8s 基础设施、追求弹性与资源利用率的团队。

选型可归纳为一条判断链:若构建机已具备完整工具链且作业量小、可信度高,用 Shell;若需要环境隔离与可复现、又不想引入集群,用 Docker;若负载波动大、已有 K8s 且追求弹性伸缩,用 Kubernetes。三者并非互斥,同一 GitLab 实例可同时注册多种 Runner,由标签决定作业去向。

GitLab Runner 三种 Executor 选型与作业路由 GitLab 实例 Pipeline 作业 按 tags 匹配 Runner 标签路由 shell / docker k8s / gpu Shell Executor 直接跑在 Runner 主机,共享文件系统与环境 适用:单机、可信、低频构建;隔离性弱 Docker Executor 每作业一个容器,image 即环境,services 辅助 适用:环境隔离、可复现;冷启动有开销 Kubernetes Executor 每作业一个 Pod,按需调度,弹性伸缩 适用:负载波动大、已有 K8s;架构复杂 弹性伸缩要点(Kubernetes Executor) 并发上限 concurrent 控制同时运行的作业数;空闲 Pod 由 idleCount / idleTime 回收; 作业高峰自动创建 Pod,低谷回收,资源按需分配,避免长期占用固定构建机。

二、Runner 注册与标签路由

安装好 Runner 二进制后,需要把它注册到 GitLab 实例,才能接收作业。注册的核心是“认证令牌 + 标签 + Executor 配置”。

首先在 GitLab 项目或群组的 Settings → CI/CD → Runners 中获取注册令牌(新版本使用 Runner 认证令牌,通过 glrt- 前缀的 token 注册)。然后在 Runner 主机执行注册命令:

gitlab-runner register \
  --url "https://gitlab.example.com/" \
  --token "glrt-xxxxxxxxxxxxxxxxxxxx" \
  --executor "docker" \
  --docker-image "alpine:3.19" \
  --description "docker-runner-01" \
  --tag-list "docker,linux,amd64" \
  --run-untagged="false" \
  --locked="false"

其中 --tag-list 是路由的关键。作业在 .gitlab-ci.yml 中用 tags: 声明所需标签,GitLab 只会把作业派发给标签匹配的 Runner。例如:

build:linux:
  stage: build
  tags: [docker, linux]
  script:
    - echo "在 Docker Runner 上构建"

deploy:prod:
  stage: deploy
  tags: [k8s, prod]
  script:
    - kubectl apply -f deploy.yaml

标签设计建议遵循“环境 + 能力 + 架构”的组合,例如 docker、k8s、gpu、arm64、prod。不要把所有 Runner 都打上相同标签,否则作业会随机落到任意 Runner,难以控制资源与安全边界。--run-untagged="false" 表示该 Runner 只接带标签的作业,避免误接无关任务。

注册完成后,Runner 配置写入 /etc/gitlab-runner/config.toml。修改并发数、镜像、缓存等参数后,执行 gitlab-runner restart 生效。可用 gitlab-runner verify 检查 Runner 与 GitLab 的连接状态,用 gitlab-runner list 查看已注册 Runner。

三、Kubernetes Executor 的自动扩缩容

Kubernetes Executor 的弹性来自“按作业创建 Pod”。当 GitLab 派发作业时,Runner 在集群中创建一个 Pod 执行脚本,作业结束后 Pod 被删除。这样构建资源随负载伸缩,无需长期维护固定数量的构建机。

关键参数在 config.toml 的 [runners.kubernetes] 段中配置:

concurrent = 20

[[runners]]
  name = "k8s-runner"
  executor = "kubernetes"
  [runners.kubernetes]
    namespace = "gitlab-runner"
    image = "alpine:3.19"
    privileged = false
    cpu_request = "500m"
    cpu_limit = "2"
    memory_request = "512Mi"
    memory_limit = "2Gi"
    service_account = "gitlab-runner"
    poll_timeout = 600
    [runners.kubernetes.node_selector]
      "kubernetes.io/arch" = "amd64"

其中 concurrent 是全局并发上限,控制同时运行的作业数,防止集群被瞬间打满;cpu_request 与 memory_request 决定 Pod 调度所需的最小资源,cpu_limit 与 memory_limit 限制单作业资源上限,避免某个作业耗尽节点。若集群开启了 Cluster Autoscaler,当 Pod 因资源不足无法调度时,会自动扩容节点;作业结束后节点回收,形成“Pod 弹性 + 节点弹性”的两级伸缩。

缓存方面,K8s Executor 支持将缓存上传到对象存储(如 S3、MinIO),通过 [runners.cache] 配置,使不同 Pod 之间共享依赖缓存,减少重复下载。日志与产物则通过 GitLab 回传,无需本地持久化。

若作业需要 Docker-in-Docker 构建镜像,需在 K8s Executor 中启用 privileged = true 或配置 Kaniko、Buildah 等无守护进程方案。生产环境优先选择 Kaniko,避免特权容器带来的安全风险。

四、部署与验证的闭环

部署完成后,应通过一次真实 Pipeline 验证路由与执行是否符合预期。观察点包括:作业是否被正确标签的 Runner 接收、Pod 是否按预期创建与回收、日志是否完整回传、缓存是否命中。若作业一直处于 pending,通常是标签不匹配或 Runner 未上线;若 Pod 创建失败,检查 RBAC、命名空间与镜像拉取密钥;若并发上不去,检查 concurrent 与集群资源配额。把这些观察点与配置参数对应起来,才能让 Runner 从“能跑”走向“跑得稳、扩得快”。