LiJell's 성장기

Efficient Multi-Architecture and Multi-Region Container Deployment with Amazon ECS and EC2 Spot Instances 본문

Cloud

Efficient Multi-Architecture and Multi-Region Container Deployment with Amazon ECS and EC2 Spot Instances

All_is_LiJell 2024. 1. 22. 14:49
반응형

Building Multi-architecture Container Images and Deploying for Multi-Region in parallel way

개요

저는 ECS EC2 spot instance 가용성과 안정성 확보를 위해서 amd64arm64 모두 사용중입니다. 따라서, container도 multi-architecture 빌드가 필요했습니다. 저는 이미지 빌드 최적화와 여러 regions에 동시 배포를 위해 manifest list를 활용했습니다. 결과적으로 Github Action matrix를 통해 빠르게 각 regions에 이미지 push와 배포할 수 있습니다.

ps) if you need a English version, you can check out following link

Container Image

  • file system
    • config
    • meta data

Container 구성

크게 layer *과 manifest *로 이루어지는데요.

Layer은 이미지의 일부분이고, 소프트웨어, 라이브러리, 그리고 설정파일 등을 말한다. 그리고 Manifest는 컨테이너 이미지 구조와 동작 방식을 정의한다. 즉, 이미지를 구성하는 레이어들과 그 이미지의 런타임 특성*구성*을 지정합니다.

도커 같은 경우 컨테이너 이미지 포멧은 Docker Image Specification 과 Image Manifest Specification을 통해서 정의됩니다. 컨테이너 기술에 대한 표준화는 OCI ( OPEN CONTAINER INITIATIVE )를 따릅니다.

  • 런타임 특성*: 컨테이너가 사용하는 포트, 환경 변수, 실행할 명령어와 매개변수, 볼륨 마운트, 리소스 제한 등을 말하는데요. Container runtime과는 다른 개념입니다.
  • 구성*: 실행할 명령어, 작업 디렉토리 등을 말합니다.

Container pull & push

Container 이미지를 pull하거나 push할 때 크게 두가지 일이 일어납니다. 예를들어, image를 처음 pull하게되면, 첫번째로 manifest를 image repository와 tag기반으로 받아온 후, manifest를 이용해서 layers를 모아 container file system을 구성합니다.

img

Deep Dive into Manifest

manifest는 docker inspect <image> 명령어로 확인할 수 있습니다. 로컬에 있는 이미지를 확인해 보시면, Architecture와 OS등이 명시되어 있는걸 볼 수 있습니다.

기존에는 아마존 ECR에 이미지를 배포할 때, 플랫폼이나 운영 체제 특성을 이미지 태그에 명시해야 했습니다. 예를 들어, 동일한 소스로부터 빌드한 플랫폼별 이미지들을 각각의 이미지 저장소에 별도로 저장했었는데요, 그 결과 container이 실행될 환경에 맞는 올바른 버전의 이미지를 명시적으로 참조하여 가져왔어야 했습니다.

예를 들어, {aws-account-id}.dkr.ecr.{aws-region}.amazonaws.com/my-image-linux-arm64:2.7과 같이 특정 OS나 아키텍처를 명시하는 것이 필요했었습니다.

하지만 요즘은 manifest listimage index로 불리는 것을 이용해서 더이상 그럴 필요가 없어졌습니다.

Manifest list

Manifest list는 Docker Image Manifest Specification에서 V2 image manifest (schema version 2) 때 부터 지원하기 시작했습니다.

Manifest list는 다른 architecture, OS , 그리고 platform attributes를 가지는 각 이미지의 manifests를 nested 형태로 가질 수 있게 해줍니다. 따라서, {aws-account-id}.dkr.ecr.{aws-region}.amazonaws.com/my-image:2.7와 같은 형태로 arm64 또는 amd64등 세부 태그 없이도 이미지를 호출이 가능하게 됩니다.

img

위 그림과 같이, Container engine이 이미지를 registry에서 pull 할 때 compute environment에 맞는 layer들을 manifest list의 value에 따라 찾아서 불러와주기 때문입니다. 쉽게 생각하면, 예전에는 단독주택이였는데, 이제 아파트가 된거죠. 결과적으로 manifest list를 이용하면, 배포 CI/CD pipeline과 ECR repository management 모두 더 간단해집니다.

Manifest list를 위해서는 빌드할 때 약간의 추가 작업이 필요한데요, 그 내용은 아래에서 확인해 보시죠.

Working with multi-architecture images in Amazon ECR

Single Region

사실 multi-architecture images를 빌드 후 한가지 region ECR에만 push하면 된다면 아래와 같은 방법으로 간단하게 해결 할 수 있습니다.

  • CLI
docker buildx build --platform linux/amd64,linux/arm64 
  • Github Action
- name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: user/app:latest

위 방법들은 알아서 manifest list를 생성해주기 때문에 ECR에 push된 이미지의 Artifact type을 확인해보시면 Image Index를 확인할 수 있습니다.

Multi-Region

Multi-region은 조금 까다롭습니다. buildx에서 multi-platform은 --load 를 지원하지 않고 --push는 한가지 region만 가능하기 때문이였습니다 (beta version에서 multi-platform load를 지원이 가능하다는 얘기도 있습니다). 그렇다고 각 region마다 빌드를 따로 하기엔 너무 비효율적이라 생각했습니다.

따라서 제가 결정한 방법은 manifest list를 활용하는 것이였습니다.

