Edited at

AWS ECR上のDockerイメージをpullせずにDockerタグをコピーする

やりたいことはタイトルどおり、

AWS ECR上のDockerイメージをpullせずにDockerタグをコピーしたい。

なぜかというと、CIからECSにデプロイするときに、 hoge:current を一旦 hoge:old に退避して、 hoge:latesthoge:current に上書きしたいみたいなことがあるんだけど、イメージがそこそこサイズがでかいと、一旦 docker pull して docker tag して docker push し直すみたいなのが時間がかかる。

AWSの公式ドキュメント見てたら、やりたいことはdockerコマンドを使わずにaws-cliでできそうだったのでやってみたんだけど、

参考: AWS CLI を使用してイメージにもう一度タグを付ける

https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/retag-aws-cli.html

CIに組み込もうとすると+αで若干の工夫が必要だったので、そのへんの補足。


ミニマム

例として、hoge:latest => hoge:current にコピーする場合は、こんなかんじ。

MANIFEST=$(aws ecr batch-get-image --repository-name hoge --image-ids imageTag=latest --query 'images[].imageManifest' --output text)

aws ecr put-image --repository-name hoge --image-tag current --image-manifest "$MANIFEST"

これは、まぁAWSの公式ドキュメントに書いてある。


CIに組み込むための若干の工夫


はまりポイント1

デプロイが途中で失敗して再ランした場合など、同じイメージダイジェストで同じタグを重複pushすると

ImageAlreadyExistsExceptionで失敗するので、ダイジェストが既に同じでないか事前にチェックしておく必要がある。

つまり

FROM_DIGEST=$(aws ecr describe-images --repository-name hoge --image-ids imageTag=latest --query "imageDetails[0].imageDigest")

TO_DIGEST=$(aws ecr describe-images --repository-name hoge --image-ids imageTag=current --query "imageDetails[0].imageDigest")
if [[ "$FROM_DIGEST" = "$TO_DIGEST" ]]
then
# 既にコピー元とコピー先が同じダイジェストの場合は何もしない
echo "Image already exists. Skipped."
else
# リモートでタグをコピー
()
fi


はまりポイント2

aws ecr describe-images だと指定したタグが存在しない場合にImageNotFoundExceptionで失敗するので

aws ecr list-images を使う。

実装してるときに手元からawsコマンドでタグ作ったりしちゃってたから、気づかなかったんだよ。

つまり

FROM_DIGEST=$(aws ecr list-images --repository-name hoge --query "imageIds[?imageTag=='latest'] | [0].imageDigest")

TO_DIGEST=$(aws ecr list-images --repository-name hoge --query "imageIds[?imageTag=='current'] | [0].imageDigest")
()


完成品

hoge:latest => hoge:current にコピーする場合は、こんなかんじ。

# デプロイが途中で失敗して再ランした場合など、同じイメージダイジェストで同じタグを重複pushすると

# ImageAlreadyExistsExceptionで失敗するので、ダイジェストが既に同じでないか事前にチェックしておく
# aws ecr describe-images だと指定したタグが存在しない場合にImageNotFoundExceptionで失敗するので
# aws ecr list-images を使う
# https://docs.aws.amazon.com/cli/latest/reference/ecr/list-images.html
FROM_DIGEST=$(aws ecr list-images --repository-name hoge --query "imageIds[?imageTag=='latest'] | [0].imageDigest")
TO_DIGEST=$(aws ecr list-images --repository-name hoge --query "imageIds[?imageTag=='current'] | [0].imageDigest")
if [[ "$FROM_DIGEST" = "$TO_DIGEST" ]]
then
# 既にコピー元とコピー先が同じダイジェストの場合は何もしない
echo "Image already exists. Skipped."
else
# リモートでタグをコピー
echo "docker tag copy: latest => current"
# https://docs.aws.amazon.com/cli/latest/reference/ecr/batch-get-image.html
MANIFEST=$(aws ecr batch-get-image --repository-name hoge --image-ids imageTag=latest --query 'images[].imageManifest' --output text)
# https://docs.aws.amazon.com/cli/latest/reference/ecr/put-image.html
aws ecr put-image --repository-name hoge --image-tag current --image-manifest "$MANIFEST"
fi

できたー。


余談

本題と関係ないけど、Dockerで動的タグを使うなとか条件反射でツッコミたくなった人は aws ecs update-service --force-new-deployment とか ECS_IMAGE_PULL_BEHAVIOR でググってくれ。