AWS CodePipeline을 이용한 ECS Fargate 배포하기

dev Jan 10, 2020

예전부터 인프라 구축에 대한 관심이 많았지만 어떤 프로젝트에 어떤 기술로 어떻게 적용해야 할지를 몰라 관련 기술에 대한 글만 보고 넘기는 경우가 많았다. 그러나 최근에 클라우드 인프라를 코드로 정의하고 프로비저닝하는 AWS CDK(Cloud Development Kit)를 통해 AWS를 더 많이 접하게 됐으며, 여러 가지를 둘러보는 계기가 됐다. 이번에는 그중에서도 AWS CodePipeline을 통해 ECS Fargate에 배포한 경험을 정리하고자 한다.

AWS CodePipeline ?

AWS CodePipeline은 빠르고 안정적인 애플리케이션 및 인프라 업데이트를 위해 릴리즈 파이프라인을 자동화하는 데 도움이 되는 완전관리형 지속적 전달 서비스이다. 코드 변경이 발생할 때마다 사용자가 정의한 릴리즈 모델을 기반으로 릴리즈 프로세스의 빌드, 테스트 및 배포 단계를 자동화한다.

지금부터 AWS CodePipeline을 통한 ECS Fargate 배포를 알아본다.


아키텍처 및 플로우

AWS CodePipeline의 파이프라인은 Source, Build, Deploy와 같이 크게 3가지로 구성되며, 본인이 구성한 아키텍처는 그림 1과 같다.

그림 1. 구성한 아키텍처

그리고 각 파트에서 수행하고자 하는 것은 아래와 같이 구성했다.

Source

  • 소스 코드 관련 작업이 수행된다. (Github 웹훅을 통한 파이프라인 실행)

Build

  • 소스 코드를 통해 도커 컨테이너 이미지를 빌드한다.
  • AWS ECR에 푸시한다.

Deploy

  • 빌드한 이미지를 AWS ECS Fargate에 배포한다.

다음은 Source에 해당 되는 애플리케이션 구성이다.


애플리케이션 구성

컨테이너에서 사용할 애플리케이션은 TypeScript, Yarn, Nest.js를 사용했다. 프로젝트는 Nest-CLI의 명령어를 통해 만들었다. CLI를 통해 프로젝트를 생성하면 그림 2처럼 구성된다.

그림 2. Nest Cli를 통해 생성한 프로젝트

app.controller.ts

기본으로 제공하는 Hello World!를 출력하는 API가 있다. 그리고 로드밸런서와 ECS 서비스 간 헬스체크를 하는 API는 따로 작성했다.

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }

  @Get('/health-check')
  healthCheck(): string {
    return 'ok'
  }
}

package.json

package.json에는 개발 및 운영에 필요한 패키지 및 애플리케이션을 구동하는 명령어로 구성되어있다.

Dockerfile

도커파일은 컨테이너에서 수행 할 명령어로 구성했다.

FROM node:12.14-alpine

ENV TZ Asia/Seoul
RUN	apk update && apk upgrade && \
	apk add --update tzdata && \
	cp /usr/share/zoneinfo/${TZ} /etc/localtime && \
	echo "${TZ}" > /etc/timezone

WORKDIR /usr/src/app

COPY . .

RUN rm -rf node_modules

RUN yarn
RUN yarn build

EXPOSE 80
CMD [ "yarn", "start:prod" ]

이로써 컨테이너에 올라 갈 애플리케이션의 구성은 마무리 됐다. 다음은 AWS 작업이다.


AWS ECR 만들기

도커 컨테이너 이미지를 올리기 위한 컨테이너 리포지토리를 만든다. 당장 사용하지는 않지만 CodeBuild에서 빌드에 중요한 코드 작성을 하므로 미리 만들어둔다.

그림 3. ECR 리포지토리 만들기

AWS CodeBuild 만들기

다음으로 파이프라인의 구성 중 하나인 빌드를 위해 AWS CodeBuild를 만든다. AWS CodeBuild는 소스 코드를 컴파일하는 단계부터 테스트 실행 후 소프트웨어 패키지를 개발하여 배포하는 단계까지 마칠 수 있는 완전관리형의 지속적 통합 서비스이다. CodeBuild에서 빌드를 하기 위한 여러 설정을 진행하며, 다음과 같다.

1. 프로젝트 구성

CodeBuild 프로젝트 이름을 설정한다.

그림 4. CodeBuild - 프로젝트 구성

2. 소스

현재 소스 코드가 위치한 공급자와 리포지토리를 설정한다. 본인은 GitHub의 프라이빗 리포지토리로 설정했다. 추가 구성에서 서브 모듈에 대한 옵션을 선택할 수 있다.

그림 5. CodeBuild - 소스

3. 환경

빌드에서 사용 할 환경에 대한 설정이다. 자신의 상황에 맞게 선택하면 된다.

그림 6. CodeBuild - 환경

그리고 도커 컨테이너 이미지 빌드를 위해 CodeBuild에 권한을 부여하고, 서비스 역할을 선택한다. 그 외에도 추가 구성에서 빌드 제한 시간, 컴퓨팅 등 여러가지 옵션을 조정한다.

그림 6-1. CodeBuild - 환경

4. BuildSpec

BuildSpec은 CodeBuild가 빌드를 실행하는 데 사용하는 YAML 형식의 빌드 명령 및 관련 설정의 모음이다. 소스 코드의 일부로 BuildSpec을 포함할 수도 있고 명령어로 작성할 수도 있다.

그림 7. BuildSpec

