대외활동/UMC 4th

CI/CD with Github Action & AWS S3

oxdjww 2023. 9. 6. 17:11
728x90
반응형

이전에 포스팅 했던 서비스에 CI/CD를 추가하고자 하였습니다.

 

[FlagApp] SpringBoot - Server 배포

UMC 4th Master Course로 팀원들과 앱 프로젝트를 진행하고 있습니다. Demo day에 앞서, back server를 배포하려 합니다. 목차 0. 개발환경 1. AWS EC2 생성 2. SSH Connection & git clone 3. Build with jar 4. 배포 cf. 하단

oxdjww.tistory.com


문제 상황

서버 첫 배포 이후, 지속적인 프론트와의 테스트 과정이 필요했습니다.

이 때 매번 ssh 접속 후 실행중인 jar process를 중단시키고, 재배포 하는 과정은 번거로웠습니다.

 

프로그래밍을 할 때에도 2번 이상 반복되는 작업은 함수를 만들듯이, 이런 배포 및 운영 과정에서도 반복되는 작업은 자동화를 통해 일을 효율적으로 진행하고자 합니다.

 

전체적인 실행 흐름은 이렇습니다.

 

CI/CD Flow

1. Master branch merge시, Github Action 실행

2. git pull, gradlew clean build, deploy

3. AWS 인증 과정을 통해 AWS S3에 압축하여 임시 업로드

4. AWS CodeDeploy를 통해 AWS S3에 있는 파일 압축 해제

5. 해제한 파일을 EC2 server에 배포

 

이를 그림으로 나타내면 이렇습니다.

 

1. Github Actions workflow를 통해 AWS S3 bucket이라는 임시 저장소에 프로젝트 파일(*.jar)을 업로드 합니다.

1-1. 이 때 deploy.yml 이라는 스크립트가 실행되며 작업을 수행합니다.

2. Github Actions가 각종 환경설정을 마치고 AWS CodeDeploy에게 배포 작업을 수행하게끔 합니다.

3. AWS CodeDeploy는 AWS S3 bucket에 접근하여 업로드된 압축 파일 (*.jar)을 받아 옵니다.

4. AWS CodeDeploy가 EC2 instance에 배포를 합니다. (appspec.yml 스크립트 기반 실행함. stop.sh, start.sh 등 활용)

 

자세한 내용을 이제 다뤄봅시다.

 

우선 AWS 설정부터 시작합니다.

1. EC2 tag configuration

EC2 > Instance

AWS EC2 instance 관리에서 태그를 설정해줍니다.

 

CodeDeploy에서 ec2 instance를 구분할 수 있게하기 위함입니다.

 

2. IAM 역할 추가

IAM > 역할

EC2 instance가 나중에 CodeDeploy를 이용해 AWS S3에 압축하여 올려놓은 파일을 다운로드 받을 수 있게 하기 위함입니다.

기존 역할들을 무시하고, 새 역할을 만듭니다.

 

S3 FULL을 검색하여 권한을 추가합니다.

다른 역할들과 구별하여 식별할 수 있는 이름을 정해주고, 하단에 역할 생성 버튼을 눌러 생성을 완료합니다.

EC2 > Instance

EC2 instance 섹터로 가서 방금 생성한 IAM 역할과 instance를 매핑합니다.

3. Install CodeDeploy (on EC2 instance)

EC2 instance에 ssh 접속을 하고, CodeDeploy agent를 설치합니다.

$ sudo apt update
$ sudo apt install ruby-full
$ sudo apt install wget
$ cd /home/ubuntu
$ wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
$ chmod +x ./install
$ sudo ./install auto > /tmp/logfile
$ sudo service codedeploy-agent status

Ref (공식문서)

 

Ubuntu Server용 CodeDeploy 에이전트 설치 - AWS CodeDeploy

출력을 임시 로그 파일에 쓰는 것은 Ubuntu Server 20.04에서 install 스크립트를 사용하여 알려진 버그를 해결하는 동안 사용해야 하는 해결 방법입니다.

docs.aws.amazon.com

EC2 instance에 ssh 접속을 하는 방법은 하단 포스팅을 참조하면 됩니다.

 

[FlagApp] SpringBoot - Server 배포

UMC 4th Master Course로 팀원들과 앱 프로젝트를 진행하고 있습니다. Demo day에 앞서, back server를 배포하려 합니다. 목차 0. 개발환경 1. AWS EC2 생성 2. SSH Connection & git clone 3. Build with jar 4. 배포 cf. 하단

