Github Actions + CodeDeploy + S3로 CI/CD 구축

짱J·2023년 2월 3일
1

DevOps

목록 보기
8/8
post-thumbnail

들어가며

이전 포스트 Docker를 활용한 Spring Boot 프로젝트 EC2 배포Github Actions과 Docker을 활용한 CI/CD 구축를 통해 Github Actions과 Docker를 사용하여 CI/CD 환경을 구축하는 방법에 대해 알아보았다.

하지만 도커를 사용해 배포를 하기 위해서는, 스프링 프로젝트 빌드 파일을 도커 이미지로 만들어 Docker Hub의 Public Repository에 올려야 한다. (무료로 사용하기 위해서)

그렇게 되면, 모든 사람이 우리의 빌드 파일에 접근할 수 있고 만약 누군가 이를 역빌드한다면 AWS key 등 민감한 정보가 노출될 수 있다는 위험이 있다.

그래서 이번 포스트에서는 Docker 대신 CodeDeploy와 S3를 사용한 CI/CD 환경을 구축해보고자 한다.

참고로 AWS CodeDeploy를 사용하여 EC2에 코드를 배포하는 것은 비용이 부과되지 않는다 :)


배포 파이프라인

  1. 코드를 작성한 뒤, Github에 push를 한다.
  2. develop 브랜치에 push가 발생하면, Github Actions이 실행된다.
  3. Github Actions는 빌드를 하여 코드에 문제가 없는지 확인한다.
  4. Github Actions는 프로젝트 파일을 압축하여 AWS S3로 전송하고, CodeDeploy에게 배포를 요청한다.
  5. CodeDeploy는 S3로부터 zip 파일을 받아 배포를 진행한다.

이번 포스트에서는 EC2 인스턴스 생성이나 S3 버킷 생성에 대해 다루지 않을 것이다.


IAM 권한 생성

CodeDeploy로 배포 자동화 환경을 만들기 위해서는 2가지 IAM 역할이 필요하다.

  1. CodeDeploy를 위한 역할
  2. EC2 인스턴스에서 CodeDeploy Agent를 활용하여 S3에 있는 배포 파일을 가져오기 위한 S3 접근 가능 역할

AWS S3 IAM 역할 생성

CodeDeploy용 역할

역할 만들기 버튼을 눌러 역할을 생성하자.

  • 신뢰할 수 있는 엔티티 유형 - AWS 서비스
  • 사용 사례 - CodeDeploy
    선택해주자.

역할 이름과 설명을 적어준다.

  • AWSCodeDeployRole 권한을 부여한 뒤 역할 생성을 마무리한다.

EC2용 역할

  • 신뢰할 수 있는 엔티티 유형 - AWS 서비스
  • 사용 사례 - EC2
    선택해주자.

역할 이름과 설명을 적고, AWSCodeDeployAccess, AmazonS3FullAccess 권한을 추가해주어 역할을 만든다.

그리고 EC2 서비스로 들어가서 위 사진과 같은 방법으로 EC2 인스턴스에 IAM 역할을 설정해주자. 이 과정을 하지 않아 한참 헤맸었다 🥲

(뒤 과정에서 IAM 역할을 설정하지 않았을 때 발생한 에러 스크린샷이다.)

S3 사용자 권한 추가

이전에 S3 버킷을 만들며 IAM 사용자를 만든 적이 있다.
IAM 사용자에게 AWSCodeDeployFullAccessAWSCodeDeployRole 권한을 추가해준다.


CodeDeploy 애플리케이션 만들기

이제 배포 과정을 관리할 CodeDeploy 애플리케이션을 만들자.


애플리케이션 생성 버튼 클릭


애플리케이션 이름을 지어주고, 컴퓨팅 플랫폼은 EC2/온프레미스를 선택한다.

배포 그룹 만들기

다음으로는 CodeDeploy 애플리케이션에 연결할 배포 그룹을 만들어야 한다.

배포 이름을 지어주고, 서비스 역할에 앞서 만든 CodeDeploy용 역할을 추가해준다.

EC2 인스턴스의 태그를 연동한다.
만약 EC2 인스턴스에 태그가 없다면, 태그 추가 버튼을 눌러 태그를 만들어준다.

혹시 몰라서 로드밸런싱만 비활성화해두고, 나머지는 기본 설정으로 하여 배포 그룹을 만들었다.


CodeDeploy agent 설치 및 실행

EC2 인스턴스로 들어가 CodeDeploy agent를 설치하자. 아래 명령어를 따라하면 된다.

나는 실행권한 추가 및 설치에서 Current running Ruby version for root is 3.0.2, but Ruby version 2.x needs to be installed라는 에러를 만났고, 이 글을 참고하여 해결하였다.

# 1. ruby 설치
sudo apt-get install ruby

