Help us learn about your current experience with the documentation. Take the survey.

容器注册表故障排除

在调查特定问题之前,请尝试以下故障排除步骤:

  1. 验证您的 Docker 客户端和 GitLab 服务器上的系统时钟是否已同步(例如,通过 NTP)。

  2. 对于使用 S3 支持的注册表,请验证您的 IAM 权限和 S3 凭证(包括区域)是否正确。 有关更多信息,请参阅示例 IAM 策略

  3. 检查注册表日志(例如,/var/log/gitlab/registry/current)和 GitLab 生产日志 (例如,/var/log/gitlab/gitlab-rails/production.log)中的错误。

  4. 检查容器注册表的 NGINX 配置文件(例如,/var/opt/gitlab/nginx/conf/gitlab-registry.conf) 以确认哪个端口接收请求。

  5. 验证请求是否正确转发到容器注册表:

    curl --verbose --noproxy "*" https://<hostname>:<port>/v2/_catalog

    响应应包含一行带有 Www-Authenticate: Bearer 且包含 service="container_registry" 的内容。例如:

    < HTTP/1.1 401 Unauthorized
    < Server: nginx
    < Date: Fri, 07 Mar 2025 08:24:43 GMT
    < Content-Type: application/json
    < Content-Length: 162
    < Connection: keep-alive
    < Docker-Distribution-Api-Version: registry/2.0
    < Www-Authenticate: Bearer realm="https://<hostname>/jwt/auth",service="container_registry",scope="registry:catalog:*"
    < X-Content-Type-Options: nosniff
    <
    {"errors":[{"code":"UNAUTHORIZED","message":"authentication required","detail":
    [{"Type":"registry","Class":"","Name":"catalog","ProjectPath":"","Action":"*"}]}]}
    * Connection #0 to host <hostname> left intact

在容器注册表中使用自签名证书

如果您在容器注册表中使用自签名证书,您可能会在 CI 作业期间遇到如下问题:

Error response from daemon: Get registry.example.com/v1/users/: x509: certificate signed by unknown authority

运行该命令的 Docker 守护进程期望一个由受信任 CA 签名的证书, 因此会出现上述错误。

虽然 GitLab 不支持开箱即用地在容器注册表中使用自签名证书, 但可以通过指示 Docker 守护进程信任自签名证书, 挂载 Docker 守护进程并在 GitLab Runner 的 config.toml 文件中设置 privileged = false 来使其工作。 设置 privileged = true 会优先于 Docker 守护进程:

  [runners.docker]
    image = "ruby:2.6"
    privileged = false
    volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]

有关此问题的更多信息:问题 18239

Docker 登录尝试失败并显示:’token signed by untrusted key'

注册表依赖 GitLab 验证凭证 如果注册表无法验证有效的登录尝试,您会收到以下错误消息:

# docker login gitlab.company.com:4567
Username: user
Password:
Error response from daemon: login attempt to https://gitlab.company.com:4567/v2/ failed with status: 401 Unauthorized

更具体地说,在 /var/log/gitlab/registry/current 日志文件中出现以下内容:

level=info msg="token signed by untrusted key with ID: "TOKE:NL6Q:7PW6:EXAM:PLET:OKEN:BG27:RCIB:D2S3:EXAM:PLET:OKEN""
level=warning msg="error authorizing context: invalid token" go.version=go1.12.7 http.request.host="gitlab.company.com:4567" http.request.id=74613829-2655-4f96-8991-1c9fe33869b8 http.request.method=GET http.request.remoteaddr=10.72.11.20 http.request.uri="/v2/" http.request.useragent="docker/19.03.2 go/go1.12.8 git-commit/6a30dfc kernel/3.10.0-693.2.2.el7.x86_64 os/linux arch/amd64 UpstreamClient(Docker-Client/19.03.2 \(linux\))"

GitLab 使用证书密钥对两侧的内容来加密注册表的认证令牌。 此消息表示这些内容不一致。

