GitAction + CI/CD + AWS

murphytklee·2023년 5월 27일
0

0. GitAction + AWS + CI/CD 구축

쉽게 설명하면 Github에서 제공하는 GitAction을 통해서 프로젝트의 레포지토리에 CI/CD를 적용할 수 있다.

배포 플랫폼, 환경에 따라 설정법은 다양하다.

AWS EC2를 이용한 방법의 흐름은 다음과 같다.

  1. 레포지토리에서 push가 일어나면 테스트 과정을 거치고 Build되어 AWS S3에 업로드 된다.
  2. AWS CodeDeploy가 S3에 접근해 빌드된 파일을 EC2에 배포를 도와준다.

1. CI/CD 이란?

1.1 CI (Continuous Integration)

  • CI는 지속적 통합이라는 뜻으로 개발을 진행하면서도 품질을 관리할 수 있도록 하는 것으로 여러 명이 하나의 코드에 대해서 수정을 진행해도 지속적으로 통합하면서 관리할 수 있음을 의미한다.
  • CI가 나오기 전까지는 위와 같이 개발을 끝마치고 배포가 되어야만 코드에 오류는 없는지, 올바르게 동작하는지를 검증하며 코드 품질을 관리할 수 있었다.
  • CI를 적용하게 되면 각자의 개발자가 자신의 구현해야 할 기능을 구현하면 된다. 이후 완성이 되면 main 브랜치와 통합하고 코드가 잘 빌드되는지 보고, 올바르게 동작하는지 테스트하며 코드에 버그가 있다면 해결한다.
  • 하지만 개발자가 직접 코드를 병합하고 빌드, 테스트를 검증하는 것은 시간이 많이 소요될 뿐만 아니라 귀찮고 그 양도 프로젝트의 크기가 커질수록 많아질 수밖에 없다.
  • 이를 자동화하면 개발자가 빌드와 테스트를 직접 하지 않고도 수정한 코드를 브랜치에 병합하기만 하면 자동으로 빌드와 테스트를 검증할 수 있다.
  • 개발자가 단위별로 구현한 부분을 병합할 때마다 자동화된 빌드와 테스트가 트리거되어 실행된다. 결과를 통해 우리는 어떤 부분에서 문제가 있는지 배포 전에 확인할 수 있고, 배포가 완성된 후에야 버그를 수정할 수 있던 기존의 문제를 빠르고 정확하게 해결할 수 있다.
  • 결과적으로 버그를 신속하게 찾아 해결할 수 있을 뿐 아니라, 소프트웨어 품질을 개선하고 새로운 소프트웨어 업데이트를 검증하고 릴리즈하는데에 걸리는 시간을 단축할 수도 있다.

1.2 CD **(Continuous Deployment)**

  • 이제 지속적 통합을 거친 코드에 대해서 신뢰할 수 있고 바로 배포할 수 있다.
  • CD는 지속적 배포로 소프트웨어가 항상 신뢰 가능한 수준에서 배포될 수 있도록 관리하자는 개념으로 지속적 제공(Continuous Delivery)로 사용되기도 한다.
  • 지속적 제공은 CI를 통해서 새로운 소스코드의 빌드와 테스트 병합까지 성공적으로 진행되었다면, 빌드와 테스트를 거쳐 github과 같은 저장소에 업로드하는 것을 의미한다.
  • 지속적 배포는 이렇게 성공적으로 병합된 내역을 저장소뿐만 아니라 사용자가 사용할 수 있는 배포환경까지 릴리즈하는 것을 의미한다.
  • 지속적 배포에서는 지속적 통합을 통해 빌드한 소스코드를 테스트 가능한 알파나 베타 버전으로 만든다. 이 버전에서 테스트를 수행해 문제가 발생하면 수정한 뒤 정식 버전으로 배포를 진행한다.

2. Github Actions란?

  • Github Actions이란 소프트웨어 개발 라이프사이클 안에서 Pull Request, Push 등의 이벤트 발생에 따라 자동화된 작업을 진행할 수 있게 해주는 기능이다.