oxdjww.tistory.com

 

4. Create AWS S3

AWS S3란?

AWS에서 제공하는 스토리지 서비스입니다.

S3 버킷에 파일을 업로드하고, 다운로드 하며 활용할 수 있습니다.

Ref (공식문서)

 

Amazon S3란 무엇인가요? - Amazon Simple Storage Service

Amazon S3란 무엇인가요? Amazon Simple Storage Service(Amazon S3)는 업계 최고의 확장성, 데이터 가용성, 보안 및 성능을 제공하는 객체 스토리지 서비스입니다. 모든 규모와 업종의 고객은 Amazon S3를 사용하

docs.aws.amazon.com

Amazon S3 > Bucket

'버킷 만들기' 버튼으로 버킷을 생성합니다.

마찬가지로 원하는 이름을 설정하되, 저는 배포할 어플리케이션 이름을 넣어 구별가능하게끔 했습니다.

나머지 옵션들은 디폴트값으로 설정하고 버킷을 생성했습니다.

성공적으로 생성된 모습입니다.

 

5. Create CodeDeploy

IAM > 역할

실질적인 배포작업을 할 CodeDeploy를 생성합니다.

IAM에서 역할을 만들어서 이 역할을 바탕으로 EC2 instance가 배포작업을 진행합니다.

마찬가지로 역할 만들기 버튼을 클릭합니다.

이번엔 서비스 검색에서 CodeDeploy를 검색하고 선택하고 다음을 누릅니다.

잘 적용된 것을 확인하고 다음을 누릅니다.

역할 이름을 식별가능하게 적어주고 하단에 역할 생성 버튼을 누릅니다.

그리고 CodeDeploy Application을 생성합니다.

개발자 도구 > CodeDeploy > Application

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

이름 지정 후, 컴퓨팅 플랫폼은 EC2/온프레미스 를 선택하고 어플리케이션 생성 버튼을 클릭합니다.

그러면 생성된 모습을 확인 할 수 있습니다.

그 후, 생성한 어플리케이션에 들어가서 배포 그룹을 생성합니다.

이름을 지정하고, 서비스 역할을 아까 만든 CodeDeploy 역할을 선택합니다.

생성된 서비스 역할이 보이지 않는다면 위를 참조하여 다시 생성하면 됩니다.

그리고 하단의 추가적인 설정을 합니다.

EC2 instance를 선택하고, key/value를 사용할 instance의 값으로 설정하면 됩니다.

처음에 만든 instance tag가 여기에 쓰입니다.

나머지 추가적인 설정은 이렇게 하고 배포 그룹 생성 버튼을 클릭합니다.

이렇게 배포 그룹까지 생성했습니다.

 

6. Create IAM user

IAM > User

직접적인 사용자를 추가하여 해당 사용자로 Github Actions의 workflow에서 접근 후, 배포작업을 진행할 겁니다.

사용자 생성을 클릭합니다.

권한 부여를 위해 다음과 같이 설정 후 다음으로 넘어갑니다.

필자는 이미 생성한 사용자가 있기에 중복 오류가 뜨는 모습입니다. 무시하셔도 됩니다.

그리고 권한을 설정할건데, 직접 정책 연결 옵션을 클릭하고, AWSCodeDeployFull과 AmazonS3FullAccess 권한을 부여합니다.

마지막으로 사용자 생성 버튼을 클릭하여 생성을 완료합니다.

생성을 완료하면 Access key ID와 Secret Access key가 있는데, 이 두 값을 Github Repo에 등록합니다.

Github > 배포할 Repo > Settings > Secret > Actions

이렇게 저장해두고, 이렇게 저장해도 나중에 확인하기는 불가능하기에 필요하면 따로 저장해두면 됩니다.

이러면 AWS 설정은 모두 마무리 되었습니다.

7. Create AppSpec.yml

이제 배포할 프로젝트의 root dir에서 appspec.yml을 생성할 겁니다.

version: 0.0
os: linux

files:
  - source:  /
    destination: /home/ubuntu/flag-app-back/
    overwrite: yes
file_exists_behavior: OVERWRITE

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

hooks:
  AfterInstall:
    - location: scripts/stop.sh
      timeout: 60
  ApplicationStart:
    - location: scripts/start.sh
      timeout: 60

