使用 unexpected end of JSON input 进行图片拉取失败
目录
问题描述根因故障排查步骤 1 - 检查 LB 监听器配置步骤 2 - 使用显式协议进行复现步骤 3 - 查看运行时调试日志解决方案临时解决方案 - 在 Harbor 访问地址中固定 HTTP 端口长期修复 - 通过 HTTPS 暴露 Harbor说明问题描述
当从通过仅 HTTP Ingress 暴露的 Harbor 实例拉取镜像时,容器运行时会报出如下错误:
使用同一个 Harbor 的流水线也可能表现为同一根因的另一种错误:
即使满足以下条件,该问题仍可在使用 containerd / CRI-O / Podman 作为容器运行时的 Kubernetes 集群中复现:
- Harbor 地址可达。
- 凭据正确,且
login成功。 - 在浏览器中 Harbor UI 完全正常。
根因
当且仅当同时满足以下所有条件时,会出现该问题:
- Harbor 通过 仅 HTTP 的 Ingress 暴露(Ingress 上未配置 TLS)。
- 集群的 LB(负载均衡器)同时监听 80 和 443 端口,且 443 监听器具有有效的默认证书。
- 拉取镜像的客户端是遵循常见“先 HTTPS,失败后回退 HTTP”约定的容器运行时(containerd、CRI-O、Podman)。
在这些条件下:
- 由于镜像引用中没有显式指定协议或端口,运行时会先尝试 HTTPS。
- LB 由于 443 端口上存在默认证书,因此能够接受 TLS 握手。
- 但是,Ingress 没有针对 Harbor 主机名配置 HTTPS 路由规则,因此 LB 会对该 HTTPS 请求返回
404。 - 由于 TLS 握手已经成功,运行时 不会回退到 HTTP。它会将该
404视为最终响应,并尝试将其解析为 registry 返回值,从而产生unexpected end of JSON input。
故障排查
步骤 1 - 检查 LB 监听器配置
确认暴露 Harbor 的 LB 是否同时监听 80 和 443 端口,并且 443 端口具有可用的默认证书。如果 443 端口未监听,或者握手失败(没有默认证书),则该问题不适用,应从其他方向排查。
步骤 2 - 使用显式协议进行复现
在出现该故障的节点上,通过显式指定端口以 HTTP 方式拉取镜像。如果这样可以成功,而默认(未指定协议)的拉取失败,则可确认诊断结果:
步骤 3 - 查看运行时调试日志
在受影响的节点上,为容器运行时开启调试日志,并查找表明运行时在成功完成 TLS 握手后跳过 HTTP 端点的消息,例如:
如果看到此消息(或看到运行时在成功 TLS 握手后切换到 HTTPS),即可确认根因。
解决方案
临时解决方案 - 在 Harbor 访问地址中固定 HTTP 端口
在 Harbor 访问地址中显式固定 HTTP 端口,可强制客户端使用 HTTP,从而绕过该问题。在配置 Harbor 地址的任何位置使用 http://<HARBOR_HOST>:80,这样每个镜像引用都会解析为 <HARBOR_HOST>:80/<PROJECT>/<IMAGE>:<TAG>。
在某些情况下,客户端必须重启后新地址才能生效——例如,已经拉取失败的工作负载需要重启,才能使用固定端口后的地址重新拉取。
长期修复 - 通过 HTTPS 暴露 Harbor
对于生产环境,推荐的修复方式是通过 HTTPS 暴露 Harbor。完整流程请参见 配置 HTTPS。
在 Ingress 上正确配置 HTTPS 后,LB 在 443 端口返回的 404 将不再出现,运行时也能在无需固定端口的情况下正确访问 Harbor。在 HTTPS 配置生效后,请从集成地址中移除端口固定配置。
说明
- 相同根因,不同错误。 拉取 manifest 的流水线可能会报告
invalid character '<' looking for beginning of value,而不是unexpected end of JSON input。二者都源自同一个 HTML/空内容的404响应。 - 为什么不在运行时中修复? “先 HTTPS、在成功握手后不回退”的行为是大多数容器运行时遵循的已知约定,这不是 Harbor 或此 operator 能够更改的内容。