检查正在使用的文件:

  • grep -A6 'auth:' /var/opt/gitlab/registry/config.yml

    ## Container registry certificate
       auth:
         token:
           realm: https://gitlab.my.net/jwt/auth
           service: container_registry
           issuer: omnibus-gitlab-issuer
      -->  rootcertbundle: /var/opt/gitlab/registry/gitlab-registry.crt
           autoredirect: false
  • grep -A9 'Container Registry' /var/opt/gitlab/gitlab-rails/etc/gitlab.yml

    ## Container registry key
       registry:
         enabled: true
         host: gitlab.company.com
         port: 4567
         api_url: http://127.0.0.1:5000 # internal address to the registry, is used by GitLab to directly communicate with API
         path: /var/opt/gitlab/gitlab-rails/shared/registry
    -->  key: /var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key
         issuer: omnibus-gitlab-issuer
         notification_secret:

这些 openssl 命令的输出应该匹配,证明证书密钥对是匹配的:

/opt/gitlab/embedded/bin/openssl x509 -noout -modulus -in /var/opt/gitlab/registry/gitlab-registry.crt | /opt/gitlab/embedded/bin/openssl sha256
/opt/gitlab/embedded/bin/openssl rsa -noout -modulus -in /var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key | /opt/gitlab/embedded/bin/openssl sha256

如果证书的两部分不一致,请删除这些文件并运行 gitlab-ctl reconfigure 以重新生成该对。如果存在,该对将使用 /etc/gitlab/gitlab-secrets.json 中的现有值重新创建。 要生成新的一对,请在运行 gitlab-ctl reconfigure 之前删除 /etc/gitlab/gitlab-secrets.json 中的 registry 部分。

如果您已用自己的证书覆盖自动生成的自签名对, 并确保它们的内容一致,您可以删除 /etc/gitlab/gitlab-secrets.json 中的 ‘registry’ 部分, 然后运行 gitlab-ctl reconfigure

使用 GitLab 注册表时的 AWS S3 推送大镜像错误

当在 GitLab 注册表中使用 AWS S3 时,推送 大镜像时可能会发生错误。在注册表日志中查找以下错误:

level=error msg="response completed with error" err.code=unknown err.detail="unexpected EOF" err.message="unknown error"

要解决此错误,请在注册表配置中指定 chunksize 值。 从 25000000(25 MB)到 50000000(50 MB)之间的值开始。

  1. 编辑 /etc/gitlab/gitlab.rb

    registry['storage'] = {
      's3' => {
        'accesskey' => 'AKIAKIAKI',
        'secretkey' => 'secret123',
        'bucket'    => 'gitlab-registry-bucket-AKIAKIAKI',
        'chunksize' => 25000000
      }
    }
  2. 保存文件并重新配置 GitLab 以使更改生效。

  1. 编辑 config/gitlab.yml

    storage:
      s3:
        accesskey: 'AKIAKIAKI'
        secretkey: 'secret123'
        bucket: 'gitlab-registry-bucket-AKIAKIAKI'
        chunksize: 25000000
  2. 保存文件并重启 GitLab 以使更改生效。

支持旧版 Docker 客户端

GitLab 附带的 Docker 容器注册表默认禁用 schema1 清单。 如果您仍在使用旧版 Docker 客户端(1.9 或更早版本),您可能会 在推送镜像时遇到错误。有关更多详细信息,请参阅 问题 4145

您可以添加一个配置选项以实现向后兼容性。

  1. 编辑 /etc/gitlab/gitlab.rb

    registry['compatibility_schema1_enabled'] = true
  2. 保存文件并重新配置 GitLab 以使更改生效。

  1. 编辑您在部署注册表时创建的 YAML 配置文件。添加以下代码段:

    compatibility:
        schema1:
            enabled: true
  2. 重启注册表以使更改生效。

Docker 连接错误

当组、项目或分支名称中包含特殊字符时,可能会发生 Docker 连接错误。特殊字符可以包括:

  • 前导下划线
  • 尾随连字符/破折号
  • 双连字符/破折号

要解决此问题,您可以更改组路径更改项目路径 或更改 分支名称。另一种选择是创建推送规则 以防止 整个实例出现此错误。

镜像推送错误

即使 docker login 成功,您在推送 Docker 镜像时也可能会陷入重试循环。

当 NGINX 未正确将标头转发到注册表时,会发生此问题,通常在自定义 设置中,SSL 被卸载到第三方反向代理。

有关更多信息,请参阅通过 NGINX 代理推送 Docker 失败,尝试发送 32B 层 #970