아래 과정은 병렬(parallel)하게 진행됐습니다.

  1. linux/amd64, linux/arm64를 parallel하게 빌드 후 runner에 --load를 합니다.
  2. 각 region에 이미지를 push합니다.
  3. 두 이미지의 manifest를 묶어줄 manifest list를 생성해줍니다.
  4. manifest list에 annotate으로 어떤 architecture이 어떤 이미지인지를 명시해줍니다.
  5. manifest list를 push해 줍니다.
  6. ECR을 확인해 보시면 Image Index를 확인 할 수 있습니다.
  • 예시:

저는 Github Action을 통해서 진행했지만, 예시는 AWS Blog에서 제공해주는 내용을 보여드리겠습니다.

  1. 일단 ECR에 이미지를 push 하기 위해서는 ECR private registry에 로그인을 해야겠죠?

     $ AWS_ACCOUNT_ID=*aws-account-id* $ AWS_REGION=*aws-region*
     $ aws ecr get-login-password --region ${AWS_REGION} | \ 
         docker login --username AWS --password-stdin \ 
         ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/hello 
       Login Succeeded
  2. 이미지 빌드 후 각 platform에 맞게 tag를 붙여주세요

     $ for i in amd64 arm64; do
     for> docker tag hello:${i} ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/hello:${i}
     for> done
    • 이미지를 확인하면 아래 처럼 보이게 됩니다.

      $ docker images | grep hello
      REPOSITORY                                                    TAG    IMAGE ID      CREATED        SIZE
      {aws-account-id}.dkr.ecr.{aws-region}.amazonaws.com/hello     arm64  8d2063eddc5e  4 minutes ago  7.19MB
      hello                                                         arm64  8d2063eddc5e  4 minutes ago  7.19MB
      {aws-account-id}.dkr.ecr.{aws-region}.amazonaws.com/hello     amd64  cbfda9e83a41  4 minutes ago  7.59MB
      hello                                                         amd64  cbfda9e83a41  4 minutes ago  7.59MB
  3. 태그를 붙인 각 이미지를 ECR에 push해 주세요

     $ for i in amd64 arm64; do
     for> docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/hello:${i}
     for> done

    푸쉬된 이미지를 aws ecr command로 아래와 같이 확인 할 수 있습니다.

     $ aws ecr --region ${AWS_REGION} describe-images --repository-name hello
     {
         "imageDetails": [
             {
                 "registryId": "aws-account-id",
                 "repositoryName": "hello",
                 "imageDigest":"sha256:b50bd7f7..5a0dc770",
                 "imageTags": [
                     "amd64"
                 ],
                 "imageSizeInBytes": 3954211,
                 "imagePushedAt": "2020-04-24T17:24:11-04:00"
             },
             {
                 "registryId": "aws-account-id",
                 "repositoryName": "hello",
                 "imageDigest": "sha256:2f333a8b..27fc2172",
                 "imageTags": [
                     "arm64"
                 ],
                 "imageSizeInBytes": 3702268,
                 "imagePushedAt": "2020-04-24T17:24:25-04:00"
             }
         ]
     }

    이 시점에서 각 architecture-specific tags에 따라서 이미지를 pull할 수 있습니다. 하지만 좀 더 간단하게 이미지를 pull하기 위해서 manifest list를 생성 후 Amazon ECR에 푸쉬해 봅시다.

  4. docker manifest create command로 manifest list를 생성해줍니다.

     $ docker manifest create ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/hello \
         ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/hello:amd64  \
         ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/hello:arm64 
     Created manifest list {aws-account-id}.dkr.ecr.{aws-region}.amazonaws.com/hello:latest

    mainfest는 default로 latest tag로 정해지지만, 각자 원하는 tag를 붙일 수 있습니다. 저 같은 경우 ${{env.ECR_REGISTRY}}/${{matrix.ecr_repository}}:${{env.ENV}}-${{github.run_number}}-${{matrix.platform}}로 Github Action에서 정해줬습니다.

  5. Manifest Annotate

    Manifest list가 어떤 image가 어떤 architecture을 위한건지 명시해줍니다. 저는 amd64에 대해서도 동일하게 진행했습니다.

     $ docker manifest annotate --arch arm64 ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/hello \
           ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/hello:arm64
  6. Manifest를 push하기 전에 mapping이 잘 됐는지 검증을 한번 해볼까요? platform.architecture values를 확인하시면 됩니다.

     $ docker manifest inspect ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/hello
     {
         "schemaVersion": 2,
         "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
         "manifests": [
             {
                 "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
                 "size": 528,
                 "digest": "sha256:b50bd7f7..5a0dc770",
                 "platform": {
                     "architecture": "amd64",
                     "os": "linux"
                 }
             },
             {
                 "mediaType": "application/vnd.docker.distribution.manifest.v2+json",
                 "size": 528,
                 "digest": "sha256:2f333a8b..27fc2172",
                 "platform": {
                     "architecture": "arm64",
                     "os": "linux"
                 }
             }
         ]
     }
  7. Manifeset List Push to ECR
    검증이 완료되면 생성한 Manifest List를 ECR에 push해줍니다.

     $ docker manifest push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/hello
     sha256:bd7a61a6ea3c366c0e58d70233900f0c761d6051da0ad8cbaa19011f873c37bc

이제 ECR Registry를 확인하시면 push한 image의 Artifact typeImage Index인것을 확인할 수 있습니다!!

이 이미지로 이제 배포만 진행하면 돼요!

결과

우리는 container의 구성과 ECR에 push&pull할 때의 로직을 간단히 훑어보고, multi-architecture 이미지를 빌드 후 multi-region에 parallel하게 push하는 방법을 중점적으로 확인해 봤습니다. 그 중에서도 manifest list를 이용하는 방법을 함께 해봤는데요, 도움이 됐었으면 좋겠네요!

반응형
Comments