使用Curl/JFrog CLI推送Docker镜像至Artifactory失败排查
TLDR
尝试用Curl/JFrog CLI推送Docker镜像,但上传后的镜像损坏,无法正常拉取使用。
背景
由于网络连接限制,必须通过API网关访问公司的Artifactory Docker镜像仓库。Docker登录等命令无法正常工作,因为它尝试连接子域名(例如my-registry.public-domain.com)。可以通过Artifactory API使用Curl/JFrog CLI推送/拉取文件,目标是仅通过API将有效的Docker镜像推送到该Artifactory。
操作步骤
构建Docker镜像
docker build . --file Dockerfile -t helloworld:v1.1
将镜像保存为本地文件
docker image save --output=helloworld-v1.1.tar helloworld:v1.1
解压.tar镜像文件
mkdir v1.1; tar -xvf helloworld-v1.1.tar -C v1.1
上传各层文件及manifest.json至Artifactory
jfrog rt upload "v1.1/*" my-registry/helloworld/ --url xyz.com/artifactory --user foo --password bar
(上述命令相当于递归执行Curl PUT上传v1.1/目录下的所有内容)
结果
各层文件和manifest.json已上传至Artifactory,但出现异常:
- Docker信息为空,提示:
Unable to extract Docker metadata for 'my-registry/helloworld/v1.1/' - 尝试从Openshift拉取镜像时出现以下错误(通过常规方式上传的镜像可正常拉取):
Failed to pull image "my-registry.my-artifactory/helloworld:v1.1": rpc error: code = Unknown desc = pinging container registry l-docker-dev-workspace-dev.a-artifactory.vt.ch: invalid status code from registry 400 (Bad Request)
提问
请问该异常的原因是什么?如何将.tar格式的Docker镜像传输至Artifactory,使其能像其他镜像一样正常使用?
异常原因
当前上传方式不符合Docker Registry API规范,Artifactory的Docker仓库需要遵循标准镜像结构与上传流程,具体问题点:
- 目录结构错误:
docker save导出的tar包解压后是镜像原始层文件和manifest,但Artifactory Docker仓库需要按镜像名称、标签、摘要组织的层级结构,而非直接将解压文件放到helloworld/v1.1/下。 - 元数据关联错误:镜像的
config.json引用路径不正确,manifest未正确关联到对应标签,Artifactory无法识别这是合法Docker镜像。 - 未触发元数据解析:直接上传文件不会触发Artifactory的Docker元数据提取流程,导致它无法识别镜像类型。
正确上传方法
方法1:使用JFrog CLI的Docker镜像上传功能(推荐)
JFrog CLI原生支持Docker镜像上传,无需手动解压tar包,自动遵循Docker Registry API规范:
# 配置JFrog CLI指向Artifactory实例 jfrog rt config --url="xyz.com/artifactory" --user="foo" --password="bar" # 上传本地镜像到Artifactory Docker仓库 jfrog rt docker-push helloworld:v1.1 my-registry/helloworld:v1.1
若网络限制导致无法直接用Docker客户端连接,添加--local参数让JFrog CLI模拟Registry交互:
jfrog rt docker-push helloworld:v1.1 my-registry/helloworld:v1.1 --local
方法2:手动按Docker Registry规范上传
若必须用Curl操作,需遵循以下步骤:
- 提取镜像核心元数据:
解压tar包后,查看manifest.json,找到Config字段对应的配置文件(如abc123.json),同时记录每个Layers字段中的层文件路径。 - 上传镜像配置文件:
先计算配置文件的SHA256哈希值(用sha256sum v1.1/abc123.json获取),再上传:curl -u foo:bar -X PUT "xyz.com/artifactory/my-registry/v2/helloworld/blobs/sha256:<config-sha256>" --data-binary "@v1.1/abc123.json" - 上传所有镜像层文件:
对每个层文件计算SHA256哈希,然后上传:curl -u foo:bar -X PUT "xyz.com/artifactory/my-registry/v2/helloworld/blobs/sha256:<layer-sha256>" --data-binary "@v1.1/def456/layer.tar" - 上传修正后的manifest:
修改解压后的manifest.json,确保config和layers的digest字段对应已上传的SHA256值,然后上传到标签路径:curl -u foo:bar -X PUT "xyz.com/artifactory/my-registry/v2/helloworld/manifests/v1.1" -H "Content-Type: application/vnd.docker.distribution.manifest.v2+json" --data-binary "@modified-manifest.json"
方法3:通用仓库中转+Artifactory转换
- 将tar包上传到Artifactory通用本地仓库(如
generic-repo):jfrog rt upload helloworld-v1.1.tar generic-repo/docker-images/ - 在Artifactory中配置匹配Docker结构的Repository Layout,通过Promote功能或REST API将tar包转换为Docker仓库中的合法镜像(需Artifactory具备对应权限与配置)。
验证
上传完成后,在Artifactory Docker仓库中查看helloworld:v1.1的镜像信息,确认元数据正常显示后,再尝试从Openshift拉取测试。
内容的提问来源于stack exchange,提问作者Don_Malocran

