かきた豆も

自分用メモ

CircleCI で Amazon ECR にマルチアーキテクチャコンテナイメージを push するメモ

これは

aws.amazon.com

  • Intel チップ搭載 Macが今後なくなっていくようなので、Amazon ECR のマルチアーキテクチャコンテナイメージを利用したい
  • CircleCI で Docker Image の build も push もしたい

使ったもの

Terraform

$ terraform version
Terraform v1.0.11
on darwin_amd64
+ provider registry.terraform.io/hashicorp/aws v3.66.0

Amazon ECR マルチアーキテクチャコンテナイメージとは

  • 詳しくはここを読む
  • 端的に言うと docker pull 時に、docker manifest を利用して platform.architecture が自身に合うものを参照するらしい

docker manifest から CPU アーキテクチャが合致する docker image を確認し、pull する

実際にやってみた

これに書いてあるチュートリアル的なのをそのまま CircleCI で行えるようにやっていく。

aws.amazon.com

成果物

github.com

ECR リポジトリを作る

resource "aws_ecr_repository" "default" {
  name = "web-server"
}

CircleCI が ECR リポジトリにあれこれ出来るように IAM User を作る

docker manifest create を行うために通常の ECR 操作用の権限に加えて ecr:BatchGetImageecr:GetDownloadUrlForLayer を与えている。

# IAM User used by CircleCI
resource "aws_iam_user" "default" {
  name = "circleci_web-server"
}

resource "aws_iam_policy" "default" {
  name = "circleci_web-server"

  policy = <<-POLICY
    {
        "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "ecr:GetAuthorizationToken"
                ],
                "Resource": "*"
            },
            {
                "Effect": "Allow",
                "Action": [
                    "ecr:BatchGetImage",
                    "ecr:BatchCheckLayerAvailability",
                    "ecr:CompleteLayerUpload",
                    "ecr:GetDownloadUrlForLayer",
                    "ecr:InitiateLayerUpload",
                    "ecr:PutImage",
                    "ecr:UploadLayerPart"
                ],
                "Resource": "${aws_ecr_repository.default.arn}"
            }
        ]
    }
    POLICY
}

resource "aws_iam_user_policy_attachment" "default" {
  user       = aws_iam_user.default.name
  policy_arn = aws_iam_policy.default.arn
}

作成した IAM User のアクセスキーを CircleCI に食わせる

AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, AWS_ECR_ACCOUNT_URLを与えた。 AWS_ECR_ACCOUNT_URL は ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com だが CircleCI の config の中で組み立てるのがめんどくさい(AWS_ACCOUNT_ID を渡すことになる)のでそのまま URL を渡した。

CircleCI に build と push をさせる

完成形はこれ

github.com

docker build と ECR への docker push はめんどくさいので Orb を使う。

circleci.com

その際、以下のように executor で resource_class を arm.medium にすると arm アーキテクチャを使用することが出来る。

executors:
  amd:
    machine:
      image: 'ubuntu-2004:202101-01'
    resource_class: medium
  arm:
    machine:
      image: 'ubuntu-2004:202101-01'
    resource_class: arm.medium
jobs:
  build-amd64:
    executor: amd
    steps:
      - aws-ecr/build-and-push-image:
          <<: *params
          tag: amd64
  build-arm64:
    executor: arm
    steps:
      - aws-ecr/build-and-push-image:
          <<: *params
          tag: arm64

これで、web-server:amd64web-server:arm64 の2つのイメージが push された。

CircleCI に docker manifest を作らせる

以下のようなジョブを書いた。 circleci/aws-ecr はどれだけググっても(2021年11月末頃調べ) aws-ecr/build-and-push-image の使用方法しか情報が転がっていないが、 aws-ecr/ecr-login のように書くと一部のコマンドだけ使用することも出来る。

  create-manifest:
    executor: amd
    steps:
      - aws-ecr/ecr-login
      - run:
          name: Create docker manifest
          command: >-
              docker manifest create $AWS_ECR_ACCOUNT_URL/web-server \
                $AWS_ECR_ACCOUNT_URL/web-server:amd64 \
                $AWS_ECR_ACCOUNT_URL/web-server:arm64
      - run:
          name: Push docker manifest
          command: docker manifest push $AWS_ECR_ACCOUNT_URL/web-server

このジョブが実行されると、以下のように push した覚えのない latest タグが急に生えてくる。

f:id:kakitamama:20211207100913p:plain

これを pull して manifest を確認すると以下のようになる。

$ docker manifest inspect 000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/web-server:latest
{
   "schemaVersion": 2,
   "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
   "manifests": [
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 528,
         "digest": "sha256:b6da94ded5a0f9f86f4c03c69ddc675622b133441f3125ac03cb5531d0009e08",
         "platform": {
            "architecture": "amd64",
            "os": "linux"
         }
      },
      {
         "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
         "size": 528,
         "digest": "sha256:068c08e45b5a8c326cb985282090706df66fe8afbc5446b9ba110040a7086f64",
         "platform": {
            "architecture": "arm64",
            "os": "linux"
         }
      }
   ]
}

多分 latest の manifest から各 architecture の digest を用いてイメージを参照しているのでしょう。

Intel チップ搭載の Mac で arm64 向けビルドしたやつを動かしてみた

WARNING は出るけどなんか動いた。

$ docker run -p 3000:3000 000000000000.dkr.ecr.ap-northeast-1.amazonaws.com/web-server:arm64
WARNING: The requested image's platform (linux/arm64) does not match the detected host platform (linux/amd64) and no specific platform was requested
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /                         --> main.glob..func1 (3 handlers)
[GIN-debug] Listening and serving HTTP on :3000

終。

課題

  • M1 Mac を持っていないのでちゃんとした動作確認が出来ていない。