完美解决:Harbor + Nginx 反向代理 HTTPS 后 Docker 登录超时问题

问题背景

在使用官方 Docker Compose 方式部署 Harbor 时,我们通常会选择在 Harbor 层面关闭 HTTPS,而在前端使用 Nginx 或其他网关来进行 SSL 卸载和反向代理,以实现通过 HTTPS (https://harbor.yourdomain.com) 访问。
然而,在这种经典架构下,当我们在客户端服务器上执行 docker login 时,经常会遇到一个令人困惑的超时错误:

(base) username@ubuntu-server:\~/config$ docker login harbor.yourhost.cn  
Username: username  
Password:  
Error response from daemon: Get "https://harbor.yourhost.cn/v2/": Get "http://harbor.yourhost.cn:8188/service/token?account=username&...": net/http: request canceled (Client.Timeout exceeded while awaiting headers)

这个错误提示非常具有迷惑性:明明我们访问的是 https:// 地址,为什么错误日志里却出现了一个 http:// 地址,并且还带了一个奇怪的端口号(比如这里的 8188)?本文将深入剖析这个问题背后的原因,并提供一劳永逸的解决方案。

错误原因分析

让我们来分解一下 docker login 的整个流程,看看问题出在哪里:

  1. 客户端发起首次请求: Docker 客户端向 https://harbor.yourhost.cn/v2/ 发起一个未经身份验证的请求,以探测 Harbor Registry 的版本和认证方式。
  2. Nginx 成功代理: Nginx 接收到这个 HTTPS 请求,解密流量,然后将其转发给后端的 Harbor 服务。在我们的案例中,Harbor 的 HTTP 服务监听在 8188 端口,所以 Nginx 将请求转发到了 http://<harbor-server-ip>:8188。
  3. Harbor 返回认证地址: Harbor 收到请求后,需要告诉 Docker 客户端:“请到我的认证服务(Token Service)获取一个令牌(Token)再回来。” 问题的关键就在这里!Harbor 如何确定自己的认证服务地址呢?它会查阅自己的配置文件。
  4. 错误的配置: 在我们的初始配置 harbor.yml 中,是这样设置的:
   \# harbor.yml (错误配置)  
   hostname: harbor.yourhost.cn  
   http:  
     port: 8188

Harbor 根据这个配置,认为自己的访问地址就是 http:// + hostname + http.port。因此,它给 Docker 客户端返回的认证地址就是 http://harbor.yourhost.cn:8188/service/token?...。

  1. 客户端请求认证失败: Docker 客户端非常“听话”,它拿到这个地址后,就立刻尝试去访问。但是,这个 8188 端口很可能只在 Harbor 服务器的内部网络中暴露,或者被防火墙阻挡,客户端根本无法从外部访问到。
  2. 最终结果: 请求无法建立连接,在等待一段时间后,客户端因超时而放弃,最终抛出了我们看到的 Client.Timeout exceeded 错误。

简单来说,Harbor 并不知道它正“生活”在一个 Nginx 的 HTTPS 反向代理之后。它错误地将自己的内部 HTTP 地址告诉了外部客户端,导致了通信失败。

解决方案:让 Harbor 感知到公开 URL

要解决这个问题,我们必须明确地告诉 Harbor,它的公开访问地址是什么。这需要通过修改 harbor.yml 文件中的一个关键参数:external_url

1. 修改 harbor.yml

进入你的 Harbor 安装目录,编辑 harbor.yml 文件。

  • 注释掉或删除 hostname 参数。
  • 添加 external_url 参数,并将其值设置为你完整的、公开的 HTTPS 地址。

修改后的配置如下:

\# harbor.yml (正确配置)

\# 注释掉或者直接删除 hostname  
\# hostname: harbor.yourhost.cn

\# http port 保持不变,这是 Harbor 内部监听的端口,供 Nginx 访问  
http:  
  port: 8188

\# https:  
  \# ... (保持注释状态,因为 SSL 由 Nginx 处理)

\# 这是关键!设置完整的外部访问URL,必须以 https:// 开头  
external\_url: https://harbor.yourhost.cn

这个 external\_url 参数会作为 Harbor 生成所有对外链接(包括 API、UI 和认证服务)的根地址。

2. 重新配置并启动 Harbor

保存 harbor.yml 文件后,你需要让这个新配置生效。在 Harbor 的安装目录下,执行以下命令:
# 1. 停止并移除旧的容器实例
# -v 选项会删除挂载的数据卷,如果需要保留镜像数据,请先备份或省略 -v
sudo docker-compose down -v

# 2. 根据修改后的 harbor.yml 重新生成所有服务的配置文件
# 这一步是必须的,它会将 external_url 的值写入到各个组件的配置中
sudo ./prepare

# 3. 重新启动所有 Harbor 服务
sudo docker-compose up -d

3. 验证结果

等待所有容器启动成功后(可以通过 docker-compose ps 查看状态),回到你的客户端服务器,再次尝试登录:

docker login harbor.yourhost.cn  
Username: username  
Password:  
Login Succeeded

现在,登录成功了!

附:推荐的 Nginx 配置

虽然问题主要出在 Harbor 的配置上,但一个健壮的 Nginx 反向代理配置同样重要。请确保你的配置包含了必要的请求头,特别是 X-Forwarded-Proto,它可以再次向后端应用表明原始请求的协议。

server {  
    listen 443 ssl http2;  
    server\_name harbor.yourhost.cn;

    \# SSL 证书配置  
    ssl\_certificate /path/to/your/fullchain.pem;  
    ssl\_certificate\_key /path/to/your/privkey.pem;  
    ssl\_protocols TLSv1.2 TLSv1.3;

    \# 安全与性能优化  
    add\_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;  
    client\_max\_body\_size 0; \# 允许上传大镜像

    location / {  
        \# 代理到Harbor的内部HTTP端口  
        proxy\_pass http://127.0.0.1:8188;

        \# 关键的代理头信息  
        proxy\_set\_header Host $host;  
        proxy\_set\_header X-Real-IP $remote\_addr;  
        proxy\_set\_header X-Forwarded-For $proxy\_add\_x\_forwarded\_for;  
        proxy\_set\_header X-Forwarded-Proto $scheme; \# $scheme 的值通常是 http 或 https  
        proxy\_set\_header X-Forwarded-Port $server\_port;

        \# WebSocket 支持 (用于Web UI)  
        proxy\_http\_version 1.1;  
        proxy\_set\_header Upgrade $http\_upgrade;  
        proxy\_set\_header Connection "upgrade";

        \# 超时设置  
        proxy\_connect\_timeout 600s;  
        proxy\_send\_timeout 600s;  
        proxy\_read\_timeout 600s;  
    }  
}

\# (可选) 将所有HTTP请求强制重定向到HTTPS  
server {  
    listen 80;  
    server\_name harbor.yourhost.cn;  
    return 301 https://$server\_name$request\_uri;  
}

总结

在部署 Harbor 并使用 Nginx 进行 HTTPS 反向代理时,docker login 超时是一个典型但容易解决的问题。其根源在于 Harbor 自身配置未能感知到外部访问的协议和地址。通过在 harbor.yml 中正确设置 external_url 为公开的 HTTPS 地址,即可彻底解决此问题,确保 Harbor 生成的认证地址正确无误。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容