修复 docker buildx 编译后,manifest 包含 unknown 的问题

发布于 2024-10-07
省流:自 buildx 0.10 版本开始,就可能会出现这个问题,解决版本是 docker buildx 时增加参数 --provenance=false。 问题复现

省流:自 buildx 0.10 版本开始,就可能会出现这个问题,解决版本是 docker buildx 时增加参数 --provenance=false

问题复现

先来看示例,如有 Dockerfile:

FROM alpine
RUN uname -a > /os.txt

CMD cat /os.txt

通过以下命令构建 Multi-platform 镜像:

docker buildx create --driver docker-container --use
docker buildx build --platform linux/amd64,linux/arm64 --push --tag zhaojun1998/buildx-demo:latest .

推送后,通过 docker manifest inspect zhaojun1998/buildx-demo:latest 查看:

{
   "schemaVersion": 2,
   "mediaType": "application/vnd.oci.image.index.v1+json",
   "manifests": [
      {
         "mediaType": "application/vnd.oci.image.manifest.v1+json",
         "size": 668,
         "digest": "sha256:5a0b3d0151d5505f6ab6bf2f6824b2d8fdcfcd0ae7cf9f2403606c6fccaf27f2",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.oci.image.manifest.v1+json",
         "size": 668,
         "digest": "sha256:38da41f844e3bfae577f8b768b53a4980838bba97c6952aaae4debf3ea66cee7",
         "platform": {
            "architecture": "arm64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.oci.image.manifest.v1+json",
         "size": 566,
         "digest": "sha256:6749a2e66b9e0e9cfc751e215e1036385c9cfd17e68bc8feca771381ce8ce806",
         "platform": {
            "architecture": "unknown",
            "os": "unknown"
         }
      },
      {
         "mediaType": "application/vnd.oci.image.manifest.v1+json",
         "size": 566,
         "digest": "sha256:f0b8829a483c98b1bc7fd0a8b16765fe5b29cca632a13db971c0c3749df09c3f",
         "platform": {
            "architecture": "unknown",
            "os": "unknown"
         }
      }
   ]
}

这里面有几个问题:

  1. mediaType 版本为 application/vnd.oci.image.index.v1+json,最新版本为 v2。
  2. 下面有两个 platform.architectureplatform.osunknown 的数据项。

有些工具无法正常处理这种情况的镜像,如阿里的 image-syncer

解决办法

构建命令中增加 --provenance=false

docker buildx build --provenance=false --platform linux/amd64,linux/arm64 --tag zhaojun1998/buildx-demo:no-provenance --push .

然后再通过 docker manifest inspect zhaojun1998/buildx-demo:no-provenance 查看:

{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
   "manifests": [
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 696,
         "digest": "sha256:c2f325142a90e152e0ea9eca07f5de281e183028da40991d5354b08da913da62",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 696,
         "digest": "sha256:8211d0f98a9a8a6f4b87826a2cb78ae375e76c2e5d6fbaaee46709f9f422022d",
         "platform": {
            "architecture": "arm64",
            "os": "linux"
         }
      }
   ]
}

现在可以发现,上面的两个问题已经解决了。

已有镜像修复

上面的可以在新推送镜像时避免产生问题,但是对于已有镜像有时候不方便重新制作,可以通过以下办法修复已有镜像:

去除 unknown 部分

可以用以下脚本 fix-image.sh 来解决:

#!/bin/bash

set -v
set -e

# 检查是否提供了镜像地址
if [ -z "$1" ]; then
  echo "请提供镜像地址"
  exit 1
fi

IMAGE=$1

# 获取镜像的 manifest
MANIFEST=$(docker manifest inspect $IMAGE)

# 检查是否成功获取 manifest
if [ $? -ne 0 ]; then
  echo "获取 manifest 失败"
  exit 1
fi

echo "获取到 manifest 为: $MANIFEST"

# 解析 manifest,移除 architecture 或 os 为 unknown 的条目
FILTERED_MANIFEST=$(echo $MANIFEST | jq 'del(.manifests[] | select(.platform.architecture == "unknown" or .platform.os == "unknown"))')

echo "过滤后 manifest 为: $FILTERED_MANIFEST"

# 检查是否有剩余的条目
if [ $(echo $FILTERED_MANIFEST | jq '.manifests | length') -eq 0 ]; then
  echo "过滤后没有剩余的条目,退出"
  exit 1
fi

# 提取剩余条目的 digest
DIGESTS=$(echo $FILTERED_MANIFEST | jq -r '.manifests[].digest')

# 准备 docker manifest create 命令
NEW_IMAGE="${IMAGE}"   # 这里可以自行决定修复后的镜像是否和原来的 tag 一样,如增加 -fix
CREATE_CMD="docker manifest create $NEW_IMAGE"

for DIGEST in $DIGESTS; do
  CREATE_CMD="$CREATE_CMD --amend ${IMAGE%%:*}@$DIGEST"
done

# 执行 docker manifest create 命令
eval $CREATE_CMD

# 推送新的 manifest
docker manifest push $NEW_IMAGE

echo "新的 manifest 已创建并推送: $NEW_IMAGE"

使用办法为 ./fix-image.sh zhaojun1998/buildx-demo:latest,可以看到修复后的 manifest 变为了:

{
  "schemaVersion": 2,
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "manifests": [
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "size": 668,
      "digest": "sha256:d756e0a11dd550bb5ea6affd46352119aee6f16981fa588667c54929efa95589",
      "platform": {
        "architecture": "amd64",
        "os": "linux"
      }
    },
    {
      "mediaType": "application/vnd.oci.image.manifest.v1+json",
      "size": 668,
      "digest": "sha256:ef6cd7110abcd9eb9c1c402e46c6de4de6138e5e0ef6f7b84b81a56d64b84212",
      "platform": {
        "architecture": "arm64",
        "os": "linux"
      }
    }
  ]
}

升级镜像规范版本

根据自己的系统架构安装 regctl,然后执行命令:

regctl image mod zhaojun1998/buildx-demo:latest --to-docker --replace

再查看 manifest 为:

{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
   "manifests": [
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 587,
         "digest": "sha256:89d1d24005f61c825f1e07d819ce52c71c0c4ba96d7457d9b9f40dde59c31c78",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 587,
         "digest": "sha256:c1cd468c5476d0c33ca877a0dce7871d55550fdfbc00466f450f3bf63e5ea8ac",
         "platform": {
            "architecture": "arm64",
            "os": "linux"
         }
      }
   ]
}

参考

  • https://github.com/docker/buildx/issues/1507
  • https://github.com/docker/buildx/issues/1509#issuecomment-1378454396
  • https://github.com/docker/buildx/issues/1964#issuecomment-1644634461
  • https://stackoverflow.com/questions/75692640/modify-docker-image-with-older-manifest-format