1. 这不是装个软件那么简单:GitLab Runner 的真实角色与部署动机
很多人第一次看到“部署 GitLab Runner”这个标题,下意识觉得就是下载一个二进制、注册一下、启动服务——完事。我刚接触 CI/CD 时也这么想,直到被线上环境连续三天的 pipeline 卡在 “pending” 状态逼到凌晨三点翻日志,才发现自己连 runner 是什么、为什么需要它、它和 GitLab 实例之间到底在“聊”什么都没搞清楚。GitLab Runner 不是 GitLab 的附属插件,它是独立运行的执行代理(Execution Agent),是整个 CI/CD 流水线真正干活的“手”和“脚”。GitLab 本身只负责调度、编排、展示结果,而所有代码拉取、依赖安装、编译、测试、打包、镜像构建、甚至推送到生产服务器的操作,全由 Runner 在你指定的机器上完成。它不跑在 GitLab 服务器上,而是部署在你自己的基础设施里——可能是开发机、测试服务器、云主机,甚至是树莓派。这就决定了它的部署不是“一键安装”,而是一次基础设施级的权限、网络、资源与安全策略的协同配置。
核心关键词gitlab-runner、CI/CD、pipeline、gitlab-ci.yml、executor,每一个都指向一个关键环节:gitlab-runner是载体;CI/CD是目标范式;pipeline是可视化的工作流;gitlab-ci.yml是声明式指令集;而executor则是 Runner 的“肌肉类型”——它决定这双手用什么方式干活:是直接调用宿主机 shell(shell executor),还是拉起一个干净隔离的 Docker 容器(docker executor),或是连接 Kubernetes 集群(kubernetes executor)。当前热词里反复出现的 “gitlab-runner 自动化部署 dotnet8”、“docker 镜像构建与自动化部署实践”,本质上都是在选择并配置合适的 executor,再围绕它设计gitlab-ci.yml的 job 流程。比如 dotnet8 项目,若用 shell executor,就得在 runner 主机上预装 .NET SDK 8.x、dotnet cli、nuget 源配置;而用 docker executor,你只需在.yml里指定image: mcr.microsoft.com/dotnet/sdk:8.0,Runner 会自动拉镜像、启动容器、执行命令——环境完全隔离,版本精准可控,这才是现代 CI/CD 的底层逻辑。所以,“部署 GitLab Runner” 的第一课,从来不是curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash这条命令,而是先问自己:我的 pipeline 要跑什么?环境依赖有多重?是否需要多环境隔离?团队协作时权限怎么划?这些决策,直接决定了后续 executor 选型、注册方式、并发策略、缓存机制等所有细节。它不是运维的收尾工作,而是整个自动化交付链路的起点锚点。
2. 部署前必须厘清的四大核心设计维度
部署 GitLab Runner 绝非机械执行安装脚本。它是一次面向业务交付能力的架构设计,需从四个相互耦合的维度系统性规划。我见过太多团队在 pipeline 稳定运行半年后,因突然要支持 Java + Python + DotNet 三栈并行,才发现当初用 shell executor 部署的单台 runner 已成瓶颈,重装又怕影响线上发布——根源就在于部署前没做这四维推演。
2.1 Executor 类型选型:不是“能用就行”,而是“长期可维护”
Executor 是 Runner 的执行引擎,选错类型,后期改造成本极高。目前主流有五种,但实际生产中 90% 场景集中在三种:
Docker Executor:这是当前最推荐、最主流的选择。Runner 启动一个 Docker 容器作为 job 执行环境,容器镜像由
gitlab-ci.yml中的image字段指定(如mcr.microsoft.com/dotnet/sdk:8.0或node:18-alpine)。优势极其明显:环境完全隔离、依赖版本精确锁定、无需在 runner 主机预装任何语言 SDK、不同 job 互不干扰。劣势是 runner 主机必须安装 Docker daemon,且需赋予 runner 用户对/var/run/docker.sock的读写权限(安全风险需评估)。适用于绝大多数 Web 应用、微服务、前后端分离项目。Shell Executor:Runner 直接在宿主机的 shell(bash/zsh)中执行命令。部署最简单,无需额外依赖。但致命缺陷是:所有 job 共享同一套系统环境,A 项目的
npm install可能污染 B 项目的 node_modules;Python 3.9 和 3.11 无法共存;更别说 dotnet8 和 dotnet6 的全局 SDK 冲突。仅适合极简场景(如纯静态页构建、轻量脚本触发),或作为临时调试用。Kubernetes Executor:Runner 本身运行在 K8s 集群外,但每个 job 会动态创建一个 Pod 来执行。环境隔离性比 Docker 更强(Pod 级别),资源调度更精细(CPU/Memory Request/Limit),天然支持多租户隔离。但要求你已有稳定运行的 K8s 集群,并需配置 ServiceAccount、RBAC 权限、StorageClass 等。适合大型企业、多团队共享 CI 资源的场景。
提示:不要被“Kubernetes 很酷”带偏。我曾帮一家 50 人规模的 SaaS 公司评估过,他们当时只有 3 台云主机,硬上 K8s executor 导致运维复杂度飙升,最终退回 Docker executor + 多 runner 实例方案,稳定性反而提升。选型核心标准是:你的团队是否有能力持续维护该 executor 的底层依赖?
2.2 注册模式:Shared、Group 还是 Specific?权限与复用的平衡术
Runner 注册后,需绑定到 GitLab 的某个作用域,这决定了谁的 pipeline 能调用它:
Shared Runner:注册时选择 “Run untagged jobs” 并勾选 “Run for all projects”,它将出现在 GitLab 实例首页的 “Shared Runners” 列表中,所有项目(只要未禁用 shared runners)均可使用。优点是资源复用率高,运维成本低;缺点是 job 随机调度,不同项目可能抢占同一 runner 的 CPU/内存,导致构建超时或失败。适用于内部工具链、文档生成等低优先级任务。
Group Runner:注册时绑定到特定 Group(如
backend-team),该 Group 下所有子项目自动继承此 runner。权限边界清晰,资源归属明确,适合按业务线或技术栈划分的团队。例如,为frontendGroup 配置 Node.js 优化的 runner,为dotnetGroup 配置预装 SDK 8.0 的 runner。Specific Runner:注册时绑定到单一 Project(如
payment-service),仅该项目的 pipeline 可调用。隔离性最强,配置最灵活(可为每个项目定制专属 executor、tags、并发数),但管理成本最高。适用于核心支付、风控等对构建环境、安全性、稳定性要求极高的关键项目。
注意:Shared Runner 的 “untracked jobs” 并非指无 tag 的 job,而是指
.gitlab-ci.yml中未声明tags的 job。一旦你在 job 中写了tags: [dotnet],它就只会匹配带有dotnettag 的 runner。因此,tag 是实现精细化调度的核心钥匙,而非注册模式本身。
2.3 并发与资源策略:别让一台 runner 成为流水线的“堵车点”
Runner 的concurrent参数(全局并发数)和每个 job 的resource_limits(容器资源限制)共同决定吞吐能力。常见误区是认为 “并发数越大越好”。实测数据表明:一台 4C8G 的云主机,若用 Docker executor 运行 dotnet8 构建,单个 job 平均占用 2.5G 内存、1.8 核 CPU。若设concurrent = 4,当 4 个 job 同时启动,内存极易爆满,OOM Killer 会杀掉进程,导致 pipeline 失败。正确做法是:
- 压测基线:用
gitlab-runner verify或手动触发多个相同 job,观察htop中 CPU/内存峰值; - 留出余量:设定
concurrent时,按floor(可用内存 / 单 job 内存峰值 * 0.7)计算(0.7 是安全系数); - 容器限流:在
config.toml的[[runners.docker]]段落中,强制设置memory = "3g"、cpus = "2",防止单个失控 job 吃光资源。
此外,limit参数(单 runner 最大 job 数)和output_limit(单 job 日志最大行数)是防止单个长时 job 占用全部资源的保险丝。我们曾遇到一个遗留 Python 脚本因死循环打印日志,撑爆 runner 磁盘,导致所有 pipeline 挂起——启用output_limit = 4000后,该 job 自动被 kill,其他 job 不受影响。
2.4 安全与隔离边界:你的 runner 是“透明玻璃房”还是“上锁保险柜”
Runner 主机是代码执行的“物理沙盒”,其安全等级直接决定 pipeline 的可信度。关键防线有三:
Docker Socket 权限:若用 Docker executor,Runner 必须能访问
/var/run/docker.sock。直接chmod 666是重大风险,应创建专用用户组docker-runner,将gitlab-runner用户加入该组,并设置 socket 文件组权限为g+rw。这样既满足功能,又避免 root 权限滥用。作业目录隔离:默认 runner 在
/home/gitlab-runner/builds下创建工作目录。务必确保该路径所在磁盘有充足空间(建议单独挂载 SSD 分区),并设置builds_dir = "/data/gitlab-runner/builds"(指向大容量盘)。同时,在config.toml中启用clone_url = "https://your-gitlab.com"(而非http://localhost),避免 runner 主机通过内网直连 GitLab,暴露内部网络拓扑。凭证管理:Pipeline 中常需访问私有 Docker Registry、云厂商 API、数据库等。绝不可将密码明文写入
.gitlab-ci.yml。必须使用 GitLab 的CI/CD Variables(项目级或 group 级),勾选 “Mask variable” 并设置 “Protected”(仅在 protected branches 上暴露)。Runner 在执行时会自动注入环境变量,安全且可审计。
这四大维度不是孤立选项,而是交织的决策网络。例如,选 Docker executor 就必然涉及 Docker Socket 权限(安全维度);选 Group Runner 就需规划 tags 体系(注册模式维度);而并发数设定,又依赖于你为 dotnet8 job 压测出的资源基线(资源维度)。部署前花两小时画一张四维决策表,远胜于部署后花两天排查诡异超时。
3. 从零开始:Docker Executor 的完整部署与深度配置实录
以当前最主流的 Docker Executor 为例,下面是我在线上环境反复验证过的、可直接抄作业的部署流程。全程基于 Ubuntu 22.04 LTS,GitLab 版本 16.10+,目标是部署一个专用于 dotnet8 项目的 Group Runner,并支持 Docker 镜像构建与推送。所有命令均附带原理说明,拒绝黑盒操作。
3.1 环境准备与基础依赖安装
首先,确保主机已安装 Docker Engine 并正常运行。这不是 Runner 的依赖,而是 Docker Executor 的运行基石。执行以下命令验证:
# 检查 Docker 是否安装及版本(需 >= 20.10) sudo docker --version # 启动 Docker 服务并设为开机自启 sudo systemctl enable docker && sudo systemctl start docker # 验证 Docker daemon 正常响应 sudo docker info | grep "Server Version"接着,创建专用用户组与用户,避免 runner 以 root 权限运行:
# 创建 docker-runner 组,用于管理 docker socket 权限 sudo groupadd docker-runner # 创建 gitlab-runner 用户,禁止登录 shell,主目录设为 /home/gitlab-runner sudo useradd --create-home --shell /bin/bash --groups docker-runner gitlab-runner # 设置 gitlab-runner 用户密码(仅用于 sudo 操作,非必需) sudo passwd gitlab-runner关键原理:
--shell /bin/bash是为了方便后续调试时sudo -u gitlab-runner bash进入用户环境;--groups docker-runner将用户加入新组,为后续 socket 权限控制铺路。这一步看似繁琐,却是安全隔离的第一道墙。
3.2 下载、安装与服务初始化
GitLab 官方提供多种安装方式,强烈推荐使用官方 APT 仓库,因其更新及时、签名验证严格,避免手动下载二进制带来的校验风险:
# 添加 GitLab 官方 GPG key curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash # 更新 apt 缓存 sudo apt-get update # 安装 gitlab-runner(会自动安装 systemd service) sudo apt-get install gitlab-runner # 验证安装版本(确保 >= 16.10,以支持 dotnet8 的最新特性) gitlab-runner --version安装完成后,Runner 服务已注册为gitlab-runner.service,但尚未启动。此时需先完成注册,再启用服务。
3.3 注册 Runner:绑定 Group、打 Tag、设权限
注册是 Runner 与 GitLab 建立信任关系的关键步骤。你需要从 GitLab 项目或 Group 的 Settings > CI/CD > Runners 页面获取Registration Token(注意:不是 Project ID,也不是 Personal Access Token)。执行注册命令:
sudo gitlab-runner register \ --url "https://your-gitlab.com/" \ --registration-token "GR1348941zxxxxxxxxxxxxxxxxxxxxxx" \ --description "dotnet8-builder-prod" \ --tag-list "dotnet8,prod,linux" \ --run-untagged="false" \ --locked="false" \ --access-level="not_protected" \ --executor "docker" \ --docker-image "alpine:latest" \ --docker-volumes "/cache" \ --docker-privileged="false"参数详解:
--url:你的 GitLab 实例地址,必须与浏览器访问地址一致(含 https);--registration-token:从 GitLab UI 复制的 token,有效期 24 小时,务必及时使用;--description:Runner 描述,建议包含用途(dotnet8)、环境(prod)、OS(linux),便于后续识别;--tag-list:核心调度标签,用英文逗号分隔。此处dotnet8表示只运行声明了tags: [dotnet8]的 job;prod表示可用于生产环境相关 job;linux是 OS 标签,便于跨平台调度;--run-untagged="false":关闭 untagged job,强制所有 job 必须显式声明 tags,提升可追溯性;--executor "docker":明确指定 executor 类型;--docker-image "alpine:latest":为 runner 自身(非 job)指定基础镜像,alpine 轻量安全;--docker-volumes "/cache":挂载主机/cache目录到容器内,用于 job 级缓存(如 nuget packages);--docker-privileged="false":严禁开启!privileged 模式等于给 job root 权限,是严重安全漏洞。
注册成功后,会在/etc/gitlab-runner/config.toml生成配置。此时不要启动服务,先进行关键配置优化。
3.4 深度配置config.toml:超越默认的性能与安全加固
config.toml是 Runner 的心脏。默认配置仅满足基本运行,生产环境必须调整。以下是经过压测验证的核心修改项(修改前请备份原文件):
concurrent = 3 # 全局并发数,根据 4C8G 主机压测设定为 3 check_interval = 30 # 每 30 秒轮询 GitLab 获取新 job,平衡延迟与负载 [session_server] session_timeout = 1800 # Session 会话超时 30 分钟,避免长连接堆积 [[runners]] name = "dotnet8-builder-prod" url = "https://your-gitlab.com/" token = "xxx" # 自动生成,勿修改 executor = "docker" clone_url = "https://your-gitlab.com/" # 强制使用 HTTPS 克隆,避免内网暴露 limit = 10 # 单 runner 最大处理 10 个 job,防止单点过载 output_limit = 4000 # 单 job 日志上限 4000 行,防日志爆炸 [runners.cache] Type = "s3" # 启用 S3 缓存,替代默认本地缓存,提升跨 runner 一致性 Shared = true [runners.cache.s3] ServerAddress = "s3.your-company.com" AccessKey = "YOUR_S3_ACCESS_KEY" SecretKey = "YOUR_S3_SECRET_KEY" BucketName = "gitlab-runner-cache" BucketLocation = "us-east-1" [runners.docker] tls_verify = false image = "alpine:latest" privileged = false disable_entrypoint_overwrite = false oom_kill_disable = false disable_cache = false volumes = ["/cache:/cache:rw", "/data/gitlab-runner/builds:/builds:rw"] # 显式挂载 builds 目录到大容量盘 shm_size = 1024000000 # /dev/shm 大小设为 1GB,解决 dotnet build 的 tmpfs 不足问题 memory = "3g" # 单 job 容器内存上限 3GB cpus = "2" # 单 job 容器 CPU 上限 2 核 network_mode = "bridge" # 使用 bridge 网络,隔离性优于 host 模式 [runners.custom_build_dir] enabled = true # 启用自定义构建目录,配合上面的 /builds 挂载实操心得:
shm_size是 dotnet8 构建的隐形杀手。.NET 的 MSBuild 在编译大型解决方案时,会大量使用/dev/shm(tmpfs),默认 Docker 容器只有 64MB,极易触发System.IO.IOException: No space left on device。将shm_size设为 1GB 后,该错误归零。这个参数在官方文档里藏得很深,但却是 dotnet8 项目部署的必填项。
3.5 启动服务与权限固化
配置完成后,启动服务并检查状态:
# 启用服务开机自启 sudo systemctl enable gitlab-runner # 启动服务 sudo systemctl start gitlab-runner # 查看服务状态(重点关注 Active: active (running)) sudo systemctl status gitlab-runner # 查看实时日志,确认无报错 sudo journalctl -u gitlab-runner -f最关键的一步是固化 Docker Socket 权限,确保 runner 用户能安全访问:
# 将 gitlab-runner 用户加入 docker 组(如果之前没加) sudo usermod -aG docker gitlab-runner # 重启 docker 服务,使组变更生效 sudo systemctl restart docker # 验证权限:切换到 gitlab-runner 用户,尝试 list containers sudo -u gitlab-runner docker ps -q >/dev/null 2>&1 && echo "Permission OK" || echo "Permission Denied"注意:
usermod -aG docker中的-a(append)至关重要。漏掉-a会导致用户被移出其他组(如docker-runner),引发权限丢失。这是新手最常踩的坑。
3.6 验证与上线:用一个 dotnet8 pipeline 实战检验
最后,用真实的.gitlab-ci.yml验证 runner 是否就绪。创建一个极简的 dotnet8 构建 job:
# .gitlab-ci.yml stages: - build build-dotnet8: stage: build image: mcr.microsoft.com/dotnet/sdk:8.0 tags: - dotnet8 script: - dotnet --version # 输出 8.0.x,证明环境正确 - dotnet restore src/MyApp.sln - dotnet build src/MyApp.sln --configuration Release --no-restore artifacts: paths: - bin/Release/ cache: key: "$CI_COMMIT_REF_SLUG" paths: - "**/*.csproj" - "**/obj/**" - "**/bin/**"提交该文件到项目主分支,触发 pipeline。在 GitLab UI 的 CI/CD > Pipelines 页面,观察 job 状态:
- 若显示
running并在日志中看到dotnet --version输出8.0.x,说明 runner 已成功拉起容器、执行命令; - 若卡在
pending,检查 runner 状态(Settings > CI/CD > Runners),确认其为绿色(active)且Run for this project已勾选; - 若报错
permission denied on /var/run/docker.sock,回溯权限固化步骤,重点检查usermod和systemctl restart docker是否执行。
至此,一个安全、高效、专用于 dotnet8 的 Docker Executor Runner 已部署完成。整个过程约 25 分钟,但背后是数次失败后沉淀的配置逻辑。
4. Pipeline 实战:从 dotnet8 构建到 Docker 镜像自动化部署
Runner 部署只是基础设施就绪,真正的价值在于 pipeline 的编排。结合当前热词 “gitlab ci/cd中docker镜像构建与自动化部署实践”,下面给出一个完整的、生产可用的 dotnet8 项目流水线,覆盖构建、测试、镜像打包、推送、K8s 部署全流程。所有步骤均基于上一节部署的 runner,无需额外配置。
4.1 完整.gitlab-ci.yml解析:模块化、可复用、易维护
# .gitlab-ci.yml # 定义全局变量,避免重复 variables: DOTNET_VERSION: "8.0" APP_NAME: "myapp" IMAGE_REGISTRY: "registry.your-company.com" IMAGE_TAG: "$CI_COMMIT_SHORT_SHA" K8S_NAMESPACE: "prod" # 定义可复用的 job 模板 .default-job: &default-job image: mcr.microsoft.com/dotnet/sdk:$DOTNET_VERSION tags: - dotnet8 before_script: - export PATH="$PATH:/root/.dotnet/tools" - dotnet tool restore # 构建阶段:编译、测试、生成发布包 build-and-test: <<: *default-job stage: build script: - dotnet restore src/$APP_NAME.sln - dotnet build src/$APP_NAME.sln --configuration Release --no-restore - dotnet test tests/$APP_NAME.Tests.csproj --no-build --logger "trx;LogFileName=test-results.xml" artifacts: paths: - src/$APP_NAME/bin/Release/net8.0/publish/ expire_in: 1 week coverage: '/^Total.*?([0-9]{1,3})\%$/' # 镜像构建阶段:基于 publish 输出,构建轻量 Alpine 镜像 build-docker-image: stage: build image: docker:stable services: - docker:dind # 启用 Docker-in-Docker 服务 tags: - dotnet8 variables: DOCKER_DRIVER: overlay2 DOCKER_TLS_CERTDIR: "/certs" before_script: - docker info - docker login -u "$REGISTRY_USER" -p "$REGISTRY_PASSWORD" $IMAGE_REGISTRY script: - | # 构建多阶段 Dockerfile cat > Dockerfile << 'EOF' FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS base WORKDIR /app EXPOSE 80 FROM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build WORKDIR /src COPY . . RUN dotnet restore src/$APP_NAME.sln RUN dotnet publish src/$APP_NAME.sln -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=build /app/publish . ENTRYPOINT ["dotnet", "$APP_NAME.dll"] EOF - docker build -t $IMAGE_REGISTRY/$APP_NAME:$IMAGE_TAG . - docker push $IMAGE_REGISTRY/$APP_NAME:$IMAGE_TAG after_script: - docker logout $IMAGE_REGISTRY # 部署阶段:更新 K8s Deployment deploy-to-k8s: stage: deploy image: bitnami/kubectl:latest tags: - dotnet8 variables: KUBECONFIG: "/root/.kube/config" before_script: - mkdir -p /root/.kube - echo "$KUBE_CONFIG" | base64 -d > /root/.kube/config script: - kubectl config current-context - kubectl set image deployment/$APP_NAME $APP_NAME=$IMAGE_REGISTRY/$APP_NAME:$IMAGE_TAG -n $K8S_NAMESPACE - kubectl rollout status deployment/$APP_NAME -n $K8S_NAMESPACE --timeout=300s only: - main # 仅 main 分支触发部署4.2 关键环节深度拆解:为什么这样写?
services: - docker:dind:Docker-in-Docker 是在 runner 容器内启动一个嵌套的 Docker daemon,用于构建镜像。这是安全的,因为 dind 容器与宿主机 Docker daemon 隔离。DOCKER_DRIVER: overlay2是推荐的存储驱动,性能优于 aufs。多阶段构建(Multi-stage Build):Dockerfile 中
FROM ... AS build和FROM ... AS final将构建环境(含 SDK)与运行环境(仅 ASP.NET Runtime)分离。最终镜像大小从 800MB+ 降至 120MB,极大提升推送与拉取速度,减少攻击面。kubectl set image:这是 K8s 部署的原子操作,直接更新 Deployment 的 container image 字段,触发滚动更新。相比kubectl apply -f,它无需维护 YAML 文件,更简洁可靠。only: - main:严格限定部署仅在 main 分支发生,避免 feature 分支误触发生产变更。GitLab 的rules语法更强大,但only对简单场景足够清晰。
4.3 CI/CD Variables 配置:安全注入敏感信息
上述 pipeline 中的$REGISTRY_USER、$REGISTRY_PASSWORD、$KUBE_CONFIG均来自 GitLab 的 CI/CD Variables。在项目 Settings > CI/CD > Variables 中配置:
| Key | Value | Masked | Protected | Description |
|---|---|---|---|---|
| REGISTRY_USER | your-registry-username | ✅ | ✅ | 私有镜像仓库用户名 |
| REGISTRY_PASSWORD | base64 -w0 ~/.docker/config.json | jq -r '.auths."registry.your-company.com".auth'解码后的密码 | ✅ | ✅ | 镜像仓库密码,Masked 防止日志泄露 |
| KUBE_CONFIG | cat ~/.kube/config | base64 -w0 | ✅ | ✅ | K8s 集群 kubeconfig 文件 Base64 编码,Protected 确保仅在 main 分支暴露 |
实操心得:
KUBE_CONFIG的 Base64 编码是必须的,因为 YAML 文件中不能直接嵌入多行文本。kubectl会自动解码KUBECONFIG指向的文件。将 kubeconfig 存储在 Variables 中,比挂载 secret volume 更简单,且 GitLab 会自动加密存储。
4.4 效果验证与可观测性
Pipeline 运行后,可在 GitLab UI 直观看到:
- Jobs 面板:每个 job 的执行时长、日志、状态(passed/failed);
- Artifacts:
build-and-test生成的 publish 文件可下载,用于手动验证; - Environments:
deploy-to-k8sjob 会自动创建 Environment(如prod),点击可跳转到 K8s Dashboard 或查看部署详情; - Pipelines Graph:可视化展示各 stage 依赖关系,一目了然。
更重要的是,kubectl get pods -n prod应能看到新 pod 处于Running状态,且kubectl describe pod中的Image字段已更新为registry.your-company.com/myapp:abc123。至此,从代码提交到生产环境更新,全程无人工干预,真正实现自动化闭环。
5. 常见问题排查与独家避坑指南
部署和运行 GitLab Runner 的过程中,90% 的问题都集中在几个高频场景。下面是我整理的实战问题速查表,每一条都来自真实故障现场,附带根因分析与一招解决法。
5.1 Pipeline 卡在 “pending”:不是 runner 挂了,而是调度失灵
| 现象 | 根因分析 | 排查命令 | 解决方案 |
|---|---|---|---|
| 所有 job 长期 pending | Runner 未激活或 token 失效 | sudo gitlab-runner verify | 进入 GitLab UI,Settings > CI/CD > Runners,检查 runner 状态是否为灰色(inactive),点击 “Enable for this project”;若 token 过期,重新注册 |
| 部分 job pending,部分 running | job 的tags与 runner 的tag-list不匹配 | gitlab-runner list | 检查.gitlab-ci.yml中 job 的tags字段(如tags: [dotnet8])是否与 runner 注册时的--tag-list完全一致(区分大小写);runner 的--run-untagged是否为false |
| runner 状态 green,但 job 仍 pending | runner 的limit已达上限 | sudo gitlab-runner status+sudo journalctl -u gitlab-runner | grep "job limit" | 在config.toml中增大limit值,或增加 runner 实例数;检查是否有 long-running job 占用 slot |
独家技巧:
gitlab-runner list命令会显示每个 runner 的当前 job 数(如busy=2),这是判断资源是否耗尽的最快方式。比翻日志高效十倍。
5.2 Docker Executor 报错:权限、资源、网络三座大山
| 错误日志片段 | 根因 | 解决方案 |
|---|---|---|
ERROR: Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? | gitlab-runner用户未加入docker组,或docker服务未重启 | sudo usermod -aG docker gitlab-runner→sudo systemctl restart docker→sudo -u gitlab-runner docker ps验证 |
ERROR: failed to dial gRPC: unable to upgrade to tcp, received 404 | docker:dind服务启动失败,常见于DOCKER_DRIVER不匹配 | 在build-docker-imagejob 的variables中显式添加DOCKER_DRIVER: overlay2;检查宿主机docker info | grep "Storage Driver" |
ERROR: Job failed: failed to pull image ... no basic auth credentials | Docker registry 认证失败 | 确认REGISTRY_USER/REGISTRY_PASSWORDVariables 已正确配置且Masked;检查docker login命令中的 registry 地址是否与IMAGE_REGISTRY一致 |
ERROR: Job failed: command terminated with exit code 137 | OOM Killer 杀死了进程,内存不足 | 在config.toml的[runners.docker]段落中,增加memory = "3g";检查shm_size是否已设为1024000000(1GB) |
注意:exit code 137 是 Linux OOM Killer 的标志性信号,意味着进程因内存超限被强制终止。此时
dmesg -T \| grep -i "killed process"会输出具体被杀进程名。
5.3 dotnet8 特有问题:SDK、Runtime、NuGet 的三重陷阱
| 问题现象 | 根因 | 解决方案 |
|---|---|---|
dotnet --version输出7.0.x,而非8.0.x | job 使用了错误的image,如mcr.microsoft.com/dotnet/sdk:7.0 | 在.gitlab-ci.yml的 job 中,显式指定image: mcr.microsoft.com/dotnet/sdk:8.0;检查config.toml中[[runners]]的image是否覆盖了 job 的image |
error NU1102: Unable to find package Microsoft.NETCore.App.Runtime... | NuGet 源未配置,或网络无法访问api.nuget.org | 在before_script中添加dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org;或使用公司私有 NuGet 源 |
The type or namespace name 'AspNetCore' does not exist in the namespace 'Microsoft' | 项目文件(.csproj)中<TargetFramework>未设为net8.0 | 检查src/MyApp/MyApp.csproj,确认<TargetFramework>net8.0</TargetFramework>;若为多框架,需指定 ` net6.0;net |