생각정리/항해99

[주특기 프로젝트] 6일차

생각중임 2023. 9. 22. 00:36

CI / CD 적용


Git Hub Action과 S3을 이용해서 CI를 구성하고 S3와 AWS deploy와 EC2를 이용해서 CD를 구성하였다.

Git Hub Action

Workflow 기본 구성

스프링 3.0 이후 버전부터는 자바 최소 버전이 17부터 이기 때문에 자바버전만 수정을 해준다.

name: Java CI with Gradle

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3

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

    - name: Build with Gradle
      uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
      with:
        arguments: build

gradlew 접근 권한이 없다고 오류가 나기 때문에 gradlew의 접근 권한을 추가해준다.

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

이제 접근은 하는데 빌드 자체에서 오류가 다시 난다.

확인해보니 빌드 시 테스트 단계를 거칠 때 빈 테스트 파일이 있을 경우 오류가 나기 때문에 빈 테스트 파일을 삭제 처리하면 빌드에 성공한다.

이제 자동으로 빌드를 해주는 CI단계는 기본적으로 완성된 셈이다.

이제 빌드된 Jar파일을 S3로 넘겨주는 일만 남았다.

더보기

aws-access-key-id와 aws-secret-access-key는 GitHub에서 Settings에서 Security에서 Action에서 New repository secret을 이용해 등록해서 사용할 수 있다.

env:
  S3_BUCKET_NAME: [생성한 S3 버킷이름]
  
# 디렉토리 생성
- name: Make Directory
  run: mkdir -p deploy
# Jar 파일 복사
- name: Copy Jar
  run: cp ./build/libs/*.jar ./deploy
# deploy 폴더 파일 zip 압축
- name: Make zip file
  run: zip -r ./fishingLoad.zip ./deploy
  shell: bash
# IAM 인증
- 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
# S3로 zip파일 보내기
- name: Upload to S3
  run: aws s3 cp --region ap-northeast-2 ./fishingLoad.zip s3://$S3_BUCKET_NAME/

S3 버킷 만들기

더보기

나머지 설정은 안 건들고 버킷 만들기

이제 자동 빌드와 배포를 위한 jar파일을 S3로 보내는 부분이 완성이 됐으니 S3에서 파일을 가져와 자동으로 배포하고 실행하는 부분을 만들기 위해서는 스크립트를 추가해야 한다.

 

appspec.yml (build.gradle 옆에 생성)

version: 0.0 # 버전 표시
os: linux # 적용 os
files: # 배포 파일 설정
  - source: / # 인스턴스에 복사할 디렉터리 경로
    destination: /home/ubuntu/app/ # 인스턴스에서 파일이 복사될 경로
    overwrite: yes # 복사할 위치에 파일이 있는 경우 덮어쓰기 가능 여부

permissions: # 복사한 파일에 대한 권한 설정
  - object: / # 권한이 지정되는 파일 또는 디렉터리
    pattern: "**" # 매칭되는 패턴에만 권한 부여
    owner: ubuntu # object의 소유자
    group: ubuntu # object의 그룹 이름

hooks: # 자동으로 수행할 스크립트 지정
  ApplicationStart: # 자동 실행
    - location: deploy.sh # 시행할 스크립트 이름
      timeout: 60 # 스크립트 실행시 시간 제한 (초과할 시 배포 실패 처리)
      runas: ubuntu # 스크립트 실행자

deploy.sh (root에 만들어도 상관없으나 스크립트가 많아질 수 있기에 scripts 디렉터리를 만들고 넣어주자)

#!/usr/bin/env bash

REPOSITORY=/home/ubuntu/app # 실행 경로 설정

echo "> 현재 구동 중인 애플리케이션 pid 확인"

# 자바로 실행중인 프로세스 PID와 이름 검색 | 해당 이름이 들어간 프로세스 선택 | 해당 프로세스의 PID 선택
CURRENT_PID=$(pgrep -fla java | grep SNAPSHOT | awk '{print $1}')

echo "현재 구동 중인 애플리케이션 pid: $CURRENT_PID"

if [ -z "$CURRENT_PID" ]; then # 실행중인 PID가 없다면
  echo "현재 구동 중인 애플리케이션이 없으므로 종료하지 않습니다."
else # PID가 있으면 PID를 가진 소프트웨어 강제 종료
  echo "> kill -15 $CURRENT_PID"
  kill -15 $CURRENT_PID
  sleep 5
fi

echo "> 새 애플리케이션 배포"

JAR_NAME=$(ls -tr $REPOSITORY/*SNAPSHOT.jar | tail -n 1) # 배포한 Jar파일 이름 설정

echo "> $JAR_NAME 에 실행권한 추가"

chmod +x $JAR_NAME # Jar파일 실행 권한 추가

echo "> $JAR_NAME 실행"
# Seoul시간대로 설정하여 자동 실행
nohup java -jar -Duser.timezone=Asia/Seoul $JAR_NAME >> $REPOSITORY/nohup.out 2>&1 &

이제 해당 스크립트와 함께 Workflow에 deploy를 추가해주면 완성이 된다.

name: Spring Boot & Gradle CI/CD

# 이벤트 트리거
on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

env:
  S3_BUCKET_NAME: elasticbeanstalk-ap-northeast-2-526437230846

jobs:
  build:
    # 실행환경
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    # 자바 버전 설정 스프링 3.0 이후부터 java17이 최소 요구사항
    - name: Set up JDK 17
      uses: actions/setup-java@v3
      with:
        java-version: '17'
        distribution: 'temurin'
    # gradlew 실행 권한 부여
    - name: Grant execute permission for gradlew
      run: chmod +x gradlew
    # application.properties 추가
    - name: Update application.properties
      run: |
        echo $PROPERTIES >> src/main/resources/application.properties
      env:
        PROPERTIES: ${{ secrets.APPLICATION_PROPERTIES_DATA }}
      # gradle build
    - name: Build with Gradle
      uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
      with:
        arguments: clean bootJar
    # 디렉토리 생성
    - name: Make Directory
      run: mkdir -p deploy
    # Jar 파일 복사
    - name: Copy Jar
      run: cp ./build/libs/*.jar ./deploy
    # appspec.yml 파일 복사
    - name: Copy appspec.yml
      run: cp appspec.yml ./deploy
    # script files 복사
    - name: Copy script
      run: cp ./scripts/*.sh ./deploy
    # deploy 폴더 파일 zip 압축
    - name: Make zip file
      run: zip -r ./fishingLoad.zip ./deploy
      shell: bash
    # IAM 인증
    - 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
    # S3로 zip파일 보내기
    - name: Upload to S3
      run: aws s3 cp --region ap-northeast-2 ./fishingLoad.zip s3://$S3_BUCKET_NAME/
    # Deploy 설정
    - name: Deploy
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      run: |
        aws deploy create-deployment \
        --application-name fishingLoad \
        --deployment-group-name fishingLoad-deployment-group \
        --deployment-config-name CodeDeployDefault.AllAtOnce \
        --file-exists-behavior OVERWRITE \
        --s3-location bucket=$S3_BUCKET_NAME,key=fishingLoad.zip,bundleType=zip \
        --region ap-northeast-2
그리고 주의할 점 코드에 이상이 없는데 오류가 발생하는 것처럼 느껴지면 한번 코드 마지막 부분에 공백이 들어가 있는지 확인을 해보자.
3시간 동안 공백을 못 찾아 코드만 계속 수정만 하다가 공백을 제거하고 성공하였다..