将应用镜像发布到 ECR

AWSBeginner
立即练习

介绍

团队有一个等待交付的小型 HTTP 应用。将预置代码打包为容器镜像,保存到 Amazon Elastic Container Registry(ECR),并证明指定版本能够启动并响应请求。保留独立的参照仓库,最后清理自己创建的资源。

请先完成 AWS Foundations 和 IAM 身份策略基础。本实验介绍 Docker 概念,不需要编写应用代码或使用个人 AWS 账户。独立 VM 已准备好 AWS CLI、Docker、Dockerfile 和应用源码。在上方 AWS View 观察仓库并测试应用,在下方 Terminal 执行命令;两个区域读取同一资源状态。

认证考点

认证 考试任务 实践内容
Developer – Associate (DVA-C02) 任务 3.1,技能 3.1.1 将应用代码及其运行时打包为可部署的容器镜像。

实验概览

概念图:构建预置应用,将镜像推送到 ECR,再按指定摘要拉取并运行能够响应 HTTP 请求的 Docker 容器。

创建不可变镜像仓库

本步骤中,创建用于保存应用版本的仓库。

镜像将应用文件及其运行时打包在一起。容器是镜像的运行实例。镜像注册表负责保存和分发镜像,ECR 仓库则组织一个应用的镜像。保存镜像并不会启动应用。

进入预置工作目录并查看 Dockerfile。cat 用于输出文本文件:

cd /home/labex/project
cat application/Dockerfile

FROM 选择 Python 基础镜像,COPY 添加应用文件,USER 选择非特权用户,CMD 定义启动命令。基础镜像的 sha256 引用固定了它的内容,并不是稍后构建的应用镜像的摘要。

创建 ct-demo 仓库。标签是容易识别的镜像名称,例如 release-1。IMMUTABLE 禁止用不同内容替换已有标签,使发布标签保持可靠:

aws ecr create-repository \
  --repository-name ct-demo \
  --image-tag-mutability IMMUTABLE

响应包含 Docker 使用的仓库地址 repositoryUri,以及 imageTagMutability: IMMUTABLE。将地址保存为 shell 变量。$(...) 捕获命令输出,--query 选择字段,--output text 输出不带 JSON 引号的文本:

REPOSITORY_URI=$(aws ecr describe-repositories \
  --repository-names ct-demo \
  --query 'repositories[0].repositoryUri' \
  --output text)
echo "$REPOSITORY_URI"

保持此 Terminal 会话打开,以便后续继续使用变量。在上方 AWS View 中,ct-demo 应显示零个镜像,并与 ct-reference 同时出现。参照仓库属于另一个应用,不在你的清理范围内。

构建并发布应用版本

本步骤中,构建预置应用并将镜像推送到 ECR。