要解决此问题,请更新您的 NGINX 配置以在注册表中启用相对 URL:

  1. 编辑 /etc/gitlab/gitlab.rb

    registry['env'] = {
      "REGISTRY_HTTP_RELATIVEURLS" => true
    }
  2. 保存文件并重新配置 GitLab 以使更改生效。

  1. 编辑您在部署注册表时创建的 YAML 配置文件。添加以下代码段:

    http:
        relativeurls: true
  2. 保存文件并重启 GitLab 以使更改生效。

  1. 编辑您的 docker-compose.yaml 文件:

    GITLAB_OMNIBUS_CONFIG: |
      registry['env'] = {
        "REGISTRY_HTTP_RELATIVEURLS" => true
      }
  2. 如果问题仍然存在,请确保两个 URL 都使用 HTTPS:

    GITLAB_OMNIBUS_CONFIG: |
      external_url 'https://git.example.com'
      registry_external_url 'https://git.example.com:5050'
  3. 保存文件并重启容器:

    sudo docker restart gitlab

启用注册表调试服务器

您可以使用容器注册表调试服务器来诊断问题。调试端点可以监控指标和健康状况,以及进行性能分析。

调试端点可能提供敏感信息。 在生产环境中必须限制对调试端点的访问。

可以通过在 gitlab.rb 配置中设置注册表调试地址来启用可选的调试服务器。

registry['debug_addr'] = "localhost:5001"

添加设置后,重新配置 GitLab 以应用更改。

使用 curl 从调试服务器请求调试输出:

curl "localhost:5001/debug/health"
curl "localhost:5001/debug/vars"

启用注册表调试日志

您可以启用调试日志来帮助排除容器注册表的问题。

调试日志可能包含敏感信息,如身份验证详细信息、令牌或存储库信息。 仅在必要时启用调试日志,并在故障排除完成后禁用它们。

  1. 编辑 /var/opt/gitlab/registry/config.yml

    level: debug
  2. 保存文件并重启注册表:

    sudo gitlab-ctl restart registry

此配置是临时的,当您运行 gitlab-ctl reconfigure 时会被丢弃。

  1. 导出 Helm 值:

    helm get values gitlab > gitlab_values.yaml
  2. 编辑 gitlab_values.yaml

    registry:
      log:
        level: debug
  3. 保存文件并应用新值:

    helm upgrade -f gitlab_values.yaml gitlab gitlab/gitlab --namespace <namespace>

启用注册表 Prometheus 指标

如果启用了调试服务器,您还可以启用 Prometheus 指标。此端点公开与几乎所有注册表操作相关的详细遥测数据。

registry['debug'] = {
  'prometheus' => {
    'enabled' => true,
    'path' => '/metrics'
  }
}

使用 curl 从 Prometheus 请求调试输出:

curl "localhost:5001/debug/metrics"

空名称标签

如果使用 AWS DataSync 将注册表数据复制到 S3 存储桶或在 S3 存储桶之间复制,则会在目标存储桶中每个容器存储库的根路径中创建一个空的元数据对象。这导致注册表将这些文件解释为在 GitLab UI 和 API 中显示为没有名称的标签。有关更多信息,请参阅 此问题

要解决此问题,您可以执行以下两项操作之一:

  • 使用 AWS CLI rm 命令从每个受影响存储库的根目录中删除空对象。特别注意 尾部的 /,并确保不要使用 --recursive 选项:

    aws s3 rm s3://<bucket>/docker/registry/v2/repositories/<path to repository>/
  • 使用 AWS CLI sync 命令将注册表数据复制到新存储桶并配置注册表使用它。这 会留下空对象。

高级故障排除

我们使用一个具体示例来说明如何诊断 S3 设置的问题。

调查清理策略

如果您不确定为什么您的清理策略删除或未删除标签,请从 Rails 控制台运行以下脚本,逐行执行策略。 这可以帮助诊断策略的问题。

repo = ContainerRepository.find(<repository_id>)
policy = repo.project.container_expiration_policy

tags = repo.tags
tags.map(&:name)

tags.reject!(&:latest?)
tags.map(&:name)

