본문 바로가기

Backend/AWS

[AWS] Docker Image와 Elastic Beanstalk를 이용한 개발 환경 CI/CD

728x90

개요

현재 하고 있는 프로젝트에서 dev 환경과 prod 환경을 분리해 각각 배포해야 하는 상황이다.

dev 환경은 최대한 간단하게 구성하려고 했고, CI/CD를 쉽게 구축할 수 있어야 하므로 Elastic Beanstalk을 선택했다.

 

전체적인 CI/CD 진행은 Github Actions를 이용했고, 이미지 저장소는 AWS의 ECR을 이용했다.

CI/CD의 대략적인 과정은 다음과 같다.

  1. develop 브랜치의 push event
  2. Dockerfile을 이용해 Image 생성
  3. 생성한 Image를 ECR에 저장
  4. 위 Image를 이용해 Dockerrun.aws.json 파일을 생성하고, S3에 저장
  5. Dockerrun.aws.json 파일을 이용해 Elastic Beanstalk에 배포

Elastic Beanstalk를 중점적으로 이미지를 이용해 배포하기 위해서 필요한 것과 어떤 부분들을 자동으로 해주는지 기록하려고 한다.

Elastic Beanstalk

Elastic Beanstalk을 이용하면 애플리케이션을 빠르게 배포하고 관리할 수 있다. 콘솔에서 간단한 옵션 선택만으로 용량 프로비저닝, 로드 밸런싱, 모니터링 등에 대한 세부 정보를 자동으로 처리한다. 또한 Elastic Beanstalk에 대한 추가 비용 없이 사용한 리소스에 대한 비용만 지불하면 된다.

 

Elastic Beanstalk를 Image로 이용해 배포하기 위해선 두 가지 파일이 필요하다.

  1. Image file
  2. Dockerrun.aws.json

먼저 Image file은 애플리케이션을 Dockerfile을 이용해 이미지로 만든 파일이다. 앞서 언급한 대로 이 파일은 ECR에 저장했다.

Dockerrun.aws.json 파일은 어떤 이미지를 사용할 것인지, 호스트와 컨테이너 간의 포트 매핑은 어떻게 되는지 등 이미지를 컨테이너로 올리는 데 필요한 설정을 정의한다.

 

Dockerrun.aws.json 파일에 ECR Name을 명시할 수 있다. Dockerrun.aws.json를 생성하는 스크립트를 보자.

		echo '{
            "AWSEBDockerrunVersion": "1",
            "Image": {
              "Name": "'$ECR_REGISTRY'/'$ECR_REPOSITORY':latest",
              "Update": "true"
            },
            "Ports": [
              {
                "ContainerPort": "8080"
              }
            ]
          }' > Dockerrun.aws.json

개발 환경이기 때문에 단일 컨테이너 환경으로 설정했다.

만약 다중 컨테이너 환경이라면, 위 AWSEBDockerrunVersion의 값을 2로 지정하면 된다.

 

사용한 Github Actions 스크립트는 다음과 같다.

name: CI/CD

on:
  push:
    branches:
      - develop

env:
  ECR_REGISTRY: ${{ secrets.AWS_ECR_REGISTRY }}
  ECR_REPOSITORY: ${{ secrets.AWS_DEV_ECR_REPOSITORY }}
  AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}

jobs:
  build_and_push:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up JDK 17
        uses: actions/setup-java@v2
        with:
          distribution: 'temurin'
          java-version: '17'

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Cache Gradle packages
        uses: actions/cache@v2
        with:
          path: ~/.gradle/caches
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - name: Build with Gradle
        run: ./gradlew clean build -x test

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-2

      - name: Sign in to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v2

      - name: Build and push Docker image to Amazon ECR
        run: |
          docker build -t $ECR_REPOSITORY:latest .
          docker tag $ECR_REPOSITORY:latest $ECR_REGISTRY/$ECR_REPOSITORY:latest
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest

      - name: Create Dockerrun.aws.json file using ECR image
        run: |
          echo '{
            "AWSEBDockerrunVersion": "1",
            "Image": {
              "Name": "'$ECR_REGISTRY'/'$ECR_REPOSITORY':latest",
              "Update": "true"
            },
            "Ports": [
              {
                "ContainerPort": "8080"
              }
            ]
          }' > Dockerrun.aws.json

      - name: Upload Dockerrun.aws.json file to S3
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_REGION: ap-northeast-2
        run: |
          aws s3 cp Dockerrun.aws.json s3://$AWS_S3_BUCKET/dev/Dockerrun-${{ github.sha }}.aws.json

      - name: Continuous Deploy to Elastic Beanstalk
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_REGION: ap-northeast-2
        run: |
          aws elasticbeanstalk create-application-version --application-name flash-application-dev --version-label ${{ github.sha }} --source-bundle S3Bucket=$AWS_S3_BUCKET,S3Key="dev/Dockerrun-${{ github.sha }}.aws.json"
          aws elasticbeanstalk update-environment --application-name flash-application-dev --environment-name Flash-application-dev-env --version-label ${{ github.sha }}

 

CI/CD 과정을 모두 Github Actions로 처리했기 때문에 IAM 사용자를 생성해야 한다.

만약 필요한 권한만을 줘야 한다고 하면 조금 까다로울 수 있다. 그 이유는 Elastic Beanstalk가 자동으로 처리하는 작업에 대한 권한을 모두 줘야 명령이 가능하기 때문이다. elasticbeanstalk, ec2, cloudwatch, logs, s3 뿐만 아니라 autoscaling, cloudformation에 대한 권한도 필요하니 잘 확인하고 부여하자.

 

기존에 Docker 이미지를 이용해 CI/CD를 구축할 땐, 애플리케이션 이미지 업데이트마다 스크립트나 self-hosted-runner를 통해 직접 배포된 인스턴스에 접근해 이미지를 업데이트시켜 줬다. 하지만 Elastic Beanstalk를 이용하면, 업데이트된 이미지 이름만 넣어주면 되니 훨씬 편한 것 같다. 이번 과정에선 ECR을 이용해 이미지를 저장했지만, Dockerhub에 저장해도 이미지 업데이트가 가능하다.

 

애플리케이션 이미지 업데이트 뿐만 아니라 VPC, RDS, CloudWatch 등 다른 AWS 서비스와의 연결도 환경 구성을 업데이트하면 편하게 할 수 있기 때문에 매우 편리하다.

 

앞으로도 간단하게 웹 애플리케이션 CI/CD를 구축해야 한다고 하면, Elastic Beanstalk를 쓸 것 같다.

 

 

 

 

728x90