1. 为什么在Windows上部署GitLab Runner不是“凑合用”,而是生产级刚需?
GitLab Runner 在 Windows 上从来不是 Linux 环境下的附属品或降级替代方案——它是 .NET 生态、WPF/WinForms 桌面应用、PowerShell 自动化脚本、Windows Server 托管服务、甚至混合架构 CI/CD 流水线中不可绕过的执行节点。我从 2018 年起就在金融客户现场部署过 37 台 Windows Runner,覆盖从 Windows Server 2012 R2 到 Windows 11 的全版本矩阵,踩过所有你能想到的坑:注册失败、权限卡死、config.toml 被 PowerShell 自动转义、Docker Desktop 与 WSL2 冲突导致 executor 启动超时、Git 凭据管理器静默劫持 SSH 密钥、Windows 安全日志里刷屏的“Access Denied”事件 ID 4625……这些都不是配置错误,而是 Windows 系统层与 GitLab Runner 架构之间真实存在的张力。
核心关键词Windows、GitLab-Runner、gitlab-runner register、config.toml、PowerShell,每一个都指向一个具体战场:
- Windows不是“能跑就行”的通用平台,它有 UAC、SID、服务账户上下文、NTFS ACL、Windows Defender 实时扫描、组策略限制、PowerShell ExecutionPolicy 等一整套独立于 POSIX 的安全与执行模型;
- GitLab-Runner在 Windows 下默认使用
shellexecutor(调用 PowerShell 或 CMD),而非 Linux 常见的docker或kubernetes,这意味着你的构建脚本必须原生适配 Windows 运行时,不能简单复制粘贴.gitlab-ci.yml里的apt-get install; - gitlab-runner register是整个链路的起点,但它的交互式流程在 Windows Server 无桌面环境下极易中断——你无法靠鼠标点下一步,必须预设 token、URL、executor 类型、tags 等参数,否则注册过程会卡在“Please enter the gitlab-ci coordinator URL”之后的空白等待;
- config.toml是 Runner 的心脏文件,但它在 Windows 上极易被破坏:记事本保存为 UTF-16 BOM 编码、PowerShell
Set-Content默认写入 Unicode(而非 UTF-8)、反斜杠路径未转义、布尔值写成"true"字符串而非true原生类型——任何一项都会触发invalid type: string "true", expected a boolean这类报错; - PowerShell不是“比 CMD 高级一点的命令行”,它是 Windows 原生自动化引擎,支持对象管道、深度系统集成、证书管理、远程会话、模块签名验证,但它的 ExecutionPolicy 默认为
Restricted,gitlab-runner.exe register调用的powershell.exe -Command会直接失败,而你根本看不到错误日志——因为错误被吞掉了。
所以这不是一篇“如何安装 GitLab Runner”的教程,而是一份基于 6 年 Windows CI/CD 实战沉淀的操作手册。它面向三类人:
- 正在为 .NET 8 Web API 项目搭建 CI/CD 的后端工程师,你需要让
dotnet test在 Windows Runner 上稳定通过,而不是每次提交都看到The term 'dotnet' is not recognized; - 负责维护 Windows Server 2016/2019 生产环境的运维同学,你得让 Runner 作为 Windows Service 持续运行,不因用户登出、UAC 提示或计划任务休眠而中断;
- 正在调试
chatgpt can't load config.toml类错误的开发者——注意,这不是 ChatGPT 的问题,而是你在编辑config.toml时用了 VS Code 的“自动格式化”插件,把concurrent = 4改成了concurrent = "4",导致 TOML 解析器崩溃。
下面所有内容,全部来自真实生产环境截图、日志片段、PowerShell 调试记录和 config.toml 版本对比。没有理论推演,只有可复现、可验证、可审计的操作路径。
2. 整体部署设计:为什么放弃 Docker for Windows,坚持原生 Windows Runner?
很多人第一反应是:“既然 GitLab Runner 支持 Docker executor,那我在 Windows 上装 Docker Desktop,再跑一个 Linux 容器不就完了?”——这是最危险的幻觉。我见过太多团队掉进这个坑:他们成功注册了 Docker executor,CI 流水线跑通了npm install和python -m pytest,结果上线前发现 .NET Framework 4.8 应用根本无法在 Alpine Linux 容器里编译,msbuild.exe找不到,nuget restore报错Could not load file or assembly 'System.Security.Principal.Windows'。更糟的是,当你要调试 Windows 专属问题(比如 COM 组件调用、Active Directory 认证、Windows 服务安装)时,容器里连reg query都执行不了。
因此,我们采用原生 Windows Runner + shell executor的组合,这是唯一能 100% 复现生产环境的方案。它的技术栈非常清晰:
GitLab Server (SaaS 或自建) ↓ HTTPS / Token 认证 GitLab Runner (Windows 二进制服务) ↓ 调用本地 PowerShell 进程 PowerShell Core 7.x 或 Windows PowerShell 5.1 ↓ 加载 .NET SDK / Node.js / Java 等工具链 构建脚本 (.gitlab-ci.yml → PowerShell 脚本块)这个链路的优势在于:
- 零抽象泄漏:
dotnet build直接调用本机dotnet.exe,路径、环境变量、注册表设置、证书存储全部真实; - 调试可见性:你可以直接登录 Windows 主机,用
Get-Process gitlab-runner查看进程树,用Get-EventLog -LogName Application -Source "gitlab-runner"查看服务日志,用Get-ChildItem C:\GitLab-Runner\cache检查缓存目录权限; - 权限可控:Runner 服务账户可以精确配置为
NT AUTHORITY\SYSTEM(最高权限)或自定义域账户(最小权限原则),而 Docker 容器的--user参数在 Windows 上根本不起作用; - 资源开销极低:一个 Windows Runner 进程内存占用约 80–120 MB,而 Docker Desktop + WSL2 + Ubuntu 容器组合常驻内存 1.2 GB 起步,对 4 核 8GB 的 CI 服务器是巨大浪费。
当然,它也有代价:你必须亲手管理所有依赖工具的安装与路径。但这恰恰是 Windows CI/CD 的真相——没有魔法,只有确定性。我们不会用 Chocolatey 一键安装所有东西,因为choco install nodejs --version 18.17.0可能下载到带病毒签名的第三方包;我们也不会用 Scoop,因为它的scoop install git默认把 Git 安装在C:\Users\XXX\scoop\apps\git\current\bin,而 Runner 的$env:PATH默认不包含该路径。我们必须手动控制每一个字节。
所以整体设计分四层:
- 系统层:Windows Server 2019 标准镜像(非 Desktop Experience),关闭 Windows Update 自动重启,禁用 Windows Defender 实时扫描对
C:\GitLab-Runner目录; - Runner 层:下载官方
gitlab-runner-windows-amd64.exe,重命名为gitlab-runner.exe,放入C:\GitLab-Runner\,以服务方式安装; - 工具链层:手动安装 .NET SDK 8.0.100、Node.js 18.17.0、Git for Windows 2.42.0,全部勾选“Add to PATH”,并验证
where dotnet、where node、where git输出正确路径; - 配置层:手写
config.toml,禁用所有自动格式化插件,用 Notepad++ 设置编码为 UTF-8 无 BOM,用Test-Path验证所有路径存在,用ConvertFrom-Json (Get-Content .gitlab-ci.yml | Out-String)预检流水线语法。
这不是偷懒的方案,而是经过 37 台服务器、217 次故障复盘后,唯一能保证 SLA 99.95% 的路径。
3. 核心细节解析:PowerShell 是钥匙,也是锁
PowerShell 在整个部署中扮演双重角色:既是 Runner 的执行引擎,又是配置与调试的唯一入口。理解它的行为模式,是避免 80% 问题的前提。
3.1 PowerShell ExecutionPolicy:不是“关掉就行”,而是“精准放行”
当你运行gitlab-runner register时,Runner 内部会调用powershell.exe -Command来执行某些初始化操作(比如创建缓存目录、测试 Git 连接)。如果 ExecutionPolicy 是AllSigned或RemoteSigned,而 Runner 的二进制文件没有数字签名,PowerShell 就会静默拒绝执行,gitlab-runner进程卡住,CPU 占用 0%,日志里什么也不写。
正确的做法不是全局设为Unrestricted(这等于打开后门),而是为 Runner 创建专用策略:
# 以管理员身份运行 $runnerPath = "C:\GitLab-Runner\" $policyScope = "CurrentUser" # 或 "LocalMachine",按需选择 # 创建专用执行策略作用域 if (-not (Test-Path "$runnerPath\Policy")) { New-Item -ItemType Directory -Path "$runnerPath\Policy" -Force } # 生成签名脚本(用于后续注册) $registerScript = @" param($url, $token, $tags) & "$runnerPath\gitlab-runner.exe" register ` --url "$url" ` --registration-token "$token" ` --executor "shell" ` --description "win-server-prod" ` --tag-list "$tags" ` --run-untagged="false" ` --locked="false" ` --access-level="not_protected" "@ Set-Content -Path "$runnerPath\Policy\register.ps1" -Value $registerScript -Encoding UTF8 # 为该目录设置 RemoteSigned(只允许本地脚本执行) Set-ExecutionPolicy RemoteSigned -Scope $policyScope -Force # 验证 Get-ExecutionPolicy -Scope $policyScope # 应输出 RemoteSigned关键点在于:RemoteSigned允许本地未签名脚本执行,但阻止从互联网下载的脚本,既满足 Runner 需求,又不降低安全基线。而Set-ExecutionPolicy Unrestricted -Force是绝对禁止的——它会让任何钓鱼邮件里的.ps1文件双击即执行。
3.2 config.toml 的编码与格式:UTF-8 无 BOM 是铁律
这是chatgpt can't load config.toml类错误的根源。VS Code 默认保存为 UTF-8 with BOM,Notepad 默认保存为 ANSI(Windows-1252),而 GitLab Runner 的 TOML 解析器(BurntSushi/toml)严格要求 UTF-8 无 BOM。BOM 是三个字节EF BB BF,它在文件开头不可见,但会导致解析器报错invalid character 'ï' looking for beginning of value。
验证方法(PowerShell):
# 查看文件开头字节 (Get-Content "C:\GitLab-Runner\config.toml" -Encoding Byte -TotalCount 3) | ForEach-Object { $_.ToString("X2") } # 正常应输出:23 5B 73 —— 即 "#[s"(TOML 注释开头) # 如果输出 EF BB BF,则说明有 BOM,必须重存修复步骤:
- 用 Notepad++ 打开
config.toml; - 菜单栏 → 编码 → 转为 UTF-8 无 BOM;
- 保存;
- 用上述 PowerShell 命令确认前三字节为
23 5B 73。
同时,TOML 语法有严格要求:
- 布尔值必须是
true/false,不能是"true"; - 数字不能加引号,
concurrent = 4正确,concurrent = "4"错误; - 路径中的反斜杠必须双写或改用正斜杠:
cache_dir = "C:\\GitLab-Runner\\cache"或cache_dir = "C:/GitLab-Runner/cache"; - 注释必须独占一行,
executor = "shell" # use powershell是非法的,必须拆成两行。
一份生产可用的config.toml片段如下(已脱敏):
concurrent = 4 check_interval = 3 [session_server] session_timeout = 1800 [[runners]] name = "win-server-prod-01" url = "https://gitlab.example.com/" token = "gr1234567890abcdef1234567890abcdef1234567890abcdef" executor = "shell" shell = "powershell" environment = ["GIT_SSL_NO_VERIFY=1", "NODE_ENV=production"] output_limit = 4096 [runners.cache] type = "file" path = "cache" [runners.custom_build_dir] enabled = true [runners.shell] before_script = ["Write-Host 'Runner initialized at $(Get-Date)' -ForegroundColor Green"]注意shell = "powershell"这一行——它告诉 Runner 使用 PowerShell 而非 CMD,这样你就能在.gitlab-ci.yml中直接写pwsh命令,无需cmd /c包裹。
3.3 Windows Service 安装:不要用--service参数,要手写服务描述
gitlab-runner install --service是个陷阱。它会创建一个名为gitlab-runner的服务,但服务描述为空,启动类型为“自动(延迟启动)”,且服务账户固定为LocalSystem。在企业环境中,LocalSystem权限过高,审计不通过;“延迟启动”会导致服务器重启后 Runner 启动慢于 SQL Server 或 IIS,造成 CI 流水线首阶段超时。
我们改用手动注册服务,完全控制每个参数:
# 以管理员身份运行 $runnerExe = "C:\GitLab-Runner\gitlab-runner.exe" $serviceName = "GitLab-Runner-Prod" $displayName = "GitLab Runner for Production Builds" $description = "Executes CI/CD jobs for .NET 8 and PowerShell pipelines" # 创建服务(使用自定义域账户,假设用户名为 DOMAIN\svc-gitlab) & $runnerExe install ` --service-name "$serviceName" ` --service-display-name "$displayName" ` --service-description "$description" ` --service-user "DOMAIN\svc-gitlab" ` --service-password "YourSecurePassword123!" ` --working-directory "C:\GitLab-Runner" # 设置服务启动类型为自动 Set-Service -Name "$serviceName" -StartupType Automatic # 启动服务 Start-Service -Name "$serviceName" # 验证 Get-Service -Name "$serviceName" | Select-Object Name, Status, StartType # 应输出:GitLab-Runner-Prod Running Automatic关键参数说明:
--service-user:指定域账户,该账户必须有“以服务方式登录”权限(通过secpol.msc→ 本地策略 → 用户权限分配配置);--working-directory:设置服务工作目录,确保 Runner 在此目录下读写config.toml和cache;--service-description:提供审计所需的服务描述,不能留空。
提示:域账户密码明文出现在命令行中?不安全。实际生产中,我们用
Get-Credential获取凭据对象,再通过ConvertFrom-SecureString加密存储,启动服务时解密。但为简化本文,此处展示基础命令。
3.4 .NET 8 自动化部署的实操陷阱:SDK vs Runtime,Global.json 优先级
.gitlab-ci.yml中写dotnet build看似简单,但在 Windows Runner 上,它可能调用错误的 SDK 版本。原因在于 .NET 的多版本共存机制:dotnet.exe本身是宿主,它根据当前目录下的global.json文件决定使用哪个 SDK 版本;如果没有global.json,则使用最新安装的 SDK。
我们遇到的真实案例:客户项目根目录有global.json指定"sdk": {"version": "6.0.400"},但 Runner 机器上只装了 .NET 8 SDK。结果dotnet build报错Could not resolve SDK version '6.0.400',而dotnet --list-sdks显示8.0.100 [C:\Program Files\dotnet\sdk]。
解决方案不是卸载 .NET 8,而是让 Runner 强制使用指定版本:
# .gitlab-ci.yml build: stage: build script: - pwsh -Command "Write-Host 'Using .NET SDK 8.0.100'; & 'C:\Program Files\dotnet\dotnet.exe' build -c Release" artifacts: - bin/**/*更优雅的方式是,在 Runner 的config.toml中设置环境变量:
[[runners]] # ... 其他配置 environment = ["DOTNET_ROOT=C:\\Program Files\\dotnet", "PATH=C:\\Program Files\\dotnet;C:\\Windows\\System32"]这样所有 job 都会继承该 PATH,dotnet命令自然指向正确位置。
注意:
DOTNET_ROOT必须是dotnet.exe所在目录的父目录(即C:\Program Files\dotnet),而不是dotnet.exe的完整路径。这是 .NET 宿主的硬性约定。
4. 实操全流程:从零开始,每一步都有日志验证
以下是一个完整的、可逐行复制的部署流程。所有命令均在 Windows Server 2019(1809)上实测通过,时间戳为 2024-06-15。
4.1 环境准备:系统加固与依赖安装
第一步,关闭干扰项:
# 以管理员身份运行 # 关闭 Windows Defender 实时扫描(仅对 Runner 目录) Add-MpPreference -ExclusionPath "C:\GitLab-Runner" # 禁用 Windows Update 自动重启(防止 CI 过程中突然重启) New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "NoAutoRebootWithLoggedOnUsers" -Value 1 -PropertyType DWORD -Force # 禁用服务自动重启(避免 Runner 服务异常退出后无限循环) sc.exe failure "gitlab-runner" reset= 0 actions= restart/60000/restart/60000/restart/60000第二步,安装核心工具(全部手动下载,校验 SHA256):
- .NET SDK 8.0.100:从 https://dotnet.microsoft.com/download/dotnet/8.0 下载
dotnet-sdk-8.0.100-win-x64.exe,SHA256a1b2c3...(真实值略),静默安装:Start-Process -FilePath ".\dotnet-sdk-8.0.100-win-x64.exe" -ArgumentList "/quiet", "/norestart" -Wait - Git for Windows 2.42.0:从 https://git-scm.com/download/win 下载
Git-2.42.0-64-bit.exe,安装时勾选:- “Use Git from Windows Command Prompt”(确保 CMD 和 PowerShell 都能用)
- “Enable file system caching”(加速大仓库 clone)
- “Enable symbolic links”(如需 submodule)
Start-Process -FilePath ".\Git-2.42.0-64-bit.exe" -ArgumentList "/VERYSILENT", "/NORESTART", "/DIR=C:\Program Files\Git" -Wait - Node.js 18.17.0:从 https://nodejs.org/dist/v18.17.0/ 下载
node-v18.17.0-x64.msi,静默安装:msiexec /i "node-v18.17.0-x64.msi" /quiet /norestart INSTALLDIR="C:\Program Files\nodejs"
验证安装:
# 检查所有工具是否在 PATH 中 $tools = @("dotnet", "git", "node", "npm") foreach ($tool in $tools) { $path = where.exe $tool 2>$null if ($path) { Write-Host "$tool found at $path" -ForegroundColor Green } else { Write-Host "$tool NOT FOUND" -ForegroundColor Red exit 1 } } # 输出应为: # dotnet found at C:\Program Files\dotnet\dotnet.exe # git found at C:\Program Files\Git\cmd\git.exe # node found at C:\Program Files\nodejs\node.exe # npm found at C:\Program Files\nodejs\npm.cmd4.2 Runner 下载与注册:跳过交互,全程脚本化
下载 Runner 二进制:
# 创建目录 New-Item -ItemType Directory -Path "C:\GitLab-Runner" -Force # 下载(使用官方 CDN) Invoke-WebRequest -Uri "https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-windows-amd64.exe" -OutFile "C:\GitLab-Runner\gitlab-runner.exe" # 验证签名(可选但推荐) Get-AuthenticodeSignature "C:\GitLab-Runner\gitlab-runner.exe" | Format-List # 应显示 Publisher: "GitLab B.V.",Status: "Valid"注册 Runner(关键:预填所有参数,避免交互):
# 从 GitLab 项目 Settings → CI/CD → Runners 页面获取 URL 和 Token $gitlabUrl = "https://gitlab.example.com/" $registrationToken = "gr1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7" $runnerTags = "windows,dotnet8,powershell" # 执行注册(所有参数内联,无交互) & "C:\GitLab-Runner\gitlab-runner.exe" register ` --url "$gitlabUrl" ` --registration-token "$registrationToken" ` --executor "shell" ` --shell "powershell" ` --description "win-server-prod-01" ` --tag-list "$runnerTags" ` --run-untagged="false" ` --locked="false" ` --access-level="not_protected" ` --non-interactive # 验证注册成功(检查 config.toml 是否生成) if (Test-Path "C:\GitLab-Runner\config.toml") { Write-Host "Runner registered successfully" -ForegroundColor Green } else { Write-Host "Registration failed: config.toml not found" -ForegroundColor Red exit 1 }注意:
--non-interactive是关键开关,它强制 Runner 跳过所有Read-Host提示。没有它,脚本会在无头服务器上永远等待输入。
4.3 config.toml 深度定制:缓存、超时、安全加固
生成初始config.toml后,必须手动编辑。以下是生产环境最终版(删减注释,保留核心):
concurrent = 4 check_interval = 3 [session_server] session_timeout = 1800 [[runners]] name = "win-server-prod-01" url = "https://gitlab.example.com/" token = "gr1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7" executor = "shell" shell = "powershell" environment = ["GIT_SSL_NO_VERIFY=1", "NODE_ENV=production", "DOTNET_ROOT=C:\\Program Files\\dotnet"] output_limit = 4096 cache_dir = "C:\\GitLab-Runner\\cache" [runners.cache] type = "file" path = "cache" [runners.custom_build_dir] enabled = true [runners.shell] before_script = ["Write-Host 'Job started at $(Get-Date)' -ForegroundColor Cyan"] after_script = ["Write-Host 'Job finished at $(Get-Date)' -ForegroundColor Green"]重点说明:
cache_dir = "C:\\GitLab-Runner\\cache":显式指定缓存路径,避免默认C:\Users\XXX\cache因用户 profile 删除而丢失;environment中加入DOTNET_ROOT:确保dotnet命令指向正确 SDK;before_script和after_script:提供统一的时间戳日志,便于排查超时问题;output_limit = 4096:限制单个 job 日志最大 4MB,防止日志爆炸拖垮 Runner。
权限加固(必须执行):
# 设置 config.toml 仅管理员可读写 icacls "C:\GitLab-Runner\config.toml" /inheritance:r /grant "Administrators:(F)" /grant "SYSTEM:(F)" # 设置 cache 目录权限 icacls "C:\GitLab-Runner\cache" /t /inheritance:r /grant "DOMAIN\svc-gitlab:(OI)(CI)(F)"4.4 服务安装与启动:监控与日志闭环
安装服务:
# 使用之前创建的域账户 & "C:\GitLab-Runner\gitlab-runner.exe" install ` --service-name "GitLab-Runner-Prod" ` --service-display-name "GitLab Runner for Production Builds" ` --service-description "CI/CD runner for .NET 8 and PowerShell pipelines" ` --service-user "DOMAIN\svc-gitlab" ` --service-password "YourSecurePassword123!" ` --working-directory "C:\GitLab-Runner" # 设置启动类型 Set-Service -Name "GitLab-Runner-Prod" -StartupType Automatic # 启动 Start-Service -Name "GitLab-Runner-Prod" # 等待服务进入 Running 状态(最多 30 秒) $timeout = 30 while ((Get-Service -Name "GitLab-Runner-Prod").Status -ne "Running" -and $timeout -gt 0) { Start-Sleep -Seconds 1 $timeout-- } if ($timeout -le 0) { Write-Host "Service failed to start" -ForegroundColor Red exit 1 } Write-Host "Service started successfully" -ForegroundColor Green验证日志:
# 查看最近 10 条 Runner 日志(事件 ID 100+) Get-WinEvent -FilterHashtable @{LogName='Application'; ProviderName='gitlab-runner'; StartTime=(Get-Date).AddMinutes(-5)} -MaxEvents 10 | Select-Object TimeCreated, Id, Message | Format-Table -AutoSize # 应看到类似: # TimeCreated Id Message # ----------- -- ------- # 2024-06-15 14:22:33 100 Starting GitLab Runner v16.10.0... # 2024-06-15 14:22:35 101 Configuration loaded. Concurrent: 4, Check interval: 3s4.5 首次流水线测试:.NET 8 构建与发布
创建测试项目.gitlab-ci.yml:
stages: - build - test - publish variables: DOTNET_CLI_TELEMETRY_OPTOUT: "1" NUGET_XMLDOC_MODE: "skip" build-dotnet: stage: build image: windows-latest # 注意:这里只是占位,实际由 Runner 本地执行 script: - pwsh -Command "Write-Host 'Building .NET 8 solution'; & 'C:\Program Files\dotnet\dotnet.exe' restore src/MyApp.sln" - pwsh -Command "& 'C:\Program Files\dotnet\dotnet.exe' build src/MyApp.sln -c Release -o ./artifacts" artifacts: - artifacts/**/* tags: - windows - dotnet8 test-dotnet: stage: test script: - pwsh -Command "& 'C:\Program Files\dotnet\dotnet.exe' test src/MyApp.Tests/MyApp.Tests.csproj -c Release --no-build" tags: - windows - dotnet8 publish-artifacts: stage: publish script: - pwsh -Command "Compress-Archive -Path ./artifacts/* -DestinationPath ./MyApp-v1.0.0.zip" artifacts: - MyApp-v1.0.0.zip tags: - windows推送后,登录 GitLab,进入项目 → CI/CD → Pipelines,应看到 pipeline 状态为running→passed。点击 job,查看日志:
- 第一行应为
Job started at 2024-06-15 14:30:22(来自before_script); dotnet restore输出Restored C:\GitLab-Runner\builds\...\src\MyApp.sln;dotnet build输出Build succeeded.;- 最终生成
MyApp-v1.0.0.zip并出现在 artifacts 列表中。
至此,一个完整的、生产就绪的 Windows GitLab Runner 部署完成。它不依赖 Docker,不妥协安全性,不隐藏错误,每一个环节都可审计、可回滚、可替换。
5. 常见问题与排查技巧实录:那些没写在文档里的坑
5.1 “gitlab-runner register” 卡住不动?检查 PowerShell ExecutionPolicy 和网络代理
现象:运行gitlab-runner register后,光标停在Please enter the gitlab-ci coordinator URL后,无响应,Ctrl+C 无效。
原因分析:
- ExecutionPolicy:Runner 内部调用
powershell.exe -Command执行网络请求,若策略为AllSigned,而 Runner 二进制无签名,PowerShell 拒绝执行,进程挂起; - 网络代理:公司内网通常有 HTTP 代理,
gitlab-runner默认不读取 Windows 系统代理设置,导致连接https://gitlab.example.com/超时,但超时错误被静默吞掉。
排查步骤:
- 以管理员身份打开 PowerShell,运行:
Get-ExecutionPolicy -List # 检查 Process、CurrentUser、LocalMachine 三级策略 - 测试网络连通性:
# 模拟 Runner 的 HTTP 请求 $webRequest = [System.Net.WebRequest]::Create("https://gitlab.example.com/api/v4/version") $webRequest.Timeout = 10000 try { $response = $webRequest.GetResponse() Write-Host "GitLab API reachable" -ForegroundColor Green } catch { Write-Host "GitLab API unreachable: $($_.Exception.Message)" -ForegroundColor Red } - 若因代理失败,临时设置环境变量:
$env:HTTP_PROXY = "http://proxy.corp:8080" $env:HTTPS_PROXY = "http://proxy.corp:8080" # 再运行 register
实操心得:我们给所有 Runner 服务器部署了一个
proxy.ps1脚本,开机自动运行,设置$env:HTTP_PROXY,并写入config.toml的environment字段。这样既不影响其他服务,又保证 Runner 网络畅通。
5.2 “invalid type: string 'true', expected a boolean”?TOML 编码与语法双重校验
现象:启动服务时报错Failed to load config.toml: invalid type: string 'true', expected a boolean。
原因:config.toml中某处写了run_untagged = "true"(字符串),而 TOML 解析器期望布尔值true(无引号)。
但更隐蔽的原因是BOM:即使你肉眼看到run_untagged = true,文件开头的EF BB BF会让解析器把第一行读成run_untagged = true,是非法字符,整个文件解析失败,错误信息被截断,只显示后面某行的类型错误。
排查技巧:
- 用 PowerShell 二进制读取:
(Get-Content "C:\GitLab-Runner\config.toml" -Encoding Byte -TotalCount 10) | ForEach-Object { [char]$_ } # 如果开头出现乱码字符,就是 BOM - 用在线 TOML 验证器(如 https://toml-lint.com/)粘贴内容,它会明确指出哪一行语法错误。
修复流程:
- 用 Notepad++ → 编码 → 转为 UTF-8 无 BOM;
- 全局搜索
"true"和"false",替换为true和false(无引号); - 全局搜索
"包裹的数字,如"4",替换为4; - 删除所有行尾空格和 Tab(TOML 不允许行尾空白);
- 保存,重启服务。
注意:VS Code 的 Prettier