regex_delete = ::Gitlab::UntrustedRegexp.new("\A#{policy.name_regex}\z")
regex_retain = ::Gitlab::UntrustedRegexp.new("\A#{policy.name_regex_keep}\z")

tags.select! { |tag| regex_delete.match?(tag.name) && !regex_retain.match?(tag.name) }

tags.map(&:name)

now = DateTime.current
tags.sort_by! { |tag| tag.created_at || now }.reverse! # Lengthy operation

tags = tags.drop(policy.keep_n)
tags.map(&:name)

older_than_timestamp = ChronicDuration.parse(policy.older_than).seconds.ago

tags.select! { |tag| tag.created_at && tag.created_at < older_than_timestamp }

tags.map(&:name)
  • 脚本构建要删除的标签列表(tags)。
  • tags.map(&:name) 打印要删除的标签列表。这可能是一个耗时的操作。
  • 在每个过滤器之后,检查 tags 列表以查看它是否包含要销毁的预期标签。

推送期间意外的 403 错误

用户尝试启用使用 S3 支持的注册表。docker login 步骤 顺利进行。但是,在推送镜像时,输出显示:

The push refers to a repository [s3-testing.myregistry.com:5050/root/docker-test/docker-image]
dc5e59c14160: Pushing [==================================================>] 14.85 kB
03c20c1a019a: Pushing [==================================================>] 2.048 kB
a08f14ef632e: Pushing [==================================================>] 2.048 kB
228950524c88: Pushing 2.048 kB
6a8ecde4cc03: Pushing [==>                                                ] 9.901 MB/205.7 MB
5f70bf18a086: Pushing 1.024 kB
737f40e80b7f: Waiting
82b57dbc5385: Waiting
19429b698a22: Waiting
9436069b92a3: Waiting
error parsing HTTP 403 response body: unexpected end of JSON input: ""

这个错误是模糊的,因为不清楚 403 是来自 GitLab Rails 应用程序、Docker 注册表还是其他来源。在这种情况下, 因为我们知道登录成功,我们可能需要查看客户端和注册表之间的通信。

Docker 客户端和注册表之间的 REST API 在Docker 文档中有描述。通常,人们只会 使用 Wireshark 或 tcpdump 来捕获流量并查看哪里出了问题。然而,因为 Docker 客户端和服务器之间的所有通信都是通过 HTTPS 进行的,即使您知道私钥,也很难快速解密流量。那么我们能做什么呢?

一种方法是通过设置不安全的注册表来禁用 HTTPS。这可能会引入安全漏洞,仅建议用于本地测试。如果您有生产系统并且不能或不想这样做,还有另一种方法:使用 mitmproxy,它代表中间人代理。

mitmproxy

mitmproxy 允许您在客户端和服务器之间放置一个代理来检查所有流量。一个复杂之处是您的系统需要信任 mitmproxy SSL 证书才能使其工作。

以下安装说明假设您正在运行 Ubuntu:

  1. 安装 mitmproxy

  2. 运行 mitmproxy --port 9000 生成其证书。 输入 CTRL-C 退出。

  3. 将证书从 ~/.mitmproxy 安装到您的系统:

    sudo cp ~/.mitmproxy/mitmproxy-ca-cert.pem /usr/local/share/ca-certificates/mitmproxy-ca-cert.crt
    sudo update-ca-certificates

如果成功,输出应表明添加了一个证书:

Updating certificates in /etc/ssl/certs... 1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d....done.

要验证证书是否正确安装,请运行:

mitmproxy --port 9000

此命令在端口 9000 上运行 mitmproxy。在另一个窗口中,运行:

curl --proxy "http://localhost:9000" "https://httpbin.org/status/200"

如果一切设置正确,mitmproxy 窗口上会显示信息, 并且 curl 命令不会生成错误。

使用代理运行 Docker 守护进程

要使 Docker 通过代理连接,您必须使用适当的环境变量启动 Docker 守护进程。最简单的方法是关闭 Docker(例如 sudo initctl stop docker),然后手动运行 Docker。以 root 身份运行:

export HTTP_PROXY="http://localhost:9000"
export HTTPS_PROXY="http://localhost:9000"
docker daemon --debug

此命令启动 Docker 守护进程并通过 mitmproxy 代理所有连接。

运行 Docker 客户端