CodeBuild는 기본적으로 코드 최상단 경로에서 BuildSpec.yml을 찾는다. 하지만 최상단에 BuildSpec.yml 파일이 위치하지 않을 때에는 그림 7의 Buildspec 이름 항목에 이름을 따로 지정한다. 본인이 작성한 BuildSpec.yml은 아래와 같다.

    version: 0.2

    phases:
      install:
        runtime-versions:
          docker: 18
        commands:
          - aws --version
          - $(aws ecr get-login --no-include-email --region ap-northeast-2)
          - npm install yarn
      build:
        commands:
          - docker build -t nestjs-example .
      post_build:
        commands:
          - docker tag nestjs-example:latest 572095694744.dkr.ecr.ap-northeast-2.amazonaws.com/nestjs-example:latest
          - docker push 572095694744.dkr.ecr.ap-northeast-2.amazonaws.com/nestjs-example:latest
          - echo Writing image definitions file...
          - printf '[{"name":"nestjs-example-container","imageUri":"%s"}]' 572095694744.dkr.ecr.ap-northeast-2.amazonaws.com/nestjs-example:latest > imagedefinitions.json
    artifacts:
      files: imagedefinitions.json

원래 BuildSpec은 더 많은 구문을 지원하지만, 본인은 필요한 구문 install, build, post_build만 작성했다.

install

런타임 버전과 수행 할 명령어를 작성했다. 본인은 도커 버전 18을 선택했고, ECR 로그인 및 패키지 관리자 yarn 설치를 수행했다. 아래의 링크에서 CodeBuild에 제공하는 도커 이미지 버전이 있으니 참고해서 설치하면 된다.

https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html

build

도커 컨테이너 이미지를 빌드했다.

post_build

post_build에서는 태깅을 하고 ECR에 이미지를 푸시했다. 그리고 태스크 데피니션에서 컨테이너의 업데이트 된 이미지를 식별하기 위한 imagedefinitions.json도 정의했다. print문의 name에는 ECS의 컨테이너 이름을 작성해야한다.

더 많은 BuildSpec의 예시는 다음 링크에서 확인할 수 있다.

https://docs.aws.amazon.com/ko_kr/codebuild/latest/userguide/build-spec-ref.html

5. 아티팩트

아티팩트는 빌드의 결과물이다. 하지만 우리는 도커 이미지를 ECR에 푸시하기 때문에 그림 8의 유형에서 아티팩트 없음을 선택한다.

그림 8. 아티팩트

6. 로그

빌드의 과정을 CloudWatch에 로그를 남길 수 있다. 로그를 설정할 경우 CloudWatch 로그 그룹에서 스트림 별로 확인할 수 있다. 빌드 실패를 했을 시 자세한 오류를 볼 수 있어서 로그 남기는 것도 도움이 된다.

그림 9. 로그

이로써 CodeBuild 생성은 마쳤으며, 위에서 CodeBuild 생성 시 만들어진 서비스 역할에 S3 정책 연결을 한다.

CodeBuild 서비스 역할에 S3 정책 연결

위에서 작성한 BuildSpec의 imagedefinitions.json이 S3에 업로드되기 때문에 S3 정책을 CodeBuild 서비스 역할에 연결한다. 그림 10에서는 편의상 AmazonS3FullAccess 정책을 연결했지만, 보안상 필요한 권한만 가진 정책을 연결하는 것이 맞다.

그림 10. 정책 연결

권한을 부여하고나면 CodeBuild의 구성은 끝난다. 다음은 AWS CodePipeline을 구성한다.


AWS CodePipeline 구성하기

1. 파이프라인 설정

새 파이프라인을 생성을 누르면 파이프라인 이름과 서비스 역할 등을 설정할 수 있다. 기존의 서비스 역할이 없기 때문에 새 서비스 역할을 만들어준다. 고급 설정에서는 아티팩트 스토어 및 아티팩트 암호화를 추가로 설정할 수 있다.

그림 11. 파이프라인 설정

2. 소스

소스 설정을 한다. 자신의 소스 코드가 있는 공급자, 리포지토리 그리고 브랜치 등을 선택한다.

그림 12. 소스

3. 빌드

위에서 생성한 CodeBuild를 선택한다.

그림 13. 빌드

4. 배포

사전에 생성한  ECS 클러스터 및 서비스를 선택한다. 본인은 기존의 Fargate 템플릿의 ECS가 존재했기 때문에 이를 선택했다. 이 글에서 ECS Fargate, 로드밸런서를 만드는 과정은 생략한다.

그림 14. 배포

마지막으로 선택한 항목을 검토하게 되면 파이프라인 생성이 완료된다. 생성한 파이프라인을 실행하면 그림 15와 같이 성공한 파이프라인의 상태를 볼 수 있다.

그림 15. 파이프라인의 상태

배포된 애플리케이션은 ECS 서비스와 연결된 로드밸런서를 통해서 확인할 수 있다. 그림 16처럼 Hello World!가 뜬다면 제대로 배포된 것이다.

그림 16. 로드밸런서 DNS 이름을 통해 접근한 애플리케이션

지금까지 AWS CodePipeline을 통한 ECS Fargate 배포에 대해서 알아봤다. AWS CodePipeline은 이미 많은 곳에서 사용하고 있는 검증된 서비스이다. AWS를 사용 중이라면 누구나 쉽게 사용할 수 있는 장점이 있다.

커버 이미지 출처 : https://d1.awsstatic.com/product-marketing/CodeDeploy/Partners/ALMsuite_AWSCodePipeline.47e93c2726522cd49c8c948bbfc608a63cadd256.png

cherrypick

체리픽이라는 단어 본연의 뜻은 안 좋은 의미이지만 저는 트렌디하고 많은 기술을 공부하고 내 거로 만들자는 뜻을 가지고 사용하고 있습니다.