迁移到 Amazon ECR 存储库时自动识别重复的容器映像 - AWS 规范指引

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

迁移到 Amazon ECR 存储库时自动识别重复的容器映像

Rishabh Yadav 和 Rishi Singla,Amazon Web Services

Summary

此模式提供了一种自动解决方案,可识别存储在不同容器存储库中的映像是否重复。如果您计划将映像从其他容器存储库迁移到 Amazon Elastic Container Registry(Amazon ECR),此检查非常有用。

有关基础信息,此模式还描述了容器映像的组件,例如映像摘要、清单和标签。当您计划迁移到 Amazon ECR 时,您可能会决定通过比较映像摘要来跨容器注册表同步您的容器映像。迁移容器映像之前,您需要检查这些映像是否已存在于 Amazon ECR 存储库中,以防止重复。但是,可能很难通过比较映像摘要来检测重复,并且这可能会导致初始迁移阶段就出现问题。 此模式比较了存储在不同容器注册表中的两个相似映像的摘要,并解释了摘要不同的原因,以帮助您准确比较映像。

先决条件和限制

架构

容器映像组件

下图阐明了容器映像的其中一些组件。有关这些组件的描述,可参阅图表后面的内容。

清单、配置、文件系统层和摘要。

术语和定义

开放容器倡议(OCI)映像规范中定义了以下术语。

  • 注册表:用于映像存储和管理的服务。

  • 客户端:与注册机构通信并处理本地映像的工具。

  • 推送:将映像上传到注册表的过程。

  • 拉取:从注册表下载映像的过程。

  • Blob:由注册表存储且可通过摘要进行寻址的二进制形式内容。

  • 索引:用于识别不同计算机平台(例如 x86-64 或 ARM 64 位)或媒体类型的多个映像清单的构造。有关更多信息,请参阅 OCI 映像索引规范

  • 清单:用于定义通过清单端点上传的映像或构件的 JSON 文档。清单可以通过使用描述符来引用存储库中的其他 Blob。有关更多信息,请参阅 OCI 映像清单规范

  • 文件系统层:映像的系统库和其他依赖项。

  • 配置:包含构件元数据且可在清单中引用的 blob。有关更多信息,请参阅 OCI 映像设置规范

  • 对象或构件:存储为 blob 并与具有配置的配套清单关联的概念性内容项。

  • 摘要:根据清单内容的加密哈希值创建的唯一标识符。映像摘要有助于唯一标识不可变容器映像。使用其摘要拉取映像时,无论在何种操作系统或架构上,每次都会下载相同的映像。有关更多信息,请参阅 OCI 映像规范

  • 标签:人类可读的清单标识符。相较于不可变的映像摘要,标签是动态变化的。指向映像的标签可以更改并从一个映像移动到另一个映像,不过其底层映像摘会保持不变。

目标架构

下图显示了此模式提供的解决方案的高级架构,即通过比较存储在 Amazon ECR 和私有存储库中的映像来识别重复的容器映像。

使用 CodePipeline 和 CodeBuild自动检测重复项。

工具

AWS 服务

  • CloudFormation帮助您设置 AWS 资源,快速一致地配置资源,并在资源的整个生命周期中跨地区对其 AWS 账户 进行管理。

  • AWS CodeBuild 是一项完全托管式构建服务,可编译源代码、运行单元测试和生成部署就绪的构件。

  • AWS CodeCommit 是一项版本控制服务,可帮助您私下存储和管理 Git 存储库,而无需管理自己的源代码控制系统。

  • AWS CodePipeline 可帮助您快速对软件发布过程的不同阶段进行建模和配置,并自动执行持续发布软件变更所需步骤。

  • Amazon Elastic Container Registry(Amazon ECR)是一项安全、可扩展且可靠的托管容器映像注册表服务。

代码

此模式的代码可在存储库中找到,用于识别 GitHub 存储库之间重复的容器镜像的自动解决方案

最佳实践

操作说明

Task说明所需技能

从 Amazon ECR 公有存储库中拉取映像。

从终端运行以下命令,以从 Amazon ECR 公有存储库中拉取映像 amazonlinux

$~ % docker pull public.ecr.aws/amazonlinux/amazonlinux:2018.03

将映像拉取到本地计算机后,您将看到以下拉取摘要,而该摘要可表示映像索引。