Docker 推送镜像之前需要登录注册表。注册表地址是仓库 URI 第一个斜杠之前的部分。${REPOSITORY_URI%%/*} 去除该斜杠及其后的内容:

REGISTRY=${REPOSITORY_URI%%/*}
echo "$REGISTRY"

让 AWS CLI 获取短期 ECR 登录密码,并直接通过管道传给 Docker。| 将前一个命令的输出传给下一个命令;--password-stdin 避免密码出现在命令参数或 Terminal 输出中。不要自行输出或保存密码:

aws ecr get-login-password | docker login \
  --username AWS \
  --password-stdin "$REGISTRY"

Docker 显示 Login Succeeded。登录只允许当前身份获准执行的注册表操作,不会授予对所有仓库的访问权限。

从预置 application 目录构建镜像。-t 使用仓库 URI 和发布标签为结果命名。最后一个参数是构建上下文,其中的文件可被 COPY 使用:

docker build -t "${REPOSITORY_URI}:release-1" application

推送镜像。Docker 上传文件系统层及描述镜像内容的 manifest:

docker push "${REPOSITORY_URI}:release-1"

完成时 Docker 输出 sha256:... 摘要。摘要标识精确的镜像内容,标签则标识一个名称。即使某个仓库允许修改标签,摘要仍然对应原始内容。

查询 ECR,确认已保存该版本,并将摘要保存为变量:

aws ecr describe-images \
  --repository-name ct-demo \
  --image-ids imageTag=release-1 \
  --query 'imageDetails[0].{Tags:imageTags,Digest:imageDigest}'
IMAGE_DIGEST=$(aws ecr describe-images \
  --repository-name ct-demo \
  --image-ids imageTag=release-1 \
  --query 'imageDetails[0].imageDigest' \
  --output text)

ECR 返回的摘要应与 docker push 输出一致。在上方 AWS View 中,ct-demo 应包含 release-1 及其摘要。应用此时仍未运行,下一步再启动它。

运行精确的已发布镜像

本步骤中,按摘要取回发布镜像并测试实际应用容器。

先删除本地构建的应用镜像。docker image rm 删除本地镜像引用,不会删除 ECR 中保存的镜像;下一次拉取将获取已发布版本:

docker image rm "${REPOSITORY_URI}:release-1"

使用 @ 将 URI 和摘要组合,选择精确的已发布内容:

docker pull "${REPOSITORY_URI}@${IMAGE_DIGEST}"

Docker 应报告相同摘要及下载成功。将这个摘要对应的镜像启动为 ct01-app。--detach 让容器在后台运行,--publish 127.0.0.1:8081:8000 将主机 8081 端口连接到应用的 8000 端口。回环地址将直接访问限制在此 VM 内。镜像已选择非特权用户;--read-only 将根文件系统设为只读,--cap-drop ALL 移除 Linux capabilities,no-new-privileges 防止获得额外权限:

docker run --detach \
  --name ct01-app \
  --read-only \
  --cap-drop ALL \
  --security-opt no-new-privileges \
  --memory 256m \
  --publish 127.0.0.1:8081:8000 \
  "${REPOSITORY_URI}@${IMAGE_DIGEST}"

容器 ID 说明 Docker 接受了启动请求,但不能证明应用健康。实际请求应用:curl --fail 在 HTTP 错误时返回失败,--retry-all-errors 重试启动阶段的连接错误,包括短暂的连接重置:

curl --fail --silent --show-error \
  --retry 5 --retry-all-errors --retry-delay 1 \
  http://127.0.0.1:8081/

预期得到包含 "release": "release-1" 和容器主机名的 JSON。查看日志,确认实际请求已发生:

docker logs ct01-app

预期看到 served application request,主机名和时间戳可能变化。在上方 AWS View 点击 Request application,响应也应显示 release-1;该按钮会向实际容器发起 HTTP 请求。 真实 AWS View 检查点:ECR 保存 release-1 及其摘要,Docker 应用返回相同版本;摘要和主机名是此轮运行的示例值。

清理应用资源

本步骤中,停止应用并删除本地镜像及 ECR 仓库,同时保留参照仓库。

先停止容器,再删除它。删除容器并不会删除镜像:

docker stop ct01-app
docker rm ct01-app

现在没有运行容器使用此镜像,可以删除本地摘要引用:

docker image rm "${REPOSITORY_URI}@${IMAGE_DIGEST}"

按摘要删除 ECR 中的镜像。imageDigest 选择精确内容,而不是依赖标签:

aws ecr batch-delete-image \
  --repository-name ct-demo \
  --image-ids imageDigest="$IMAGE_DIGEST"

响应应在 imageIds 中列出已删除镜像,failures 应为空。删除现在已经空的应用仓库:

aws ecr delete-repository --repository-name ct-demo

确认参照仓库仍然存在:

aws ecr describe-repositories \
  --query 'repositories[].repositoryName'

预期只看到 ct-reference。最后删除 Docker 保存的注册表登录信息:

docker logout "$REGISTRY"

在上方 AWS View 中,ct-demo 和应用容器应消失,参照仓库应保留。HTTP 请求失败本身不能证明资源已清理,因此你还检查了仓库清单,并删除了实际容器及镜像。

总结

你创建了不可变 ECR 仓库,构建并发布了应用镜像,核对摘要,拉取并运行精确版本,还验证了实际 HTTP 响应和日志。你删除了自己的容器、本地镜像和 ECR 仓库,并保留了另一个应用的仓库。下一实验将使用 ECS 任务定义来选择并运行镜像。

进一步阅读 AWS 官方资料:镜像仓库认证与推送; 防止镜像标签被覆盖。