[ 살펴보기 ] AWS - Elastic Container Registry, Elastic Container Service

[ 살펴보기 ] AWS - Elastic Container Registry, Elastic Container Service

·

10 min read

Docker의 중요도가 나날이 높아지면서 Docker 기반의 application 개발과 배포 능력의 중요도 역시 높아지고 있다. AWS을 통해 Docker image를 관리하고 Docker image를 통해 application을 배포하는 방법을 살펴보자.

ECR ( Elastic Container Registry )

ECR은 AWS에서 제공하는 docker image registry다. Dockerhub와 같이 개발한 application image를 배포하고 관리할 수 있다. 테스트를 위해 간단한 NextJS application을 docker로 build하고 ECR에 push하는 예제를 살펴보자.

ECR로 image를 push 하거나 pull 하기 위해 authentification token을 발급 받는 절차가 있는데 이를 위해 AWS CLI를 실행하는 IAM user는 ecr:GetAuthorizationToken policy에 대한 권한을 가지고 있어야 한다.

이제 AWS ECR 페이지로 이동하고 repository에서 image를 관리할 repository를 생성한다.

repository를 생성하고 해당 repository를 선택한다. 그리고 왼쪽 sidebar의 permissions 페이지로 이동해서 해당 repository에 특정 IAM user가 image를 push할 수 있도록 permission 설정을 추가해준다.

ARN이 arn:aws:iam::123456789:user/test-user인 IAM user에게 ECR에 image를 push할 수 있도록 permission 설정을 해야 한다면 아래와 같이 설정할 수 있다. Principal의 user 정보는 사용하는 IAM user 정보에 맞게 수정하도록 하자.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "new statement",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::123456789:user/test-user"
      },
      "Action": [
        "ecr:BatchCheckLayerAvailability",
        "ecr:BatchGetImage",
        "ecr:CompleteLayerUpload",
        "ecr:GetDownloadUrlForLayer",
        "ecr:InitiateLayerUpload",
        "ecr:PutImage",
        "ecr:UploadLayerPart"
      ]
    }
  ]
}

이제 ECR에 push할 docker image를 build해보자. 필자는 테스트를 위해 기본적인 nextjs project를 생성해 docker image로 build한다. 아래는 standalone mode로 nextjs를 build하고 그 결과를 최종 docker image에 포함하는 만드는 예제다. NextJS standalone build에 관해선 documentation을 통해 보다 자세한 정보를 확인할 수 있다. ( Reference - Automatically copying traced files )

Dockerfile

FROM node:18-alpine AS base

FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY package*.json ./
RUN npm ci


FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

RUN npm run build


FROM base AS runner
WORKDIR /app

COPY --from=builder /app/.next/standalone ./.next/standalone
COPY --from=builder /app/.next/static ./.next/standalone/.next/static
COPY --from=builder /app/public ./.next/standalone/public
COPY --from=builder /app/package*.json ./

ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1
ENV PORT 3000
ENV HOSTNAME 0.0.0.0

EXPOSE 3000

CMD ["node", "./.next/standalone/server.js"]

Dockerfile은 project root 경로에 추가하고 NextJS project와 dockerfile에 모두 준비 되었다면 project root 경로에서 다음 명령어를 통해 image를 build한다.

docker build -t test-front-app .

위의 명령어를 실행하고 다음 명령어를 통해 image가 정상적으로 생성되었는지 확인하자.

docker image ls

이제 생성한 image를 ECR에 push 해보자. 선택한 repository의 image page로 이동하면 우측 상단 push를 위해 실행해야 할 command를 확인할 수 있다.

우선 첫 번째로 ECR repository 인증을 위해 authentification token을 발급 받는 command를 확인할 수 있으며 그 다음 image를 build하고 build한 image를 registry로 push하기 위해 tag를 추가하는 절차를 확인할 수 있을 것이다.

image는 위에서 build를 했으므로 해당 과정을 생략하고 image에 tag를 추가하여 push 하는 과정만 진행해보자. tag 추가 및 push를 위한 정확한 command 내용은 iam user 정보, ECR repository 정보에 따라 달라지므로 view push command 버튼을 통해 나오는 내용을 참조하자. Image를 정상적으로 push하면 ECR image 페이지에서 다음과 같이 push한 image를 확인할 수 있다.

push한 image를 local로 다시 push해서 image가 정상적으로 동작하는지 테스트 해보자. 우선 local에서 생성했던 image를 다시 삭제한다. image 삭제는 다음 명령어를 통해 삭제할 수 있다. tag 추가로 생성된 image 역시 삭제해주자.

