Amazon Web Service

[AWS] Serverless(ECS Fargate) + CI/CD(CodePipeline)

인프라보이 2023. 12. 21. 14:47

안녕하세요. Infraboy입니다.

Serverless는 2008년 Google App Engine으로 최초의 추상적인 서버리스 컴퓨팅 제품이 출시되었고, 2014년 AWS에서 Lambda가 출시되면서 대중화가 되었습니다.  그리고 2013년 Docker가 발표되었지만 이 당시 서버 엔지니어는 중요도는 알고 있었지만 접근하기에 큰 부담도 있고 생소한 영역이었습니다. 그리고 2015년 쿠버네티스(Kubernetes)가 발표되었고 최근 모든 클라우드 지원자들은 쿠버네티스를 본인들의 필수 Skill영역으로 이력서를 작성하고 있는 만큼 중요한 IT기술 영역으로 자리 잡았습니다. 

쿠버네티스는 kubectl이라는 명령어와 정의된 Yaml파일을 통해 클러스터의 노드, 네트워크, Pod들을 관리하기에 많은 학습곡선이 필요한데, AWS ECS(Elastic Container Service)는 많은 부분의 관리 포인트를 AWS에서 처리하고, 사용자(관리자)는 소스 배포와 서비스(TASK)를 관리만 하면 되어 편리한 부분을 느꼈습니다. 

 

저는 최근 자체적으로 개발을 시작하고 있어, 내가 만든 이 개발 플랫폼 인프라를 어떻게 구성해야 할지 고민을 많이 했습니다.
처음에는 Legacy방식으로 EC2에 서비스를 띄우고, RDS에 연결하는 방법으로 구성을 했다가, Serverless 환경을 경험해 보고자 ECS Fargate를 선택하여 인프라를 구성했습니다.

Flask로 구성한 내 개발 플랫폼을 Docker 이미지로 만들어 ECS Task에 띄우기까지 여럿 시행착오와 각종 AWS 권한없음 오류를 해결하면서 많은 경험을 얻어 블로그를 작성하게 되었습니다.

서론은 이제 접고, 어떻게 구성했는지 보고 드리겠습니다.

 

0. 개발 착수 및 로컬PC에서 개발 소스 동작 여부 확인

아주 불필요한 수작업을 자동화하려고 개발을 시작했고, 개알못(개발 1도 알지 못하는) 엔지니어가 ChatGPT를 이용해서 Backend를 만들었습니다. Frontend(화면 UI작업)는 후배의 도움으로 대략적인 화면 구성을 하여 이제 인프라에 배포하는 과정을 시작했습니다.

내 로컬PC에서 서비스를 실행하여 127.0.0.1로 접속해서 본 화면

 

 

1. Lambda로 flask를 실행을 시도하다 포기하고, ECS Fargate 선택

 Serverless가 대세라 EC2에 올리면 기존 로컬PC에서 개발하던 환경 그대로 구성하면 되니까 편리할 것 같았지만, 나중에 EC2 OS에 대한 보안, 서버 관리, 소스 배포 등 장기적으로 생각해 보면 더 피곤할 것 같아 Lambda를 고려했습니다.
 내가 갖고 있던 소스들을 Lambda에 구성해서 시도를 했는데 암호화 모듈(Crpyto)이 정상적으로 동작하지 않아 포기를 했습니다. Labmda에서 암호화 모듈을 사용하는 게 제약이 있는 것인지 모듈 사용이 안 되는 것인지 StackOverFlow에서도 실패 사례를 많이 보고 빠른 포기 후 ECS Fargate를 선택하여 구성했습니다. 

1-1 ECR 리포지터리 생성

 ECS 컨테이너를 쉽게 생각하긴 했습니다. 처음 접해야 하는 것은 ECR(Elastic Container Registry)이고, 여기에 내 소스파일들을 Docker 이미지화 한 것을 업로드해야 했습니다. 

 ECR은 리포지토리를 생성할 때, 설정하는 이름으로 리포지토리 URI를 만들게 됩니다. 내 로컬PC에서는 AWSCLI를 이용해서 ECR에 로그인하고, ECR에 Docker 이미지를 PUSH하면 끝이었습니다. 

1-2 ECS TASK Definition 생성

 이제 내가 만든 Docker 이미지를 Fargate로 띄우기 위해 ECS클러스터를 만들고, 태스크 정의(TASK Definition)을 설정하면 됩니다. 태스크 정의를 만들 때, 시작 유형을 EC2 또는 Fargate를 선택할 수 있습니다. 태스크 정의를 할 때 중요한 필수 컨테이너 정보 설정입니다. 

컨테이너 세부 정보와 컨테이너 포트 매핑 정도만 설정하고, 태스크 정의를 생성합니다.