2018.03: Pulling from amazonlinux/amazonlinux 4ddc0f8d367f: Pull complete Digest: sha256:f972d24199508c52de7ad37a298bda35d8a1bd7df158149b381c03f6c6e363b5 Status: Downloaded newer image for public.ecr.aws/amazonlinux/amazonlinux:2018.03 public.ecr.aws/amazonlinux/amazonlinux:2018.03
应用程序开发人员、AWS DevOps、AWS 管理员

将映像推送到 Amazon ECR 私有存储库。

  1. 在美国东部(弗吉尼亚州北部)区域(us-east-1)中,创建名为 test_ecr_repository 的私有 Amazon ECR 存储库。

    $~ % aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin <account-id>.dkr.ecr.us-east-1.amazonaws.com Login Succeeded

    其中<account-id>指的是你的 AWS 账户。

  2. 标记您之前拉取的本地映像。使用值 public.ecr.aws/amazonlinux/amazonlinux:2018.03 并将其推送至 Amazon ECR 私有存储库。

    $~ % docker tag public.ecr.aws/amazonlinux/amazonlinux:2018.03 <account-id>.dkr.ecr.us-east-1.amazonaws.com/test_ecr_repository:latest $~ % docker push <account-id>.dkr.ecr.us-east-1.amazonaws.com/test_ecr_repository:latest

    当您将映像推送到 Amazon ECR 存储库时,Docker 将推送底层映像而不是映像索引。

    The push refers to repository [<account-id>.dkr.ecr.us-east-1.amazonaws.com/test_ecr_repository] d5655967c2c4: Pushed latest: digest: sha256:52db9000073d93b9bdee6a7246a68c35a741aaade05a8f4febba0bf795cdac02 size: 529
AWS 管理员、AWS DevOps、应用程序开发者

从 Amazon ECR 私有存储库中拉取相同的映像。

  1. 从终端运行以下命令,以拉取您之前推送到 Amazon ECR 私有存储库的映像。

    $~ % docker pull <account-id>.dkr.ecr.us-east-1.amazonaws.com/test_ecr_repository:latest latest: Pulling from test_ecr_repository Digest: sha256:52db9000073d93b9bdee6a7246a68c35a741aaade05a8f4febba0bf795cdac02 Status: Image is up to date for <account-id>.dkr.ecr.us-east-1.amazonaws.com/test_ecr_repository:latest <account-id>.dkr.ecr.us-east-1.amazonaws.com/test_ecr_repository:latest

    此映像的摘要可与您推送到 Amazon ECR 私有存储库的映像摘要匹配,并且可表示底层映像。此值与您从公有存储库中拉取的映像索引不匹配。

  2. 要进行验证,请按摘要检索映像索引。 

    curl -k -H "Authorization: Bearer $TOKEN" https://public.ecr.aws/v2/amazonlinux/amazonlinux/manifests/sha256:f972d24199508c52de7ad37a298bda35d8a1bd7df158149b381c03f6c6e363b55 { "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", "manifests": [ { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 529, "digest": "sha256:52db9000073d93b9bdee6a7246a68c35a741aaade05a8f4febba0bf795cdac02", "platform": { "architecture": "amd64", "os": "linux" } } ] }
应用程序开发人员、AWS DevOps、AWS 管理员
Task说明所需技能

查找存储在 Amazon ECR 公有存储库中的映像的清单。

从终端运行以下命令,以从 Amazon ECR 公有存储库中拉取映像 public.ecr.aws/amazonlinux/amazonlinux:2018.03 的清单。