2.1 주요 기능

  1. CD 배포 자동화

    이 글의 작성 목적이자 Github Actions을 활용하는 가장 대표적인 예시 중 하나이다.

    로컬 레포지토리에서 원격 레포지토리로 푸쉬하고 난 후, Github Actions에서는 이벤트 발생에 따라 자동으로 빌드 및 배포하는 스크립트를 실행시켜준다.

    애플리케이션의 규모가 클수록 빌드, 배포 시간이 오래걸리는데 이를 자동화시켜놓으면 해당 시간을 낭비하지 않을 수 있다.

  2. Testing

    팀 프로젝트를 진행하다가 Pull Request를 보내면 자동으로 테스트를 진행하는 것또한 Github Actions으로 구현할 수 있다.

    따라서 테스트 성공 여부에 따라서 자동으로 PR을 Open 및 Close 할 수 있다.

  3. Cron Job

    Github Actions를 통해 특정 시간대에 스크립트를 반복 실행하도록 구현할 수 있다.

    매일 특정 시간이 되면 크롤링 작업을 진행한다는 등의 예시가 존재한다.

2.2 Github Actions의 구성 요소

각 구성 요소는 Workflow, Event, Job, Step, Action, Runner가 있다.

  1. Workflow

    Workflow란 레포지토리에 추가할 수 있는 일련의 자동화된 커맨드 집합다.

    하나 이상의 Job으로 구성되어 있으며, Push나 PR과 같은 이벤트에 의해 실행될 수도 있고 특정 시간대에 실행될 수도 있다.

    빌드, 테스트, 배포 등 각각의 역할에 맞는 Workflow를 추가할 수 있고, .github/workflows 디렉토리에 YAML 형태로 저장한다.

  2. Event

    Event란 Workflow를 실행시키는 Push, Pull Request, Commit 등의 특정 행동을 의미한다.

    그리고 위의 특정 행동이 아닌, Repository Dispatch Webhook을 사용하면 Github 외부에서 발생한 이벤트에 의해서도 Workflow를 실행시킬 수 있다.

    Event 종류에 대해 더 알고 싶다면 Github Actions 공식 문서

  3. Job

    Job이란 동일한 Runner에서 실행되는 여러 Step의 집합을 의미한다.

    기본적으로 하나의 Workflow 내의 여러 Job은 독립적으로 실행되지만, 필요에 따라 의존 관계를 설정하여 순서를 지정해줄 수 있다.

    예시로 테스트를 수행하는 Job과 빌드 작업을 수행하는 Job이 하나의 Workflow에 존재한다고 생각해봅시다.

    여기서 테스트 Job은 반드시 빌드 Job 이후에 수행되어야 하는데, 여기서 의존 관계를 설정해 빌드 Job이 성공적으로 끝나야 테스트 Job을 수행할 수 있도록 지정할 수 있습니다.

    따라서 만약 빌드가 실패할 시에는 테스트 Job도 실행되지 않겠죠.

  4. Step

    Step이란 커맨드를 실행할 수 있는 각각의 Task를 의미하는데, Shell 커맨드가 될 수도 있고, 하나의 Action이 될 수도 있다.

    하나의 Job 내에서 각각의 Step은 다양한 Task로 인해 생성된 데이터를 공유할 수 있다.

  5. Action

    Action이란 Job을 만들기 위해 Step을 결합한 독립적인 커맨드로, 재사용이 가능한 Workflow의 가장 작은 단위의 블럭이다.

    직접 만든 Action을 사용하거나 Github Community에 의해 생성된 Action을 불러와 사용할 수 있다.

  6. Runner

    Runner란 Github Actions Workflow 내에 있는 Job을 실행시키기 위한 애플리케이션이다.

    Runner Application은 Github에서 호스팅하는 가상 환경 또는 직접 호스팅하는 가상 환경에서 실행 가능하며, Github에서 호스팅하는 가상 인스턴스의 경우에는 메모리 및 용량 제한이 존재한다.


3. Workflow 생성

