GitLab CI/CD 故障排查手册

GitLab CI/CD 故障排查手册

环境: GitLab CE 19.0.0 Docker 部署 | 端口 8090 | Runner: Docker executor


目录

  1. [CI/CD 设置页面 500 错误](#CI/CD 设置页面 500 错误)
  2. [CI Job Token Signing Key 未设置](#CI Job Token Signing Key 未设置)
  3. [CI JWT Signing Key 配置错误](#CI JWT Signing Key 配置错误)
  4. [Runner 注册失败 / 丢失](#Runner 注册失败 / 丢失)
  5. [流水线 config_error](#流水线 config_error)
  6. [Artifact 上传 500 Internal Server Error](#Artifact 上传 500 Internal Server Error)
  7. [gitlab.rb Reconfigure 失败](#gitlab.rb Reconfigure 失败)
  8. [Docker Desktop Internal Server Error](#Docker Desktop Internal Server Error)
  9. [Puma 启动超时 / Bad Gateway](#Puma 启动超时 / Bad Gateway)
  10. 加密字段损坏批量修复方案

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:

  1. /tmp(上传临时目录)和 /var/opt/.../artifacts(存储目录)在不同的 Linux 设备上(device 191 vs device 102)
  2. FileUtils.mv 跨设备移动时会先 rename(),失败后 fallback 到 cp + rm
  3. 但在某些场景下,文件被 move 后原始路径已删除,chmod! 操作找不到文件抛出 ENOENT
  4. 整个 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 前先备份:

    bash 复制代码
    docker 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 阶段全部通过 验证通过
相关推荐
Bigger3 小时前
实战:搭建 AI Code Review 自动化流水线
前端·ci/cd·自动化运维
wb043072013 小时前
从接单到出餐——从阿明的“手写菜单“到自动化流水线,看 CI/CD 与 DevOps 的完整旅程
ci/cd·架构·自动化·devops
Aubrey-J1 天前
老版本Gitlab SSL证书自动续期错误KeyError: key not found: “token“解决
网络协议·gitlab·ssl
JackSparrow4142 天前
使用Ansible批量管理+更新产品环境服务器配置
运维·服务器·ci/cd·kubernetes·自动化·ansible·sre
smartpi_ai2 天前
CI-73T 裸片方案 MICBIAS 电容 C11 设计:模块与芯片的差异解析
ci/cd
java_logo2 天前
Docker 部署 GitLab CE 完整版教程
docker·容器·gitlab·gitlab docker部署·gitlab部署文档·gitlab部署·gitlab部署教程
lili00122 天前
AI编程三件套CI集成与质量门禁:从“看起来对“到“证据确凿“
java·人工智能·python·ci/cd·ai编程
隔窗听雨眠3 天前
GitLab CI前端加载慢优化实录
ci/cd·gitlab
jiayong233 天前
MySQL 排序规则冲突问题与 utf8mb4_general_ci 统一方案
android·mysql·ci/cd