GitLab CI/CD 故障排查手册
环境: GitLab CE 19.0.0 Docker 部署 | 端口 8090 | Runner: Docker executor
目录
- [CI/CD 设置页面 500 错误](#CI/CD 设置页面 500 错误)
- [CI Job Token Signing Key 未设置](#CI Job Token Signing Key 未设置)
- [CI JWT Signing Key 配置错误](#CI JWT Signing Key 配置错误)
- [Runner 注册失败 / 丢失](#Runner 注册失败 / 丢失)
- [流水线 config_error](#流水线 config_error)
- [Artifact 上传 500 Internal Server Error](#Artifact 上传 500 Internal Server Error)
- [gitlab.rb Reconfigure 失败](#gitlab.rb Reconfigure 失败)
- [Docker Desktop Internal Server Error](#Docker Desktop Internal Server Error)
- [Puma 启动超时 / Bad Gateway](#Puma 启动超时 / Bad Gateway)
- 加密字段损坏批量修复方案
1. CI/CD 设置页面 500 错误
现象
访问 http://192.168.1.130:8090/lizhihua/calculator/-/settings/ci_cd 返回 500,页面显示:
500: We're sorry, something went wrong on our end
Request ID: 01KT1AEE14XBEDVWATFD455X11
根因
Projects::Settings::CiCdController#show 在渲染页面时,会读取项目的所有配置。其中 runners_token_encrypted 列使用了 attr_encrypted 加密,但由于历史原因(密钥变更、数据迁移等),加密值无法被正确解密,抛出 OpenSSL::Cipher::CipherError。
排查方法
bash
# 查看 Rails 异常日志
docker exec gitlab bash -c "grep 'CipherError\|runners_token' \
/var/log/gitlab/gitlab-rails/exceptions_json.log | tail -5"
# 通过 Rails console 确认
docker exec gitlab gitlab-rails runner "
Project.find_each do |p|
begin
p.runners_token
rescue OpenSSL::Cipher::CipherError => e
puts \"Project #{p.id} (#{p.path}): #{e.message}\"
end
end
"
修复方法
重置所有项目的加密 runners_token:
bash
docker exec gitlab gitlab-rails runner "
# 重置所有项目的 runners_token
Project.update_all(runners_token_encrypted: nil, runners_token: nil)
puts 'All project runners_token reset'
# 重置 Runner 的 token
Ci::Runner.update_all(token_encrypted: nil, token: nil)
puts 'All runner tokens reset'
# 重置 ApplicationSetting 中的 registration token
ApplicationSetting.current.update!(runners_registration_token_encrypted: nil)
puts 'Registration token reset'
"
# 重置后需要重新注册 Runner
docker exec gitlab gitlab-ctl restart puma
预防措施
- 不要手动修改数据库中的加密列
- 备份时同时备份
db_key_base(在/etc/gitlab/gitlab-secrets.json) - 升级 GitLab 前先运行
gitlab-rake gitlab:check
2. CI Job Token Signing Key 未设置
现象
流水线 job 执行成功,但 trace(日志)上传失败:
RuntimeError: CI job token signing key is not set
Runner PATCH /api/v4/jobs/XXX/trace 返回 500。
根因
ApplicationSetting.ci_job_token_signing_key 在数据库中为 nil。虽然在 gitlab.rb 中配置了:
ruby
gitlab_rails["ci_job_token_signing_key"] = File.read("/etc/gitlab/ci_job_token_signing_key.pem")
但 gitlab-ctl reconfigure 在将配置写入数据库时,会因为其他加密列的 CipherError 导致 save 回调失败,密钥无法持久化。
排查方法
bash
# 检查数据库中的值
docker exec gitlab gitlab-rails runner "
as = ApplicationSetting.current
puts 'ci_job_token_signing_key: ' + as.ci_job_token_signing_key.inspect
puts 'encrypted value present: ' + as.read_attribute('encrypted_ci_job_token_signing_key').present?.to_s
puts 'IV present: ' + as.read_attribute('encrypted_ci_job_token_signing_key_iv').present?.to_s
"
常见症状: encrypted 值存在但 IV 为 nil(之前的修复脚本只写了加密值没写 IV)。
修复方法
bash
docker exec gitlab gitlab-rails runner "
# 1. 清空损坏的加密值
ApplicationSetting.where.not(id: nil).update_all(
encrypted_ci_job_token_signing_key: nil,
encrypted_ci_job_token_signing_key_iv: nil
)
# 2. 重新加载
as = ApplicationSetting.current
as.reload
# 3. 通过 setter 正确写入(自动生成 IV 并加密)
key_content = File.read('/etc/gitlab/ci_job_token_signing_key.pem')
as.ci_job_token_signing_key = key_content
# 4. 保存(跳过验证避免触发其他加密列的回调)
as.save!(validate: false)
# 5. 验证
as.reload
puts 'ci_job_token_signing_key OK: ' + as.ci_job_token_signing_key.present?.to_s
"
# 重启 Puma 使配置生效
docker exec gitlab gitlab-ctl restart puma
关键点
- 必须通过
as.ci_job_token_signing_key = value这个 setter 写入,它会自动调用Gitlab::CryptoHelper.aes256_gcm_encrypt生成加密值和 IV - 不能直接 SQL 写入,因为缺少 IV 会导致解密失败
save!(validate: false)跳过模型验证,避免触发其他加密列的读取回调
3. CI JWT Signing Key 配置错误
现象
CI/CD 设置页面或 API 调用报 OpenSSL::PKey::RSAError,或流水线相关功能异常。
根因
ci_jwt_signing_key 要求是 RSA PEM 格式私钥,但之前被错误地设置为 hex 字符串:
ruby
# 错误: hex 字符串不是合法的 RSA 私钥
gitlab_rails['ci_jwt_signing_key'] = 'a1b2c3d4e5f6...'
# 正确: 必须是 PEM 格式
gitlab_rails['ci_jwt_signing_key'] = <<~EOK
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
-----END RSA PRIVATE KEY-----
EOK
修复方法
bash
# 1. 生成 RSA 私钥
docker exec gitlab openssl genrsa 2048 > /tmp/jwt_signing_key.pem
# 2. 复制到 GitLab 配置目录
docker cp /tmp/jwt_signing_key.pem gitlab:/etc/gitlab/jwt_signing_key.pem
# 3. 在 gitlab.rb 中引用文件
echo "gitlab_rails[\"ci_jwt_signing_key\"] = File.read(\"/etc/gitlab/jwt_signing_key.pem\")" \
>> /etc/gitlab/gitlab.rb
# 4. 同样处理 ci_job_token_signing_key
docker exec gitlab openssl genrsa 2048 > /tmp/ci_job_token_signing_key.pem
docker cp /tmp/ci_job_token_signing_key.pem gitlab:/etc/gitlab/ci_job_token_signing_key.pem
# 5. Reconfigure
docker exec gitlab gitlab-ctl reconfigure
docker exec gitlab gitlab-ctl restart puma
4. Runner 注册失败 / 丢失
现象
- 流水线一直处于
pending状态 - Runner 状态显示 offline
- Docker Desktop 重启后 Runner 的 config.toml 丢失
排查方法
bash
# 检查 Runner 是否在线
curl -s --header "PRIVATE-TOKEN: <token>" \
"http://192.168.1.130:8090/api/v4/runners/all" | python -m json.tool
# 检查 Runner 容器状态
docker ps | grep runner
# 查看 Runner 日志
docker logs --tail 50 gitlab-runner
修复方法
bash
# 1. 获取 Runner 注册 token(通过 API 创建新 Runner)
RUNNER_TOKEN=$(curl -s --request POST \
--header "PRIVATE-TOKEN: <root-pat>" \
"http://192.168.1.130:8090/api/v4/user/runners" \
--data "runner_type=instance_type" \
--data "description=Docker CI Runner" \
| python -c "import sys,json; print(json.load(sys.stdin)['token'])")
# 2. 注册 Runner
docker exec gitlab-runner gitlab-runner register \
--non-interactive \
--url http://192.168.1.130:8090 \
--token "$RUNNER_TOKEN" \
--executor docker \
--docker-image python:3.11-slim \
--docker-pull-policy if-not-present \
--description "Docker CI Runner"
# 3. 添加 clone_url 到 config.toml
docker exec gitlab-runner bash -c "
sed -i '/\[runners.cache\]/i \ clone_url = \"http://192.168.1.130:8090\"' \
/etc/gitlab-runner/config.toml
"
# 4. 重启 Runner
docker restart gitlab-runner
预防措施
- 使用 Docker volume 持久化 Runner 配置:
-v gitlab-runner-config:/etc/gitlab-runner - 在 docker-compose.yml 中配置
restart: unless-stopped
5. 流水线 config_error
现象
流水线状态显示 config_error,failure_reason 也是 config_error。
常见原因及修复
原因 A: CI 变量加密值损坏
bash
# 查看 CI 变量状态
docker exec gitlab gitlab-rails runner "
Ci::Variable.where(project_id: 14).each do |v|
begin
v.value
puts \"#{v.key}: OK\"
rescue => e
puts \"#{v.key}: ERROR - #{e.message}\"
end
end
"
# 删除损坏的变量
docker exec gitlab gitlab-rails runner "
Ci::Variable.find_by(project_id: 14, key: 'ANTHROPIC_API_KEY')&.delete
puts 'Deleted broken variable'
"
原因 B: YAML 语法错误
yaml
# 错误: 内联注释破坏 YAML 解析
script:
- echo "hello" # 打包应用 # <-- 这会导致解析失败
# 正确: 注释放在单独行
script:
# 打包应用
- echo "hello"
验证 YAML 语法
bash
# 使用 GitLab API 验证
curl -s --request POST \
--header "PRIVATE-TOKEN: <token>" \
--header "Content-Type: application/json" \
--data '{"content": "<yaml-content>"}' \
"http://192.168.1.130:8090/api/v4/ci/lint" | python -m json.tool
6. Artifact 上传 500 Internal Server Error
现象
测试本身通过,但上传 artifact 时报错:
Uploading artifacts as "archive" to coordinator... 500 Internal Server Error
WARNING: Retrying...
FATAL: invalid argument
Rails 异常日志显示:
Errno::ENOENT: No such file or directory @ apply2files -
/var/opt/gitlab/gitlab-rails/shared/artifacts/tmp/work/1780317050-8597-0006-5127/artifacts.zip
根因
CarrierWave 1.3.4 的 move_to 方法在跨文件系统移动文件时存在 bug:
/tmp(上传临时目录)和/var/opt/.../artifacts(存储目录)在不同的 Linux 设备上(device 191 vs device 102)FileUtils.mv跨设备移动时会先rename(),失败后 fallback 到cp+rm- 但在某些场景下,文件被 move 后原始路径已删除,
chmod!操作找不到文件抛出 ENOENT - 整个
cache!流程因此失败
修复方法
Patch CarrierWave 的 sanitized_file.rb:
bash
# 备份原始文件
docker exec gitlab cp \
/opt/gitlab/embedded/lib/ruby/gems/3.3.0/gems/carrierwave-1.3.4/lib/carrierwave/sanitized_file.rb \
/opt/gitlab/embedded/lib/ruby/gems/3.3.0/gems/carrierwave-1.3.4/lib/carrierwave/sanitized_file.rb.bak
# Patch 1: chmod! 增加文件存在性检查
docker exec gitlab bash -c "
sed -i 's/def chmod!(path, permissions)\n File.chmod(permissions, path) if permissions/def chmod!(path, permissions)\n return unless permissions\n return unless File.exist?(path)\n File.chmod(permissions, path)/' \
/opt/gitlab/embedded/lib/ruby/gems/3.3.0/gems/carrierwave-1.3.4/lib/carrierwave/sanitized_file.rb
"
# Patch 2: move! 增加跨设备回退
# 在 FileUtils.mv 之后增加:
# unless File.exist?(new_path)
# FileUtils.cp(path, new_path) if File.exist?(path)
# end
# 重启 Puma 使 patch 生效
docker exec gitlab gitlab-ctl restart puma
注意事项
- 此 patch 直接修改了 gem 文件,GitLab 升级后会被覆盖,届时需要重新 patch
- 原始文件备份在
sanitized_file.rb.bak - 长期方案: 升级到新版 CarrierWave 或等 GitLab 官方修复
7. gitlab.rb Reconfigure 失败
现象 A: SyntaxError - unterminated string
SyntaxError: (eval):123: unterminated string meets end of file
根因 : 编辑 gitlab.rb 时残留了 heredoc 标记(如 '@)。
修复:
bash
# 查找并删除残留的 heredoc 标记
docker exec gitlab sed -i "/^'@$/d" /etc/gitlab/gitlab.rb
docker exec gitlab gitlab-ctl reconfigure
现象 B: ci_jwt_signing_key 不是合法 PEM
OpenSSL::PKey::RSAError: Neither PUB key nor PRIV key
根因 : ci_jwt_signing_key 配置了 hex 字符串而非 RSA PEM。
修复 : 参考 [第 3 节](#第 3 节)。
预防措施
-
每次修改 gitlab.rb 前先备份:
bashdocker exec gitlab cp /etc/gitlab/gitlab.rb /etc/gitlab/gitlab.rb.bak.$(date +%Y%m%d%H%M%S) -
修改后先用
docker exec gitlab ruby -c /etc/gitlab/gitlab.rb检查语法
8. Docker Desktop Internal Server Error
现象
Windows Docker Desktop 间歇性报错:
error during connect: Get "http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.24/...":
open //./pipe/docker_engine: The system cannot find the file specified.
或:
An unexpected error occurred: Internal Server Error
根因
Docker Desktop 的 WSL2 后端或 Hyper-V 虚拟机间歇性崩溃,pipe endpoint 不可达。
修复方法
powershell
# 方法 1: 重启 Docker Desktop 服务
Restart-Service *docker*
Restart-Service *com.docker*
# 方法 2: 重启 WSL
wsl --shutdown
# 方法 3: 重启 Docker Desktop 进程
Stop-Process -Name "Docker Desktop" -Force
Start-Process "C:\Program Files\Docker\Docker\Docker Desktop.exe"
预防措施
- 在 Docker Desktop Settings 中增大资源分配(至少 4GB RAM + 4 CPU)
- 启用
Use the WSL 2 based engine - 避免同时运行 GitLab + Runner + 多个 CI job(内存不足会触发 OOM)
9. Puma 启动超时 / Bad Gateway
现象
重启 Puma 后,GitLab 返回 502 Bad Gateway,持续 1-3 分钟。
原因
Puma 预加载 GitLab Rails 应用需要时间(通常 1-2 分钟),在预加载完成前 nginx 无法连接后端。
判断方法
bash
# 查看 Puma 是否在预加载
docker exec gitlab bash -c "tail -5 /var/log/gitlab/puma/current"
# 如果看到 "Preloading application" 说明还在启动
# 等待 worker 启动
docker exec gitlab bash -c "tail -10 /var/log/gitlab/puma/puma_stdout.log"
# 看到 "Worker N booted" 说明启动完成
处理方法
等待 1-3 分钟后重试即可。如果超过 5 分钟仍 502:
bash
# 强制重启所有服务
docker exec gitlab gitlab-ctl restart
# 如果仍然失败,查看完整日志
docker exec gitlab gitlab-ctl tail puma
10. 加密字段损坏批量修复方案
背景
GitLab 使用 attr_encrypted gem 对数据库中的敏感字段进行加密。加密方案:
- 加密算法: AES-256-GCM
- 密钥来源:
/etc/gitlab/gitlab-secrets.json中的db_key_base - 每个加密字段有对应的
encrypted_XXX(密文)和encrypted_XXX_iv(初始向量)列
当 db_key_base 变更、数据库迁移异常、或手动修改加密值时,会导致 OpenSSL::Cipher::CipherError。
查找所有加密列
bash
docker exec gitlab gitlab-rails runner "
as = ApplicationSetting.current
cols = as.class.columns.select { |c| c.name.start_with?('encrypted_') }
puts \"Found #{cols.size} encrypted columns\"
cols.each { |c| puts \" #{c.name}\" }
"
批量清空损坏的加密值
bash
docker exec gitlab gitlab-rails runner "
as = ApplicationSetting.current
cols = as.class.columns.select { |c| c.name.start_with?('encrypted_') }
updates = {}
cols.each do |c|
val = as.read_attribute(c.name)
if val.present?
updates[c.name] = nil
# 同时清空对应的 IV 列
iv_col = c.name + '_iv'
updates[iv_col] = nil if as.class.column_names.include?(iv_col)
end
end
as.class.where(id: as.id).update_all(updates) unless updates.empty?
puts \"Cleared #{updates.size / 2} encrypted field pairs\"
"
安全地重新写入加密字段
bash
# 正确方式: 通过 setter 写入(自动生成 IV)
docker exec gitlab gitlab-rails runner "
as = ApplicationSetting.current
as.reload
as.ci_job_token_signing_key = File.read('/etc/gitlab/ci_job_token_signing_key.pem')
as.save!(validate: false)
puts 'Written successfully'
"
# 错误方式: 直接 SQL 写入(缺少 IV,解密必然失败)
# ApplicationSetting.where(id: 1).update_all(
# encrypted_ci_job_token_signing_key: 'base64value...'
# )
常用命令速查
bash
# GitLab 服务状态
docker exec gitlab gitlab-ctl status
# 重启 Puma(Web 服务)
docker exec gitlab gitlab-ctl restart puma
# 重新配置 GitLab
docker exec gitlab gitlab-ctl reconfigure
# Rails console
docker exec -it gitlab gitlab-rails console
# 查看最近异常
docker exec gitlab bash -c "tail -20 /var/log/gitlab/gitlab-rails/exceptions_json.log"
# 查看 Runner 状态
docker exec gitlab-runner gitlab-runner list
# 查看 Runner 日志
docker logs --tail 50 gitlab-runner
# 查看流水线状态
curl -s --header "PRIVATE-TOKEN: <token>" \
"http://192.168.1.130:8090/api/v4/projects/lizhihua%2Fcalculator/pipelines?per_page=5"
# 查看 job 日志
curl -s --header "PRIVATE-TOKEN: <token>" \
"http://192.168.1.130:8090/api/v4/projects/lizhihua%2Fcalculator/jobs/<job_id>/trace"
# 验证 .gitlab-ci.yml
curl -s --request POST \
--header "PRIVATE-TOKEN: <token>" \
--header "Content-Type: application/json" \
--data '{"content":"..."}' \
"http://192.168.1.130:8090/api/v4/ci/lint"
问题处理时间线
| 时间 | 问题 | 状态 |
|---|---|---|
| 2026-06-01 上午 | CI/CD 设置页 500 (加密字段损坏) | 已修复 |
| 2026-06-01 上午 | Runner 注册失败 / config.toml 丢失 | 已修复 |
| 2026-06-01 上午 | gitlab.rb reconfigure SyntaxError | 已修复 |
| 2026-06-01 中午 | 流水线 config_error (CI 变量损坏 + YAML 错误) | 已修复 |
| 2026-06-01 中午 | CI job token signing key 未设置 | 已修复 |
| 2026-06-01 下午 | Artifact 上传 500 (CarrierWave 跨设备 bug) | 已修复 (patch) |
| 2026-06-01 下午 | 流水线 4 阶段全部通过 | 验证通过 |