Workflow는 .github/workflows 디렉토리 내에 .yml 파일을 생성해도 되지만, Repository의 Actions 탭에서 자동으로 템플릿을 만들어주는 기능을 이용하는 것이 좋다.

set up a workflow yourself 로 직접 생성해도 되고 템플릿을 만들어주는 아래 기능들을 선택해도 된다.

main.yml

name: Build and Deploy Spring Boot to AWS EC2

# main 브랜치에 푸쉬 했을때
on:
  push:
    branches: [ dev ]

# 해당 코드에서 사용될 변수 설정
env:
  PROJECT_NAME: chatchallenge
  BUCKET_NAME: chatchallengebucket
  CODE_DEPLOY_APP_NAME: codeDeploy-test
  DEPLOYMENT_GROUP_NAME: codeDeploy-group

jobs:
  build-with-gradle:
    runs-on: ubuntu-20.04
    steps:
      - name: main 브랜치로 이동
        uses: actions/checkout@v3

      - name: JDK 17 설치
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: gradlew에 실행 권한 부여
        run: chmod +x ./gradlew

      - name: jasypt key주입
        run: echo ${{secrets.JASYPT}} | base64 --decode >> ./src/main/resources/application-common.yml
        
      - name: 프로젝트 빌드
        run: ./gradlew clean build -x test
      # 프로젝트 압축
      - name: Make zip file
        run: zip -r ./chatchallenge.zip .
        shell: bash
        
      # AWS 권한 확인
      - 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: ${{ secrets.AWS_REGION }}
          
          # 압축한 프로젝트를 S3로 전송
      - name: Upload to S3
        run: aws s3 cp --region ap-northeast-2 ./chatchallenge.zip s3://chatchallengebucket/chatchallenge.zip 
        
      # Send application to deployment group
      - name: Code Deploy
        run: aws deploy create-deployment --application-name $CODE_DEPLOY_APP_NAME --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name $DEPLOYMENT_GROUP_NAME --s3-location bucket=$BUCKET_NAME,bundleType=zip,key=chatchallenge.zip

간단하게 설명하면 on: push: branches: [dev] → dev 브랜치에서 push가 일어나면

jobs 를 step별로 실행하게 된다.

env 로 변수 설정도 가능하고

해당 레포지토리의 secret도 주입할 수 있다.

만약 application.yml 을 jasypt 처리 했다면
${{secrets.JASYPT}} | base64 --decode >> ./src/main/resources/application-common.yml

jasypt 키를 환경변수로 입력해줘야하고 base64로 인코딩 된 키 값을 입력해줘야한다.

Base64로 인코딩 - 온라인 Base64로 인코더

인코딩 된 텍스트를 secret에 추가해주면 된다.

이때 application의 마지막 줄을 충분히 띄워주지 않으면

위 사진과 같이 입력되면서 build되어 오류가 발생할 수 있음을 주의하자!

appspec.yml

version: 0.0
os: linux

files:
  - source:  /
    destination: /home/ubuntu/spring-github-action
    overwrite: yes

permissions:
  - object: /
    owner: ubuntu
    group: ubuntu

hooks:
  AfterInstall:
    - location: scripts/stop.sh
      timeout: 60
  ApplicationStart:
    - location: scripts/start.sh
      timeout: 60
  • appspec.yml파일은 CodeDeploy에서 배포를 관리하는 데 사용하는 YAML 형식 또는 JSON 형식의 파일이다.
  • appspec.yml파일은 파일에 정의된 일련의 수명 주기 이벤트 후크로 각 배포를 관리하는 데 사용된다.

EC2/온프레미스 기준으로 filespermissionshooks영역으로 나뉘는데 이를 섹션이라 부른다.

배포 주기에 따른 실행 순서

위 그림에서의 StartDownloadBundleInstallEnd는 스크립트 실행되는 구간에서 제외 되기에 회색으로 처리