docker image rm test-front-app

이제 다음 명령어를 통해 docker image를 local로 pull한다. imageURI는 생성한 image를 선택하여 상세 페이지로 들어가면 확인할 수 있다.

docker pull imageURI

위의 명령어를 통해 image를 정상적으로 pull 했다면 다음 명령어를 통해 해당 image를 통해 container를 실행시켜본다. imageName은 ecr에서 pull한 image의 이름을 기입한다.

docker container run -d --name test-front -p 3000:3000 imageName

위의 명령어를 통해 container를 실행하고 browser를 열어 localhost:3000으로 접속하면 application이 정상적으로 실행되는 것을 확인할 수 있다.

위의 예제에선 application의 image를 ECR로 push하고 pull하는 방법을 살펴봤다. 이제 이렇게 ECR로 push image를 통해 실제로 application을 배포하는 방법을 살펴보자.

ECS ( Elastic Container Service )

ECS는 AWS에서 제공하는 container orchestration service다. ECS를 통해 containerized application을 쉽게 배포하고 관리할 수 있다.

ECS를 통해 container를 배포할 때 container가 실행되는 insfrastructure를 EC2를 통해 직접 구축하여 배포할 수도 있지만 Fargate을 사용하면 container 실행을 위한 환경을 직접 구축할 필요 없이 container 기반의 application을 쉽게 배포할 수 있다.

ECS를 통해 application을 배포하기 위해서 우선 task definition을 생성해야 한다. Task definition이란 container가 실행될 infrastructure, OS, CPU, memory 그리고 container의 기반이 되는 image등의 정보를 가진 blueprint를 말한다. ECS 페이지 왼쪽 sidebar에서 task definition을 선택해 task definition 페이지로 이동해서 실행할 application을 위한 task definition을 생성한다.

Application에 대한 task definitin이 준비 되었다면 이제 ECS cluster를 생성한다.

이제 생성한 cluster를 선택해 상세 페이지에서 service를 생성해보자.

cluster service를 생성할 때 설정할 수 있는 option은 아래에서 별도로 살펴보고 우선 기본적인 설정만 추가한 채로 service를 생성해보자. service를 정상적으로 생성하고 조금 기다린 뒤 Tasks tab으로 이동해 보면 위에서 service를 생성할 때 설정한 task definition을 기반으로 task가 생성되는 것을 확인할 수 있다.

생성된 task를 선택하면 해당 task에 접속할 수 있는 public ip address를 확인할 수 있다. 해당 ip를 통해 application에 접속할 수 있으며 접속할 때 task definition을 생성할 때 설정했던 port number를 uri에 포함해서 접속해야 한다.

Launch Type

ECS를 통해 application을 배포할 때 application container가 실행될 환경을 선택해야 한다. ECS를 통해 배포할 수 있는 대표적인 launch type은 다음 두 가지다.

  • Fargate : Lauch type을 Fargate으로 설정하면 container가 실행되는 server infrastructure를 개발자가 직접 설정하고 관리하지 않아도 된다. Application의 image를 build하고 해당 image를 기반으로 task definition, service를 생성하면 나머지는 개발자가 설정한 설정 값에 따라 server infrastructure가 자동으로 관리된다. Amazon VPC, Auto scaling, Elastic Load Balancing, IAM, Secrets Manager 등 다른 AWS service와 연동하여 사용할 수도 있다.

  • EC2 : application 운영 비용을 최적화 해야 하거나 server infrastructure를 완전히 통제 해야 한다면 EC2를 application container의 launch type으로 설정할 수도 있다. EC2를 launch type으로 설정한다면 application container가 실행되는 server infrastructure 구축과 관리는 직접 해야 한다.

Task definition