# 2. wget 설치 (agent 설치 파일을 가져오기 위해 사용)
sudo apt-get install wget

cd /home/ubuntu

# 3. 설치파일 다운로드
wget https://aws-codedeploy-ap-northeast2.s3.ap-northeast-2.amazonaws.com/latest/install

# 4. 실행권한 추가 및 설치
chomd +x ./install
sudo ./install auto

설치가 되었다면, 아래 명령어를 통해 실행이 잘 되는지 확인한다.

sudo service codedeploy-agent status

만약 codedeploy-agent가 실행되지 않았다면 start를 하여 실행시켜준다.

sudo service codedeploy-agent start

배포 스크립트 작성

CI/CD 환경을 만들기 위한 AWS 서비스 세팅이 마무리되었으니, 배포 스크립트 파일을 작성하자.

CodeDeploy 설정 파일

프로젝트 최상단에 appspec.yml이라는 파일을 만들고, 아래와 같이 내용을 채워준다.
appspec.yml은 CodeDeploy의 설정 파일로, 배포 시점에 특정한 쉘을 실행시킬 수 있다.

지금은 단순 배포만 할 예정이기 때문에 설치가 된 이후에 deploy.sh라는 스크립트를 실행시키도록 설정한다.

version: 0.0
os: linux

files:
  - source: /
    destination: /home/ubuntu/app # 인스턴스에서 파일이 저장될 위치

hooks:
  AfterInstall:
    - location: scripts/deploy.sh
      timeout: 60
      runas: ubuntu

참고로 나는 최상단에 scripts라는 폴더를 만들고 그 안에 deploy.sh 파일을 만들어주었다.
만약 appspec.yml처럼 최상단에 deploy.sh 파일을 만들면 location에서 scripts/ 부분을 지워야 한다.

배포 쉘 스크립트

REPOSITORY=/home/ubuntu/app
cd $REPOSITORY

APP_NAME=demo
JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep '.jar' | tail -n 1)
JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME

CURRENT_PID=$(pgrep -f $APP_NAME)

if [ -z $CURRENT_PID ] #2
then
  echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다."
else
  echo "> kill -15 $CURRENT_PID"
  sudo kill -15 $CURRENT_PID
  sleep 5
fi

echo "> $JAR_PATH 배포" #3
nohup java -jar \
        -Dspring.profiles.active=dev \
        build/libs/$JAR_NAME > $REPOSITORY/nohup.out 2>&1 &

인스턴스에서 프로젝트를 배포하는 쉘 스크립트이다.
쉘 스크립트에 대한 자세한 내용은 EC2 서버에 프로젝트를 배포해 보자를 참고하자ㅏ.

Github Actions 파일

저번에 했던거처럼 ./github/workflows 안에서 스크립트 파일을 작성해준다.

# github repository actions 페이지에 나타날 이름
name: CI/CD using Github Actions & AWS CodeDeploy

# event trigger
# main이나 develop 브랜치에 push가 되었을 때 실행
on:
  push:
    branches: [ "main", "develop" ]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      # JDK setting - github actions에서 사용할 JDK 설정
      - uses: actions/checkout@v3
      - name: Set up JDK 11
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'

      # gradle caching - 빌드 시간 향상
      - name: Gradle Caching
        uses: actions/cache@v3
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      # 환경별 yml 파일 생성(1) - application.yml
      - name: make application.yml
        if: |
          contains(github.ref, 'main') ||
          contains(github.ref, 'develop')
        run: |
          mkdir ./src/main/resources # resources 폴더 생성
          cd ./src/main/resources # resources 폴더로 이동
          touch ./application.yml # application.yml 생성
          echo "${{ secrets.YML }}" > ./application.yml # github actions에서 설정한 값을 application.yml 파일에 쓰기
        shell: bash

      # 환경별 yml 파일 생성(2) - dev
      - name: make application-dev.yml
        if: contains(github.ref, 'develop')
        run: |
          cd ./src/main/resources
          touch ./application-dev.yml
          echo "${{ secrets.YML_DEV }}" > ./application-dev.yml
        shell: bash

      # 환경별 yml 파일 생성(3) - prod
      - name: make application-prod.yml
        if: contains(github.ref, 'main')
        run: |
          cd ./src/main/resources
          touch ./application-prod.yml
          echo "${{ secrets.YML_PROD }}" > ./application-prod.yml
        shell: bash

      # gradle build
      - name: Build with Gradle
        run: ./gradlew build -x test
	 
      # make zip file
      - name: Make zip file
        run: zip -qq -r ./$GITHUB_SHA.zip .
        shell: bash
        
      # AWS 사용자 정보 입력
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.ACCESS_KEY_SECRET }}
          aws-region: ap-northeast-2
          
      # S3에 zip 파일 업로드
      - name: Upload to S3
        run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://${{ secrets.S3_BUCKET_NAME }}/deploy/$GITHUB_SHA.zip --source .
        
      # CodeDeploy에 배포 요청
      - name: Code Deploy
        run: aws deploy create-deployment --application-name ${{ secrets.CODE_DEPLOY_APP_NAME }}
          --deployment-config-name CodeDeployDefault.OneAtATime
          --deployment-group-name ${{ secrets.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }}
          --s3-location bucket=${{ secrets.S3_BUCKET_NAME }},bundleType=zip,key=deploy/$GITHUB_SHA.zip