이벤트 목록

  • DownloadBundle – 이 배포 수명 주기 이벤트 중 에이전트는 애플리케이션 수정 파일을 다음 임시 위치로 복사한다.
  • BeforeInstall - 파일 암호화 해제 및 현재 버전의 백업 만들기와 같은 사전 설치 작업에 이 배포 수명 주기 이벤트를 사용할 수 있다.
  • Install - 이 배포 수명 주기 이벤트 중에 CodeDeploy 에이전트는 수정 파일을 임시 위치에서 최종 대상 폴더로 복사한다.
  • AfterInstall - 애플리케이션 구성 또는 파일 권한 변경과 같은 작업에 이 배포 수명 주기 이벤트를 사용할 수 있다.
  • ApplicationStart - 해당 이벤트 실행 중에 중지된 서비스를 다시 시작하려면 일반적으로 ApplicationStop 이벤트를 사용한다.
  • ValidateService – 마지막 배포 수명 주기 이벤트입니다. 배포가 성공적으로 완료되었는지 확인하는 데 사용된다.

이 외에 생략된 로드 밸런서 관련한 이벤트 후크 등은 공식 문서

files:
  - source:  /
    destination: /home/ubuntu/spring-github-action
    overwrite: yes
  • source: 인스턴스에 복사할 수정된 파일 또는 디렉토리를 설정
    • 해당 경로는 appspec.yml 파일 기준 상대경로
  • destination: 인스턴스에서 파일이 복사되어야 하는 위치를 식별한다.
  • overwrite: 선택 값으로, 동일한 파일이 있을 경우 덮어쓰기 여부를 작성한다.
permissions:
  - object: /
    owner: ubuntu
    group: ubuntu

permissions 섹션은 files 섹션에서 정의한 파일이 인스턴스에 복사된 후 해당 파일에 권한이 어떻게 적용되어야 하는 지를 지정한다.

permissions는 EC2/온프레미스에만 사용되므로 AWS Lamda/ECS 배포는 resources 섹션 을 참조할 수 있다.

start.sh

#!/bin/bash

ROOT_PATH="/home/ubuntu/spring-github-action"
JAR="$ROOT_PATH/application-plain.jar"

APP_LOG="$ROOT_PATH/application.log"
ERROR_LOG="$ROOT_PATH/error.log"
START_LOG="$ROOT_PATH/start.log"
PROFILES_ACTIVE="Dspring.profiles.active=dev"

NOW=$(date +%c)

echo "[$NOW] $JAR 복사" >> $START_LOG
cp $ROOT_PATH/build/libs/chat-0.0.1-SNAPSHOT.jar $JAR

echo "[$NOW] > $JAR 실행" >> $START_LOG
nohup java -jar -$PROFILES_ACTIVE $JAR > $APP_LOG 2> $ERROR_LOG &

SERVICE_PID=$(pgrep -f $JAR)
echo "[$NOW] > 서비스 PID: $SERVICE_PID" >> $START_LOG

stop.sh

#!/bin/bash

ROOT_PATH="/home/ubuntu/spring-github-action"
JAR="$ROOT_PATH/application-plain.jar"
STOP_LOG="$ROOT_PATH/stop.log"
SERVICE_PID=$(pgrep -f $JAR) # 실행중인 Spring 서버의 PID

NOW=$(date +%c)

if [ -z "$SERVICE_PID" ]; then
  echo " [$NOW] 서비스 NouFound" >> $STOP_LOG
else
  echo " [$NOW] [$SERVICE_PID] 서비스 종료 " >> $STOP_LOG
  kill "$SERVICE_PID"
  # kill -9 $SERVICE_PID # 강제 종료를 하고 싶다면 이 명령어 사용
fi

파일 위치

EC2, CodeDeploy, S3, IAM 생성과 설정은 레퍼런스가 많으니 참고하면서 만드시면 됩니다.






reference

Spring Boot - Github Action, S3, EC2, CodeDeploy 연동

GitHub Actions를 이용한 CI/CD 구축하기

[AWS] EC2-CodeDeploy Appspec.yml에 대하여

GitHub Actions를 이용한 CI/CD 구축하기

GitHub Action으로 CI/CD 구축하기

https://creampuffy.tistory.com/190

0개의 댓글