필자는 이렇게 설정하였습니다.

code commits

- source : 자동 배포 시, merge된 latest file을 임시로 업로드 해놓을 위치

- destination : 임시로 업로드한 파일을 붙여넣기 할 때의 위치 (git clone해놓은 repo의 위치)

- overwrite option : 중복된 파일이 있는 경우를 방지하는 옵션

- AfterInstall : 설치된 이후 stop.sh 쉘 스크립트를 실행하여 배포중인 파일을 중단

- ApplicationStart : 중단한 후, start.sh 쉘 스크립트를 실행하여 latest ver 배포

 

start.sh와 stop.sh는 이렇게 EC2 instance 내부 project root dir에 생성하면 됩니다.

# start.sh
PROJECT_ROOT="/home/ubuntu/flag-app-back"
JAR_FILE="$PROJECT_ROOT/build/libs/flag_back-0.0.1-SNAPSHOT.jar"

APP_LOG="$PROJECT_ROOT/application.log"
ERROR_LOG="$PROJECT_ROOT/error.log"
DEPLOY_LOG="$PROJECT_ROOT/deploy.log"

TIME_NOW=$(date +%c)

# build 파일 복사
echo "$TIME_NOW > $JAR_FILE 파일 복사" >> $DEPLOY_LOG
cp $PROJECT_ROOT/build/libs/*.jar $JAR_FILE

# jar 파일 실행
echo "$TIME_NOW > $JAR_FILE 파일 실행" >> $DEPLOY_LOG
nohup java -jar $JAR_FILE > $APP_LOG 2> $ERROR_LOG &

CURRENT_PID=$(pgrep -f $JAR_FILE)
echo "$TIME_NOW > 실행된 프로세스 아이디 $CURRENT_PID 입니다." >> $DEPLOY_LOG
# stop.sh
PROJECT_ROOT="/home/ubuntu/flag-app-back"
JAR_FILE="$PROJECT_ROOT/build/libs/flag_back-0.0.1-SNAPSHOT.jar"

APP_LOG="$PROJECT_ROOT/application.log"
ERROR_LOG="$PROJECT_ROOT/error.log"
DEPLOY_LOG="$PROJECT_ROOT/deploy.log"

TIME_NOW=$(date +%c)

# build 파일 복사
echo "$TIME_NOW > $JAR_FILE 파일 복사" >> $DEPLOY_LOG
cp $PROJECT_ROOT/build/libs/*.jar $JAR_FILE

# jar 파일 실행
echo "$TIME_NOW > $JAR_FILE 파일 실행" >> $DEPLOY_LOG
nohup java -jar $JAR_FILE > $APP_LOG 2> $ERROR_LOG &

CURRENT_PID=$(pgrep -f $JAR_FILE)
echo "$TIME_NOW > 실행된 프로세스 아이디 $CURRENT_PID 입니다." >> $DEPLOY_LOG

세부 코드은 이렇게 됩니다.

그리고 build.gradle 파일에 코드를 몇줄 추가합니다.

jar {
	enabled = false
}

이는 build시 생성되는 plain.jar파일을 생성하지 않고 일반 jar 파일만 생성하게 하기 위함입니다.

 

8. Create Github Action workflow

배포할 repo > .github > workflows / deploy.yml

deploy.yml 소스파일을 생성합니다.

latest ver merge시 해당 코드가 실행되며 자동 배포가 됩니다.

name: CICD Test
run-name: Running

on:
  push:
    branches:
      - master

env:
  AWS_REGION: ap-northeast-2
  AWS_S3_BUCKET: myflagbucket
  AWS_CODE_DEPLOY_APPLICATION: cicd-test-CD
  AWS_CODE_DEPLOY_GROUP: cicd-test-CD-group

jobs:
  build-with-gradle:
    runs-on: ubuntu-22.04
    steps:
    - name: master 브랜치로 이동
      uses: actions/checkout@v3
      with:
        ref: master
    - name: JDK 11 설치
      uses: actions/setup-java@v3
      with:
        java-version: '11'
        distribution: 'corretto'
    - name : git pull
      run: git pull origin master
      

    - name: Run chmod to make gradlew executable
      run: chmod +x ./gradlew

    - name: Build with Gradle
      run: ./gradlew clean build
      
    - name: AWS credential 설정
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-region: ${{ env.AWS_REGION }}
        aws-access-key-id: ${{ secrets.CICD_ACCESS_KEY }}
        aws-secret-access-key: ${{ secrets.CICD_SECRET_KEY }}
        
    - name: S3에 업로드
      run: aws deploy push --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --ignore-hidden-files --s3-location s3://$AWS_S3_BUCKET/cicdtest/$GITHUB_SHA.zip --source .
      
    - name: EC2에 배포
      run: aws deploy create-deployment --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} --s3-location bucket=$AWS_S3_BUCKET,key=cicdtest/$GITHUB_SHA.zip,bundleType=zip

- env (환경설정) : 본인이 AWS 환경세팅 시 설정했던 이름들로 적어주면 됨

- jobs : 실질적으로 실행되어야 하는 명령어들 입력

  - 마스터 브랜치 이동

  - jdk 설치 (기존에 설치되어 있어도 혹시 모르니 재설치)

  - git pull : merge된 file reload

  - chmod +x ./gradlew (수동 배포 시 쓰지 않았지만 혹시 모르니..)

  - ./gradlew clean build

  - AWS 설정 : 상단의 env에서 설정해준 값들을 이용하여 환경설정을 마무리함. 이 때 github repo에 등록해놓은 access key, secret key가 사용됨

  - S3 업로드 : AWS CodeDeploy application을 활용하여 빌드된 산출물을 압축하여 S3 bucket에 업로드

  - EC2에 배포 : AWS CodeDeploy application를 활용하여 S3 bucket에 존재하는 산출물을 다운로드 받아 EC2에 배포

 

Ref (공식문서)

 

Amazon S3에 CodeDeploy의 개정 푸시(EC2 온프레미스 배포 전용) - AWS CodeDeploy

push 명령은 애플리케이션 Artifact와 AppSpec 파일을 개정으로 번들링합니다. 이 개정의 파일 형식은 압축된 ZIP 파일입니다. 이 명령은 각각 JSON 형식 또는 YAML 형식의 AppSpec 파일인 개정을 예상하기

docs.aws.amazon.com

그리고 나서 프로젝트 소스파일에 사소한 편집(주석)을 넣어 master branch에 merge하니

자동으로 Github Actions Workflow가 작동합니다.

배포할 repo > Actions > 생성한workflow

start.sh, stop.sh, appspec.yml, deploy.yml 파일들의 dir과 사소한 파일명 불일치로 삽질을 정말 많이 했습니다..

그래도 workflow 내부 error log를 계속 서칭하고 소스코드 및 파일 위치를 바꾸다보니 해결할 수 있었습니다.

 

그래서 다시 정리하자면


start.sh : 배포할 ec2 내부 프로젝트 파일의 root dir

stop.sh : 배포할 ec2 내부 프로젝트 파일의 root dir

deploy.yml : github레포/.github/workflows/deploy.yml

appspec.yml : github레포 root dir (src파일과 동일한 위치)

 


꼼꼼히 파일들을 확인하면 시간 많이 줄일 수 있습니다..

그리고 성공..!

이제 수정사항 생길 때마다 ssh 접속 -> git pull -> ./gradlew clean build -> cd build/libs -> nohup..~~ 안 해도 됩니다..!

merge 시마다 자동으로 배포되고, 배포가 안 되었다면 github actions workflow에서 error log를 보고 문제를 파악할 수 있습니다!

예시로 이런 경우는 build과정에서 에러가 난 것이므로 소스코드 문제일 확률이 높겠죠!

 


이렇게 CI/CD까지 적용하게 되었습니다.

아직 개념적인 이해가 조금 부족한 것 같아 배포 전반적인 과정을 다시 공부 해봐야 할 것 같습니다.

 

공부하는 과정을 기록한 것이기에 틀린 점이 있을 수 있습니다!

혹시 이거 아닌데.. 싶으시면 말씀해주시면 감사합니다..

 

마치겠습니다.

728x90
반응형

'대외활동 > UMC 4th' 카테고리의 다른 글

[FlagApp] SpringBoot - Server 배포  (0) 2023.08.07
Ch 7 API : 당근마켓 CRUD  (0) 2023.05.18
Ch4 SQL : RDS실습  (0) 2023.05.17
Ch3 데이터베이스 : AWS RDS 구축  (0) 2023.05.17
Ch2 클라우드 구축 : AWS EC2 실습  (0) 2023.05.06