1-3 ECS TASK 실행

그리고 ECS의 서비스가 아닌 TASK 항목에서 "새 태스크 실행"을 클릭하여 TASK를 띄워 내 Docker가 정상적으로 동작하는지 확인합니다. 

아래 배포 구성에서 "패밀리"항목이 내가 위에서 생성한 태스크 정의(TASK Definition)을 지정하고, 해당 TASK(Fargate)가 내가 구성한 VPC에서 동작하기 위해 네트워킹도 적절하게 구성해 줍니다.

1-4 ECS Fargate 확인

이제 Fargate(TASK)가 실행되었고, 자동 할당된 퍼블릭IP로 서비스가 동작하는 것을 확인할 수 있습니다. 
여기까지가 전체 진행 단계의 약 50% 입니다. 그리고 중간에 생략된 부분이 많이 있습니다. 태스크 정의(TASK Definition)를 생성할 때 지정해야 되는 역할(Role)과 내 Docker 이미지 내부 Flask가 Listen 하는 포트에 대한 Security Group 설정, ECR에 소스 업로드(PUSH)를 위한 설정 등은 생략했습니다. 

 현재 상태에서 띄워놓은 Fargate가 비정상 동작으로 중지 될 경우, 서비스 불가 상태가 됩니다. 하지만 ECR에 새로운 소스를 업로드하고, 새로운 이미지를 Fargate로 띄우는 동작까지는 가능한 상태입니다. 나는 서비스 연속성을 가져가기 위해 ECS 서비스를 사용해 Fargate가 비정상으로 중지 될 경우 동일한 Docker이미지를 ECR에 등록된 이미지를 갖고 Fargate를 자동으로 띄우는 것을 구성하려고 합니다.

 

 

2. ECS 서비스를 통해 Fargate 연속성을 보장

 ECS를 제대로 사용하기 위해서는 ECS서비스를 생성해서 TASK(Fargate)를 관리해야 합니다. ECS서비스를 구성해 놓으면 TASK(Fargate)가 비정상으로 중지되어 사라지면, ECS서비스는 TASK를 다시 실행하여 새로운 Fargate를 실행합니다.

 ECS서비스는 위 TASK 실행 방식과 비슷한데 추가되는 중요한 것은 로드밸런서를 구성하는 것입니다. 
 이 로드밸런서는 나중에 CodePipeline을 통한 배포 작업 시 블루/그린 또는 롤링 배포 시 자동으로 적절하게 대상(Target)을 변경하여 서비스 연속성을 가져갑니다. 

 추가 옵션으로 서비스 자동 크기 조정(Auto Scale)을 지정하여 Fargate에 부하가 발생할 때 Scale Out 후 부하 감소 시 Scale In을 설정할 수 있습니다. 

위와 같이 설정하여 ECS 서비스를 생성하고, 기존 TASK(Fargate)를 중지하면 서비스는 자동으로 TASK를 실행하여 또 다른 Fargate를 띄우게 됩니다.

이제 내 소스로 만들어진 Docker 컨테이너가 ECS Fargate로 서비스를 운영하게 되었습니다. 

하. 지. 만

만약 소스가 변경이 될 경우, 나는 로컬PC에서 다시 도커 이미지를 Build 하고 ECR에 PUSH하여 이미지를 업로드하고, Fargate를 바꿔치기하는 작업을 수동으로 해야 하는 작업이 필요합니다.

이제 변경 된 소스가 자동으로 배포되는 과정을 구성합니다.

 

 

3. CodePipeline을 통한 CI/CD 구성

 많은 사람들이 CodePipeline을 이용해서 소스 배포를 사용하고 있습니다. Jenkins같은 배포 도구를 사용하는 경우도 있지만 Jenkins를 또 언제 만들고 구성을 하겠습니까라는 생각으로 CodePipeline으로 구성했습니다.

 CodePipeline을 이용하기 위해서는 CodeCommit(레포지터리), CodeBuild(빌드), CodeDeploy(배포) 라는 3가지 도구를 사용합니다. 

 3-1 CodeCommit 

 ECR을 만들어 봤기 때문에 CodeCommit은 더 쉽습니다. 다만 내 로컬PC에 git 연동이 필요합니다. 이 과정은 CodeCommit 리포지토리를 생성할 때 AWS콘솔에서 가이드가 제공됩니다.

 위 내용처럼 HTTPS를 통한 Git 연동을 통해 내 로컬PC소스와 CodeCommit 리포지터리에 엑세스합니다. 내 로컬PC소스는 Visual Studio Code를 이용하는데, 여기에서 작업하던 프로젝트의 소스 변경을 하게 되면 Git에서 변경을 감지하여 CodeCommit 브랜치(Main 또는 Master)로 커밋을 하라고 알려줍니다.