저번 포스트에서 Github Actions script 파일에 대해 자세하게 다뤘으니, 자세한 설명은 생략하겠다.
저번 포스트와 달라진 내용은 아래와 같다.

# make zip file
- name: Make zip file
  run: zip -qq -r ./$GITHUB_SHA.zip .
  shell: bash
        
# AWS 사용자 정보 입력
- name: Configure AWS credentials
  uses: aws-actions/configure-aws-credentials@v1
  with:
    aws-access-key-id: ${{ secrets.ACCESS_KEY_ID }}
    aws-secret-access-key: ${{ secrets.ACCESS_KEY_SECRET }}
    aws-region: ap-northeast-2
          
# S3에 zip 파일 업로드
- name: Upload to S3
  run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://${{ secrets.S3_BUCKET_NAME }}/deploy/$GITHUB_SHA.zip --source .
        
# CodeDeploy에 배포 요청
- name: Code Deploy
  run: aws deploy create-deployment --application-name ${{ secrets.CODE_DEPLOY_APP_NAME }}
   --deployment-config-name CodeDeployDefault.OneAtATime
   --deployment-group-name ${{ secrets.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }}
   --s3-location bucket=${{ secrets.S3_BUCKET_NAME }},bundleType=zip,key=deploy/$GITHUB_SHA.zip
  • $GITHUB_SHA는 AWS에서 자체적으로 제공해주는 변수로, 압축 파일을 만들 때 이름의 중복을 회피하기 위해 사용하였다.
  • aws-access-key-idaws-secret-access-key에는 IAM 사용자 정보를 입력해준다.
  • S3에 zip 파일을 업로드할 때, 버킷을 깔끔하게 관리하기 위해 버킷 안에 /deploy 폴더를 만들고 업로드할 예정이므로, 버킷 이름 뒤에 /deploy를 추가해주었다.

💣 트러블슈팅

CodeDeploy에 배포를 요청하는 부분에서 ${{ secrets.CODE_DEPLOY_APP_NAME }}에 내가 원하는 이름을 적는 것인 줄 알고, 마음대로 이름을 적어서 발생한 오류이다.

앞서 만든 CodeDeploy 애플리케이션의 이름을 적어주어야 한다.


Github Actions는 잘되는데 배포가 안된다면 ...

나도 Github Actions에서는 ✅이 뜨지만, 실제로는 배포가 되지 않는 어려움을 겪었다.
만약 Github Actions에 체크 표시가 뜬다면, S3에 압축 파일을 업로드하고 CodeDeploy에 요청하는 것까지는 잘 된 것이다.

그러므로 이후에 발생한 오류들은 CodeDeploy 대시보드에서 확인할 수 있다.

대시보드에서 배포 ID를 클릭하고, 배포 수명 주기 이벤트에서 View events를 클릭하여 어디가 잘못됐는지 알 수 있다. + 이 글을 참고하여 codedeploy agent의 로그를 함께 확인하면 에러 잡기가 조금 더 참고할 것이다.

나는 대부분 배포 스크립트를 적을 때 경로명을 잘못 작성하여 배포가 안되었다.

그렇게 자잘한 오류들을 수정하면 배포가 잘 된 것을 확인할 수 있다 ^~^

✨ Reference

https://geonoo.tistory.com/162
https://shinsunyoung.tistory.com/120
https://countrymouse.tistory.com/entry/awsec2cicd2
https://supern0va.tistory.com/27
https://github.com/aws/aws-codedeploy-agent/issues/301#issuecomment-1129912011
https://velog.io/@linho1150/SpringBoot-CodeDeploy-Ec2로-CICD하기#312-ouput되는-jar파일의-이름을-고정하기-위해-buildgradle파일-수정-아래-코드-추가
https://sarc.io/index.php/aws/1327-tip-codedeploy-missing-credentials
https://stackoverflow.com/questions/41997426/instanceagentpluginscodedeployplugincommandpoller-missing-credentials
https://twofootdog.tistory.com/38

profile
[~2023.04] 블로그 이전했습니다 ㅎㅎ https://leeeeeyeon-dev.tistory.com/

0개의 댓글