위의 예제에서 task definition 생성과 service 생성을 통해 ECS를 통해 application을 배포하는 방법을 간단히 살펴보았다. 이제 task definition을 생성할 때 설정할 수 있는 option에 대해 조금 더 자세히 살펴보자.

  • task definition family : task definition을 생성하면 해당 definition은 definition family에 지정한 이름을 가진 family에 속하게 된다. Family에 처음으로 생성된 task definition은 revision 1부터 시작해 이후 task definition이 같은 family에 등록되면 revision value가 순차적으로 증가하게 된다.

  • launch type : task definition의 launch type을 지정한다.

  • task role : task definition으로 생성될 task의 container에 부여할 IAM role을 설정한다. task container에서 실행되는 application이 다른 AWS API를 사용한다면 필요한 permission을 가진 role을 부여해야 한다.

  • task execution role : ECS container agent가 다른 AWS API를 사용할 때 필요한 permission을 가진 role을 부여할 때 설정한다.

  • network mode : task container가 사용할 docker networking mode를 설정한다. launch type 및 host OS에 따라 설정할 수 있는 값이 다르며 launch type이 fargate일 때 awsvpc network 설정이 요구된다. none으로 설정하면 task contanier는 외부 network과 연결되지 않고 port mapping 설정 역시 할 수 없게 된다. awsvpc mode로 설정하면 task에 elastic network interface가 할당되며 awsvpc mode를 사용하는 task definition을 통해 service를 생성하는 경우 NetworkConfiguration 설정은 필수로 요구된다.

  • task size : task definition을 통해 생성될 task의 cpu, memory를 설정한다. container level에서 설정하는 cpu, memory와는 별개이며 launch type이 fargate일 때는 필수로 설정해야 한다.

위의 Infrastructure 설정을 위한 option 외에도 task definition을 생성할 때 task definition을 기반으로 task를 생성할 때 사용될 container의 definition 역시 설정해야 한다. 하나의 task에서 실행되는 container는 하나일 수도 있고 여러개일 수도 있으므로 필요에 따라 하나의 task에서 여러개의 container defition을 설정할 수 있다.

Container definition에서 설정할 수 있는 option은 다음과 같다.

  • name : container의 name을 설정한다.

  • image : container의 기반이 되는 image의 url을 설정한다. docker hub에 있는 image url을 사용할 수도 있고 ECR에 등록한 image의 url을 사용할 수도 있다.

  • memory hard limit : container가 memory hard limit에 설정한 memory 보다 많은 memory를 소비하면 container를 종료시킨다.

  • container port : container가 expose할 port를 설정한다. task definition을 통해 task를 생성할 때 port mapping에서 설정한 port를 통해 container에 접근할 수 있다.

  • health check ( optional ) : container health check를 위한 command와 관련된 configuration parameter를 설정할 수 있다. command는 container health check를 위해 실행할 command를 설정하며 interval을 통해 얼마나 자주 health check command를 실행할 지 초 단위로 설정한다. 그리고 timeout을 통해 health check request 결과를 기다리는 기다리는 시간을 초 단위로 설정할 수 있고 timeout에 설정한 시간 내에 request 결과를 받지 못하면 health check 실패로 간주된다.

  • environment variable ( optional ) : container에 전달될 environment variable을 설정할 수 있다. 개별적으로 key/value를 설정하거나 S3에 업로드한 environment variable 파일을 upload해서 사용할 수도 있다.

위에서 설명한 option외에 task definition에서 설정할 수 있는 option은 다양하며 모든 option list는 documentation을 통해 확인할 수 있다. ( Reference - Amazon ECS task definition parameters )

Service definition

이제 생성한 task definition을 통해 service를 생성할 때 설정할 수 있는 대표적인 option에 대해 살펴보자.

  • capacity provider strategy : task 배포에 사용할 compute option의 하나로서 service task에 적용될 launch strategy를 설정할 수 있다. capacity provider를 통해 task container가 lauch될 capacity를 선택한다.

    capacity provider를 추가해서 하나 이상의 capacity provider를 사용할 수도 있다. base option을 통해 특정 capacity provider에서 실행되는 최소 task의 수를 지정할 수 있으며 다수의 capacity provider를 사용할 때는 하나의 capacity provider에서만 base option을 설정할 수 있다.

    그리고 weight option을 통해 실행되는 task의 총 갯수 중 특정 capacity provider에서 실행하고자 하는 percentage를 설정할 수 있다. 만약 두 개의 capacity providers의 weight가 각각 50으로 설정되어 있다면 ECS는 task를 각각의 capacity provider에 균등하게 배포한다. 반면 첫 번째 capacity provider의 weight가 20이고 두 번째 capacity provider가 80이라면 ECS는 총 task의 숫자 중 20%를 첫 번째 capacity provider에 분배하고 나머지 80%를 두 번째 capacity provider에 분배한다.

  • task definition : service에서 생성하는 task의 기반이 될 task definition을 설정한다. task family와 revision을 설정하고 revision이 명시적으로 설정되어 있지 않으면 task family의 latest active revision이 적용된다.

  • platform version : platform version은 task가 실행되는 runtime environment를 결정하며 capacity provider 또는 launch type이 fargate일 때 platform version을 설정해야 한다.

  • service type : ECS scheduler가 cluster에 task를 배치하는 방법을 정한다. daemon option에선 scheduler는 각각의 active container에 하나의 task를 배치하고 replica option에선 scheduler가 desired tasks option에 선언한 숫자의 task를 cluster에 배치하고 유지한다. capacity provider나 launch type이 fargate일 때 daemon type은 사용할 수 없다.

  • desired tasks : service에서 실행할 task의 숫자를 정한다. service type이 replica일 때만 설정할 수 있다.