appspec.yml 파일을 수정하면 Git에서 변경 사항을 반영하라고 알려줍니다.

 Git  설정(git init, git add . 등)이 잘 되었다면 CodeCommit 리포지터리에 내 소스가 Push된 것을 볼 수 있습니다.

 

3-2 CodeBuild 

 다음으로 CodeBuild를 설정하기 위해 빌드 프로젝트를 생성합니다. 빌드 프로젝트를 생성할 때, 위에 구성한 CodeCommit 리포지토리로 소스 공급자를 지정합니다. 

 CodePipeline을 구성할 때, CodeBuild가 가장 중요한 단계입니다.  바로 build를 위한 buildspec.yml을 구성해야 합니다. buildspec.yml은 CodeBuild 단계에서 프로젝트를 정의하는 데 사용되며 CodeCommit 리포지터리에 있는 내 소스를 빌드하고 배포를 하기 위해 ECR까지 이미지를 PUSH하는 것을 정의합니다.

 buildspec.yml이 실행되는 CodeBuild환경에서 정의한 관리 이미지는 Docker이미지를 생성할 수 있는 엔진이 기본 탑재되어 있어 buildspec.yml에서 별도로 Docker엔진을 설치하는 단계는 작성하지 않아도 됩니다.

version: 0.2

phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin [ECR ARN]

  build:
    commands:
      - echo Build started on `date`
      - echo Building the Docker image...
      - docker build -t webview .
      - echo Build completed on `date`

  post_build:
    commands:
      - echo Pushing the Docker image...
      - docker tag webview [ECR URI]
      - docker push [ECR URI]
      - echo Build completed on `date`

artifacts:
  files:
    - '**/*'
    - imagedefinitions.json
  base-directory: .
  name: build-artifact-name
  discard-paths: no

위 Sample로 작성된 buildspec.yml을 보면 pre_build에서 ECR로그인, build단계에서 Docker build, post_build단계에서 ECR주소로 이미지 태깅 후 PUSH하는 단계입니다. 기존 로컬PC에서 ECR로 Docker이미지를 보내는 과정을 여기서 수행합니다.  

추가로 중요한 것은 imagedefinitions.json 입니다. 이 파일은 정의되어 CodeCommit 리포지터리에 같이 있어야 합니다. 
imagedefinitions.json파일은 컨테이너의 이름과 imageUri가 정의되어 있어야 합니다.

[
  {
    "name": "webview",
    "imageUri": "ECR URI"
  }
]

이 imagedefinitions.json은 CodeDeploy단계에서 해당 값을 참조하여 컨테이너를 배포하게 됩니다.

3-3 CodeDeploy

CodeDeploy단계에는 Amazon ECS를 선택합니다. 여기에는 두 가지 방식이 있습니다. 

 - Amazon ECS (Rolling Deployment) :
     Rolling Deployment: 새로운 버전의 서비스를 점진적으로 배포하는 방식입니다.
     기존 서비스의 Task를 새로운 버전으로 교체하면서 서비스가 중단되지 않도록 조절합니다.

 - Amazon ECS(Blue/Green) : 
    새로운 버전의 서비스를 기존 버전과 완전히 분리된 환경에 배포한 다음, 트래픽을 전환하여 전환 동안에도 무중단 서비스를 제공합니다.

어떤 배포 방식을 선택할지는 상황에 따라 다르며, 롤백 용이성, 무중단 배포, 테스트 환경 등을 고려하여 결졍해야 합니다.
아래 배포 스테이지를 구성할 때, Build단계에서 지정한 Imagedefinition.json파일을 정의해야 합니다.

imagedefinition.json 파일 지정.

 추가적으로 Amazon ECS를 통한 Deploy를 구성하게 되면 appspec.yml 파일은 필요하지 않습니다. appspec.yml은 보통 EC2 인스턴스에 배포 시 사용되며, ECS에 배포로 설정하게 되면 ECS서비스 설정 자체에서 제어합니다. 

 

4. Serverless(ECS Fargate) + CI/CD(CodePipeline) 구성 완료

 이제 Visual Studio Code에서 작업하던 소스를 변경하여 Commit & Push를 하고 CodePipeline을 보면 자동 배포가 이루어지는 것을 볼 수 있습니다.

CodePipeline 구성 시 ImageDefinition.json에 잘 못 설정된 컨테이너 이름으로 오류, 권한 오류 등 다양한 경험을 겪었습니다. 다시 구성해도 동일하게 에러가 발생하면 당황할 것 같아 내용 정리했는데 또 당황하게 될 것 같기는 합니다. 

Serverless + CI/CD 구성 관련하여 추가되는 내용이 있으면 포스팅하겠습니다.

감사합니다.

728x90