$~ % docker manifest inspect public.ecr.aws/amazonlinux/amazonlinux:2018.03 { "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", "manifests": [ { "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "size": 529, "digest": "sha256:52db9000073d93b9bdee6a7246a68c35a741aaade05a8f4febba0bf795cdac02", "platform": { "architecture": "amd64", "os": "linux" } } ] }
AWS 管理员、AWS DevOps、应用程序开发者

查找存储在 Amazon ECR 私有存储库中的映像的清单。

从终端运行以下命令,以从 Amazon ECR 私有存储库中拉取映像 <account-id>.dkr.ecr.us-east-1.amazonaws.com/test_ecr_repository:latest 的清单。

$~ % docker manifest inspect <account-id>.dkr.ecr.us-east-1.amazonaws.com/test_ecr_repository:latest { "schemaVersion": 2, "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "config": { "mediaType": "application/vnd.docker.container.image.v1+json", "size": 1477, "digest": "sha256:f7cee5e1af28ad4e147589c474d399b12d9b551ef4c3e11e02d982fce5eebc68" }, "layers": [ { "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", "size": 62267075, "digest": "sha256:4ddc0f8d367f424871a060e2067749f32bd36a91085e714dcb159952f2d71453" } ] }
AWS DevOps、AWS 系统管理员、应用程序开发者

将 Docker 拉取的摘要与 Amazon ECR 私有存储库中映像的清单摘要进行比较。

另一个问题是,为什么 docker pull 命令提供的摘要与映像 <account-id>.dkr.ecr.us-east-1.amazonaws.com/test_ecr_repository:latest 的清单摘要不同。

用于 docker pull 的摘要表示存储在注册表中的映像清单的摘要。此摘要可视为哈希链的根,因为清单包含将下载并导入到 Docker 中的内容的哈希值。

Docker 中使用的映像 ID 可以在此清单 config.digest 中找到。这表示 Docker 使用的映像配置。因此,您可以这样说,清单就像是信封,而映像就是信封的内容。清单摘要始终与映像 ID 不同。但是,特定的清单应始终会创建相同的映像 ID。由于清单摘要是一个哈希链,因此我们不能保证指定映像 ID 的哈希链总是相同。在大多数情况下,它会创建相同的摘要,尽管 Docker 无法对此做出保证。清单摘要中可能存在的区别源于 Docker 没有在本地存储用 gzip 压缩的 blob。因此,尽管未压缩的内容保持不变,但导出层可能会创建不同的摘要。映像 ID 可确认未压缩的内容是否相同;也就是说,映像 ID 现在是内容可寻址标识符(chainID)。

要确认此信息,您可以比较 Amazon ECR 公有和私有存储库上的 docker inspect 命令的输出:

  1. 从终端运行以下命令,以获取存储在 Amazon ECR 公有存储库中的映像。

    $~ % docker inspect public.ecr.aws/amazonlinux/amazonlinux:2018.03

    有关命令的输出,请参阅其他信息部分。

  2. 从终端运行以下命令,以获取存储在 Amazon ECR 私有存储库中的映像。

    $~ % docker inspect <account-id>.dkr.ecr.us-east-1.amazonaws.com/test_ecr_repository:latest

    有关命令的输出,请参阅其他信息部分。

结果验证了两个映像具有相同的映像 ID 摘要和层摘要。

身份证:f7cee5e1af28ad4e147589c474d399b12d9b551ef4c3e11e02d982fce5eebc68

层:d5655967c2c4e8d68f8ec7cf753218938669e6c16ac1324303c073c736a2e2a2

此外,摘要的基础是本地管理的对象的字节(本地文件是容器映像层的 tar)或推送到注册表服务器的 blob。但是,当您将 blob 推送到注册表时,tar 会被压缩,并在压缩的 tar 文件中计算摘要。因此,docker pull 摘要值的差异源于在注册表(Amazon ECR 私有或公有)级别应用的压缩。

注意

此解释专门针对使用 Docker 客户端的情况。您不会在其他客户端(例如 nerdctlFinch)上看到这种行为,因为在推拉操作期间,其不会自动压缩映像。

AWS DevOps、AWS 系统管理员、应用程序开发者
Task说明所需技能

克隆存储库。

将此模式的 GitHub 存储库克隆到本地文件夹:

$git clone https://github.com/aws-samples/automated-solution-to-identify-duplicate-container-images-between-repositories
AWS 管理员,AWS DevOps

设置 CI/CD 管道。

GitHub 存储库中包含一个用于创建 CloudFormation 堆栈的.yaml文件,用于在其中设置管道 AWS CodePipeline。

  1. 登录 AWS 管理控制台 并打开CloudFormation 控制台

  2. 使用模板 pipeline.yaml 文件创建堆栈,而该文件位于克隆的存储库的 code 文件夹中。

  3. 接受或更改参数的默认值。为以下字段指定值:

    • 堆栈名称

    • ArtifactStoreBucketName— 将用于存储 AWS CodePipeline 工件的现有 S3 存储桶

    • OutputBucket— 现有 S3 存储桶,将用于存储重复图像的 URIs

    • SourceImageFile— 名为的现有文本文件input.txt,其中包含 URIs 来自公共存储库的图像,将对照 Amazon ECR 私有存储库进行检查以检测重复情况

  4. 审查并调整堆栈选项,然后选择提交以运行模板。

该管道将设置为两个阶段(CodeCommit 以及 CodeBuild,如架构图所示),以识别私有存储库中也存在于公共存储库中的图像。管道配置有以下资源:

  • CodePipeline 用于编排部署管道。

  • 用于 CodeCommit 存储 bash 脚本和输入文件的存储库。bash 脚本用于比较公共存储库和私有存储库 IDs 中的容器镜像以查找重复项。此检查是在单个存储库中指定的所有存储库 AWS 账户 中执行的 AWS 区域。

  • 一个调用 bash 脚本来识别已存在于 Amazon ECR 存储库中的图像的 CodeBuild 项目。

  • 必要的 IAM 角色,具有访问权限。

  • 一个 S3 存储桶,用于存储包含图像的输出文件 URIs。

  • 另一个用于存储 CodePipeline 工件的 S3 存储桶。 

AWS 管理员,AWS DevOps

填充 CodeCommit 存储库。

要填充 CodeCommit 存储库,请执行以下步骤:

  1. 打开CodeCommit 控制台并导航到您创建 CloudFormation 堆栈 AWS 区域 的位置。

  2. 使用列表中的 CloudFormation 脚本找到您配置的存储库,选择 “克隆 URL”,然后复制 HTTPS URL 协议以连接到存储库。

  3. 打开命令提示符,使用您在上一步中复制的 HTTPS URL 运行 git clone 命令。

  4. 导航到根目录。创建一个名为的文件,input.txt并使用您要在私有 Amazon ECR 存储库中搜索 URIs 的 Amazon ECR 公共映像注册表填充此文件。

  5. 将文件script.shbuildspec.yml、和input.txt从存储库的本地副本复制到克隆 CodeCommit 的 GitHub 存储库中识别存储库之间重复的容器映像的自动解决方案

  6. 使用以下命令 CodeCommit 将文件上传到:

    git add . git commit -m "added input files" git push
AWS 管理员,AWS DevOps

清理。

为避免将来产生费用,请按照以下步骤删除资源:

  1. 导航到存储项目的 S3 存储桶 CodePipeline ,然后清空该存储桶。

  2. 导航到存储重复图像的 S3 存储桶 URIs,然后清空该存储桶。

  3. 导航到 CloudFormation 控制台并删除您为设置管道而创建的堆栈。

AWS 管理员

问题排查

问题解决方案

当您尝试从终端或命令行推送、拉取 CodeCommit 存储库或以其他方式与仓库交互时,系统会提示您提供用户名和密码,并且必须为您的 IAM 用户提供 Git 证书。

出现此错误的最常见原因如下:

  • 您的本地计算机运行的操作系统不支持凭证管理,或者未安装凭证管理实用程序。

  • 您的 IAM 用户的 Git 凭证尚未保存到其中一个凭证管理系统中。

根据您的操作系统和本地环境,您可能需要安装凭证管理器、配置操作系统随附的凭证管理器或自定义您的本地环境以使用凭证存储。例如,如果您的计算机运行的是 macOS,您可以使用 Keychain Access 实用程序存储您的凭证。如果您的计算机运行的是 Windows,您可以使用随 Windows 版 Git 安装的 Git Credential Manager。有关更多信息,请参阅文档中的使用 Git 凭据为 HTTPS 用户设置和 Git CodeCommit 文档中的凭据存储

将映像推送到 Amazon ECR 存储库时,您会遇到 HTTP 403 或“没有基本的身份验证凭证”错误。

即使你已使用 aws ecr 命令成功向 Docker 进行了身份验证,你也可能会在 docker push 或 docker pull 命令中遇到这些错误消息。 get-login-password已知原因包括:

  • 您已验证到其他区域。有关更多信息,请参阅 Amazon ECR 文档中的私有注册表身份验证

  • 您已进行身份验证以推送到您没有权限的存储库。有关更多信息,请参阅 Amazon ECR 文档中的私有存储库策略

  • 您的令牌已过期。使用 GetAuthorizationToken 操作获取的令牌,默认有效期为 12 小时。

相关资源

附加信息

Amazon ECR 公有存储库中映像的 Docker 检查输出

[ { "Id": "sha256:f7cee5e1af28ad4e147589c474d399b12d9b551ef4c3e11e02d982fce5eebc68", "RepoTags": [ "<account-id>.dkr.ecr.us-east-1.amazonaws.com/test_ecr_repository:latest", "public.ecr.aws/amazonlinux/amazonlinux:2018.03" ], "RepoDigests": [ "<account-id>.dkr.ecr.us-east-1.amazonaws.com/test_ecr_repository@sha256:52db9000073d93b9bdee6a7246a68c35a741aaade05a8f4febba0bf795cdac02", "public.ecr.aws/amazonlinux/amazonlinux@sha256:f972d24199508c52de7ad37a298bda35d8a1bd7df158149b381c03f6c6e363b5" ], "Parent": "", "Comment": "", "Created": "2023-02-23T06:20:11.575053226Z", "Container": "ec7f2fc7d2b6a382384061247ef603e7d647d65f5cd4fa397a3ccbba9278367c", "ContainerConfig": { "Hostname": "ec7f2fc7d2b6", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Cmd": [ "/bin/sh", "-c", "#(nop) ", "CMD [\"/bin/bash\"]" ], "Image": "sha256:c1bced1b5a65681e1e0e52d0a6ad17aaf76606149492ca0bf519a466ecb21e51", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": {} }, "DockerVersion": "20.10.17", "Author": "", "Config": { "Hostname": "", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Cmd": [ "/bin/bash" ], "Image": "sha256:c1bced1b5a65681e1e0e52d0a6ad17aaf76606149492ca0bf519a466ecb21e51", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": null }, "Architecture": "amd64", "Os": "linux", "Size": 167436755, "VirtualSize": 167436755, "GraphDriver": { "Data": { "MergedDir": "/var/lib/docker/overlay2/c2c2351a82b26cbdf7782507500e5adb5c2b3a2875bdbba79788a4b27cd6a913/merged", "UpperDir": "/var/lib/docker/overlay2/c2c2351a82b26cbdf7782507500e5adb5c2b3a2875bdbba79788a4b27cd6a913/diff", "WorkDir": "/var/lib/docker/overlay2/c2c2351a82b26cbdf7782507500e5adb5c2b3a2875bdbba79788a4b27cd6a913/work" }, "Name": "overlay2" }, "RootFS": { "Type": "layers", "Layers": [ "sha256:d5655967c2c4e8d68f8ec7cf753218938669e6c16ac1324303c073c736a2e2a2" ] }, "Metadata": { "LastTagTime": "2023-03-02T10:28:47.142155987Z" } } ]

Amazon ECR 私有存储库中映像的 Docker 检查输出

[ { "Id": "sha256:f7cee5e1af28ad4e147589c474d399b12d9b551ef4c3e11e02d982fce5eebc68", "RepoTags": [ "<account-id>.dkr.ecr.us-east-1.amazonaws.com/test_ecr_repository:latest", "public.ecr.aws/amazonlinux/amazonlinux:2018.03" ], "RepoDigests": [ "<account-id>.dkr.ecr.us-east-1.amazonaws.com/test_ecr_repository@sha256:52db9000073d93b9bdee6a7246a68c35a741aaade05a8f4febba0bf795cdac02", "public.ecr.aws/amazonlinux/amazonlinux@sha256:f972d24199508c52de7ad37a298bda35d8a1bd7df158149b381c03f6c6e363b5" ], "Parent": "", "Comment": "", "Created": "2023-02-23T06:20:11.575053226Z", "Container": "ec7f2fc7d2b6a382384061247ef603e7d647d65f5cd4fa397a3ccbba9278367c", "ContainerConfig": { "Hostname": "ec7f2fc7d2b6", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Cmd": [ "/bin/sh", "-c", "#(nop) ", "CMD [\"/bin/bash\"]" ], "Image": "sha256:c1bced1b5a65681e1e0e52d0a6ad17aaf76606149492ca0bf519a466ecb21e51", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": {} }, "DockerVersion": "20.10.17", "Author": "", "Config": { "Hostname": "", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], "Cmd": [ "/bin/bash" ], "Image": "sha256:c1bced1b5a65681e1e0e52d0a6ad17aaf76606149492ca0bf519a466ecb21e51", "Volumes": null, "WorkingDir": "", "Entrypoint": null, "OnBuild": null, "Labels": null }, "Architecture": "amd64", "Os": "linux", "Size": 167436755, "VirtualSize": 167436755, "GraphDriver": { "Data": { "MergedDir": "/var/lib/docker/overlay2/c2c2351a82b26cbdf7782507500e5adb5c2b3a2875bdbba79788a4b27cd6a913/merged", "UpperDir": "/var/lib/docker/overlay2/c2c2351a82b26cbdf7782507500e5adb5c2b3a2875bdbba79788a4b27cd6a913/diff", "WorkDir": "/var/lib/docker/overlay2/c2c2351a82b26cbdf7782507500e5adb5c2b3a2875bdbba79788a4b27cd6a913/work" }, "Name": "overlay2" }, "RootFS": { "Type": "layers", "Layers": [ "sha256:d5655967c2c4e8d68f8ec7cf753218938669e6c16ac1324303c073c736a2e2a2" ] }, "Metadata": { "LastTagTime": "2023-03-02T10:28:47.142155987Z" } } ]