Auto scaling

ECS service를 생성할 때 auto scaling 기능을 통해 실행되는 task의 수를 자동으로 증가 시키거나 감소 시킬 수 있다. ECS가 지원하는 auto scaling type은 target tracking과 step scaling이 있으며 target tracking을 통해 auto scaling을 설정하는 방법을 살펴보자. ECS service 생성 페이지의 아래 부분을 보면 다음과 같이 service auto scaling을 설정할 수 있는 section을 볼 수 있다.

여기서 minimun number of task를 통해 service가 실행할 최소 task 숫자와 maximum number of task를 통해 service가 실행할 최대 task 숫자를 설정한다. 만약 desired task에 설정한 숫자가 minimum number of task 보다 작으면 task 숫자는 minimum number of task에 맞추어 지고 desired task가 maximum number of task보다 크면 maximum number of task에 맞추어 진다.

Auto scaling policy에 따라 scale-out ( task 증가 )이 발생하면 scale-out cooldown period에 설정한 시간 ( second )동안 새로운 scale-out이 발생하지 않으며 scale-in ( task 감소 )가 발생하면 scale-in cooldown period에 설정한 시간 동안 새로운 scale-in이 발생하지 않는다.

Load Balancer

Autoscaling을 통해 여러 개의 task가 실행된다면 traffic을 알맞게 분산 시켜줄 수 있도록 load balancer를 추가해줘야 한다. 우선 load balancer가 traffic을 분산 시켜줄 target group을 생성한다. ECS service의 task가 load balance의 대상이 되므로 target type은 IP address로 설정해서 생성한다.

target group을 생성할 때 advanced health check setting에서 override를 통해 health check port를 container가 expose하는 port로 변경하도록 한다. 현재 테스트 에서 container에서 실행되는 application은 3000 port에서 실행되고 task definition에서 container의 port 역시 3000으로 설정하였기에 health check의 port 역시 3000으로 설정해주어야 한다. 테스트에선 health check를 위한 별도의 route를 구성하지 않고 테스트 하지만 health-check를 위한 별도의 route가 존재한다면 health check path에 해당 path를 추가해준다 ( ex - /health-check )

이제 application load balancer를 생성해서 ECS와 연동해보자. load balancer 생성 시 방금 생성한 target group을 default action 부분에 설정해준다. Load balancer의 security group은 http port( 80 )과 https port( 443 )을 허용하는 security group을 생성하여 적용한다.

Load balancer가 정상적으로 생성되었다면 이제 load balancer가 적용된 ecs service를 생성해보자. ecs service 생성 시 아래 load balancing section에서 생성한 load balancer를 설정해준다.

그리고 load balancer의 listener는 load balancer를 생성할 때 함께 생성된 listener를 사용하면 되고 ( 80:HTTP ) target group은 위에서 생성한 target group을 사용하면 된다. Load balancer가 80 port로 request를 전달 받더라도 service를 생성할 때 load balancing section에서 설정한 container의 port로 ( 위의 예제에선 3000 ) request를 forward 해준다.

여기서 주의할 점은 service 생성 시 network 설정을 할 때 security group은 위에서 적용한 load balancer로 전달되는 traffic을 허용하는 security group을 설정해야 한다. security group을 생성할 때 traffic을 허용할 port와 ip address를 설정할 수도 있지만 ip address 대신 security group을 설정할 수도 있으므로 ip address 대신 load balancer에 적용된 security group이 설정된 새로운 security group을 생성해 service의 security group으로 설정한다.

이제 service를 생성하고 health check까지 정상적으로 완료되면 load balancer를 통해 ecs로 배포한 application에 접근할 수 있다. service를 생성할 때 load balancer로 부터 전달되는 traffic만 허용하는 security group을 적용했으므로 사이트 접속은 task의 ip address가 아닌 load balancer의 dns name으로 접속 해야 한다.

또한 위의 예제는 테스트 용도이므로 load balancer에 https listener를 추가하지 않은 상태다. 그러므로 https가 아닌 http protocol을 통해 배포한 application에 접근할 수 있다. 일부 browser는 domain만 입력해서 접속할 경우 자동으로 https를 사용하므로 테스트시 유의하자.