现在我们已经运行了 mitmproxy 和 Docker,我们可以尝试登录并推送容器镜像。您可能需要以 root 身份运行此操作。例如:

docker login example.s3.amazonaws.com:5050
docker push example.s3.amazonaws.com:5050/root/docker-test/docker-image

在前面的示例中,我们在 mitmproxy 窗口上看到以下跟踪:

PUT https://example.s3.amazonaws.com:4567/v2/root/docker-test/blobs/uploads/(UUID)/(QUERYSTRING)
    ← 201 text/plain [no content] 661ms
HEAD https://example.s3.amazonaws.com:4567/v2/root/docker-test/blobs/sha256:(SHA)
    ← 307 application/octet-stream [no content] 93ms
HEAD https://example.s3.amazonaws.com:4567/v2/root/docker-test/blobs/sha256:(SHA)
    ← 307 application/octet-stream [no content] 101ms
HEAD https://example.s3.amazonaws.com:4567/v2/root/docker-test/blobs/sha256:(SHA)
    ← 307 application/octet-stream [no content] 87ms
HEAD https://amazonaws.example.com/docker/registry/vs/blobs/sha256/dd/(UUID)/data(QUERYSTRING)
    ← 403 application/xml [no content] 80ms
HEAD https://amazonaws.example.com/docker/registry/vs/blobs/sha256/dd/(UUID)/data(QUERYSTRING)
    ← 403 application/xml [no content] 62ms

此输出显示:

  • 初始 PUT 请求顺利进行,状态码为 201
  • 201 将客户端重定向到 Amazon S3 存储桶。
  • 对 AWS 存储桶的 HEAD 请求报告 403 Unauthorized

这意味着什么?这强烈表明 S3 用户没有正确的执行 HEAD 请求的权限。 解决方案:再次检查 IAM 权限。 设置正确的权限后,错误消失了。

缺少 gitlab-registry.key 阻止容器存储库删除

如果您禁用 GitLab 实例的容器注册表并尝试删除具有容器存储库的项目,则会出现以下错误:

Errno::ENOENT: No such file or directory @ rb_sysopen - /var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key

在这种情况下,请按照以下步骤操作:

  1. gitlab.rb 中临时启用容器注册表的实例范围设置:

    gitlab_rails['registry_enabled'] = true
  2. 保存文件并重新配置 GitLab 以使更改生效。

  3. 再次尝试删除。

如果您仍然无法使用常用方法删除存储库,可以使用 GitLab Rails 控制台 强制删除项目:

# Path to the project you'd like to remove
prj = Project.find_by_full_path(<project_path>)

# The following will delete the project's container registry, so be sure to double-check the path beforehand!
if prj.has_container_registry_tags?
  prj.container_repositories.each { |p| p.destroy }
end

注册表服务监听 IPv6 地址而不是 IPv4

如果在您的 GitLab 服务器上 localhost 主机名解析为 IPv6 环回地址(::1),而 GitLab 期望注册表服务在 IPv4 环回地址(127.0.0.1)上可用,您可能会看到以下错误:

request: "GET /v2/ HTTP/1.1", upstream: "http://[::1]:5000/v2/", host: "registry.example.com:5005"
[error] 1201#0: *13442797 connect() failed (111: Connection refused) while connecting to upstream, client: x.x.x.x, server: registry.example.com, request: "GET /v2/<path> HTTP/1.1", upstream: "http://[::1]:5000/v2/<path>", host: "registry.example.com:5005"

要修复此错误,请在 /etc/gitlab/gitlab.rb 中将 registry['registry_http_addr'] 更改为 IPv4 地址。例如:

registry['registry_http_addr'] = "127.0.0.1:5000"

有关更多详细信息,请参阅问题 5449

使用 Google Cloud Storage (GCS) 时的推送失败和高 CPU 使用率

当将容器镜像推送到使用 GCS 作为后端的注册表时,您可能会收到 502 Bad Gateway 错误。在推送大镜像时,注册表也可能会遇到 CPU 使用率飙升。

当注册表使用 HTTP/2 协议与 GCS 通信时,会发生此问题。

解决方法是通过将 GODEBUG 环境变量设置为 http2client=0 来在注册表部署中禁用 HTTP/2。

有关更多信息,请参阅问题 1425