Springboot 프로젝트 Github Action을 이용해서 배포 자동화하기

vector13·2022년 9월 18일
9
post-thumbnail

###🔥 주의 🔥
해당 글은 에러와 실패 과정을 모두 포함한 (노력의 시간순서) 글이므로 만일 따라하실 때는 다 읽으시고 맞는 방법만 골라서 진행하시면됩니다. 正道만 걸으시라는 뜻.....
중간에 권한이 없어서 권한을 부여받을 때까지 며칠동안 작성한 글이기 때문에
두서가 없고, 시간 순으로 실시간 작성한 글이라서 정리가 안된 느낌이지만 그래서 오히려 누군가에겐 도움이 되지 않을까 싶어서 글을 포스팅 해본다.

참고 블로그

(처음에 따라할 때 )
https://stalker5217.netlify.app/devops/github-action-aws-ci-cd-1/
(이 후 따라한 블로그)
https://bcp0109.tistory.com/363

0. 배포 과정 도식화

1. github action을 사용한 CI 구축

1.1 Github Action을 사용한 Spring boot & gradle CI 구축

  • Workflow 작성 (Java with Gradle)
    • repository의 github action 탭에 들어가면 다음과 같이 repository에 있는 language를 감지하여 화면이 나타남 개꿀

    • 아래 코드 추가했고, 맨마지막에 gradle build 하는 줄을 블로그처럼 run : ./gradlew clean build 로 변경

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

chaebbiSpring/.github/workflows/gradle.yml

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle

name: Spring Boot & Gradle CI/CD 

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: Grant execute permission for gradlew
      run: chmod +x gradlew
    - name: Build with Gradle
      # uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
      # with:
        # arguments: build
      run : ./gradlew clean build

github action 탭에서 다음과 같이 build 결과를 확인

Build with Gradle 단계에서 에러

예상 에러원인

테스트 관련 빌드 에러로 예상하고 테스트 스킵하고 빌드하는 걸로 코드 변경

run : ./gradlew clean build --exclude-task test

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle

name: Spring Boot & Gradle CI/CD 

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: Grant execute permission for gradlew
      run: chmod +x gradlew
    - name: Build with Gradle
      # uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
      # with:
        # arguments: build
      run : ./gradlew clean build --exclude-task test
  • 빌드 성공

2. AWS S3 연동

s3 bucket을 생성하고

  • IAM 설정

    ‘AmazonS3FullAccess’, ‘AWSCodeDepolyFullAccess’ 활성화

사용자 추가 완료 ACCESSKEY와 SECRETKEY 를 꼭 잊지말고 적어둘 것
퍼블릭 엑세스 열고, 버킷 권한 정책 편집

{
    "Version": "2012-10-17",
    "Id": "Policy1657713262596",
    "Statement": [
        {
            "Sid": "Stmt1657713261442",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject",
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::chaebbi-deploy-bucket/*"
        }
    ]
}

3 github secret 설정 -> 5번에서 다시 update 할 예정

이제 github repository에서 해당 키 값들을 세팅한다. action에 키 값을 그대로 노출하는 것은 보안상 문제가 되므로 repository의 ‘Settings -> Secret’에서 해당 키 값을 등록

$GITHUB_SHA : Github 자체에서 커밋마다 생성하는 랜덤한 변수 값

3.1 생성된 gradle.yml 파일

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle

name: Spring Boot & Gradle CI/CD 

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: Grant execute permission for gradlew
     run: chmod +x gradlew
   - name: Build with Gradle
     # uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
     # with:
       # arguments: build
     run : ./gradlew clean build --exclude-task test
     
   # 전송할 파일을 담을 디렉토리 생성
   - name: Make Directory for deliver
     run: mkdir deploy
     
   # Jar 파일 Copy
   - name: Copy Jar
     run: cp ./build/libs/*.jar ./deploy/

   # 압축파일 형태로 전달
   - name: Make zip file
     run: zip -r -qq -j ./springboot-intro-build.zip ./deploy

    # S3 Bucket으로 copy
   - name: Deliver to AWS S3
     env:
       AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
       AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
     run: |
       aws s3 cp \
       --region ap-northeast-2 \
       --acl private \ 
./springboot-intro-build.zip s3://springboot-intro-build/

3.2 appspec.yml 파일 생성

version: 0.0
os: linux

files:
  - source:  /
    destination: /home/ubuntu/app
    overwrite: yes

permissions:
  - object: /
    pattern: "**"
    owner: ubuntu
    group: ubuntu

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

codedeply 가 없으므로 에러 발생

이제 두번째 블로그 https://bcp0109.tistory.com/363 블로그 위부터 다시 따라함

3.3 EC2 인스턴스에서 IAM 연결

chaebbi-deploy-iam 에다가 s3 fullAccess 주고 ec2에 보안>iam 역할수정 으로 추가했음

3.4 CodeDeploy Agent 설치

배포 자동화용으로 만든 ec2 (이름 : jenkins)(ubuntu)에 apt install codedeploy-agent
https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/codedeploy-agent-operations-install-ubuntu.html

정상 설치 완료, 응답 완료

4. CodeDeploy 생성

4.1 IAM 역할 생성

CodeDeploy 를 사용하기 위해 IAM 에서 역할 생성

4.2 CodeDeploy 애플리케이션 생성

내가 가진 iam aws 계정에서 codeploy:ListApplications 권한이 없어서 이를 요청하고 다시 시도함

권한 부여 받고 다시 애플리케이션 생성

4.3. CodeDeploy 배포 그룹 생성

이번에는 SNS:ListTopics 권한이 없다는 에러 → 다시 요청해 권한 부여받음

배포 그룹 이름 : chaebbi-codedeploy-deployment-group

어떤 인스턴스에서 동작할지 선택 :EC2 인스턴스에는 태그 추가해서 선택하는데 이 프로젝트 배포자동화를 위해서 사용할 ec2인 jenkins-ec2로 이름 붙여놓은 ec2 사용 (일전에 만들어둠 )

그 다음 설정을 해준다. 현재는 로드 밸런싱 사용하지 않으므로 활성화를 풀어주고 그룹생성을 진행한다.

배포 그룹 생성 성공!

5. Github actions에서 사용할 IAM 사용자 추가

aws를 github actions 워크 플로우에 접근 위해서 권한을 추가해준다.

이전처럼 iam ‘역할’추가가 아닌 ‘사용자’를 추가한다.

IAM 메뉴에서 사용자 추가를 클릭한다.

사용자 이름 : chaebbi-github-actions-iam-user

권한 설정에서 → 기존 정책 직접 연결 클릭 후

  • AWSCodeDeployFullAccess
  • AmazonS3FullAccess

두개를 추가해준다.

검토 화면 은 다음과 같다.


사용자 만들기 버튼 클릭해서 사용자 생성 완료 하였다.

만들고 나면 생성되는 엑세스키 id 와 비밀 엑세스 키 는 다운로드할 수 있는 마지막 기회이므로 csv파일 꼭 저장하기!!!

Github Repository 의 Secrets 추가

github action 으로 가서 Github > Repository > Settings > Secrets  로 이동

이번에 만든 chaebbi-github-actions-iam-user 의 access key 와 secret key로 변경 해줌

6. AppSpec 파일 작성

이제 서버 띄울 EC2 : jenkins-ec2

배포 결과물 저장할 S3 : chaebbi-deploy-bucket

배포할 CodeDeploy : chaebbi-codedeploy-app

AWS 서비스들을 만들었다.

AppSpec 파일을 사용해서 프로젝트의 어떤 파일들을 EC2 의 어떤 경로에 복사할지 설정 가능하고, 배포 프로세스 이후에 수행할 스크립트를 지정하여 자동으로 서버를 띄울 수도 있음.

version: 0.0
os: linux

files:
  - source:  /
    destination: /home/ubuntu/app
    overwrite: yes

permissions:
  - object: /
    pattern: "**"
    owner: ubuntu
    group: ubuntu

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

위의 3.2 appspec.yml 파일 생성 부분과 비교하면 차이가 없다.

그래서 이 부분은 이전에 생성해둔 대로 그대로 둠

7. 배포 스크립트 작성

바로 위 AppSpec Hooks 에서 실행할 스크립트 stop.sh 와 start.sh 를 설정

7.1 stop.sh

실행중인 애플리케이션이 있다면 종료하는 스크립트

블로그 스크립트에서 jar_file 이름과 kill 15에서 9로 변경 하였음

#!/usr/bin/env bash

PROJECT_ROOT="/home/ubuntu/app"
JAR_FILE="$PROJECT_ROOT/chaebbiSpring.jar"

DEPLOY_LOG="$PROJECT_ROOT/deploy.log"

TIME_NOW=$(date +%c)

# 현재 구동 중인 애플리케이션 pid 확인
CURRENT_PID=$(pgrep -f $JAR_FILE)

# 프로세스가 켜져 있으면 종료
if [ -z $CURRENT_PID ]; then
  echo "$TIME_NOW > 현재 실행중인 애플리케이션이 없습니다" >> $DEPLOY_LOG
else
  echo "$TIME_NOW > 실행중인 $CURRENT_PID 애플리케이션 종료 " >> $DEPLOY_LOG
  kill -9 $CURRENT_PID
fi

7.2 start.sh

애플리케이션 실행하는 스크립트

Github Actions 워크플로우에서 이미 빌드는 마쳤기 때문에 JAR 파일만 복사 후 실행

#!/usr/bin/env bash

PROJECT_ROOT="/home/ubuntu/app"
JAR_FILE="$PROJECT_ROOT/chaebbiSpring.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

7.3 (내가) 프로젝트에서 사용하는 수동 배포 스크립트와의 비교

원래 수동으로 배포하던 배포 스크립트 deploy.sh 와 비교해서 차이를 확인해봤다.

#!/bin/bash

REPOSITORY=/var/www/html/spring
PROJECT_NAME=chaebbiSpring

cd $REPOSITORY/$PROJECT_NAME/

echo "> GIT PULL"

git pull

echo "> 프로젝트 빌드 시즈악"

./gradlew build -x check --parallel

echo ">step1 디렉 이도옹"

cd $REPOSITORY

echo "> Build 파일 복사"

cp $REPOSITORY/$PROJECT_NAME/build/libs/*.jar $REPOSITORY/

echo "> 현재 구동중인 애플리케이션 pid 확인"
CURRENT_PID=$(pgrep -f ${PROJECT_NAME}-0.0.1-SNAPSHOT.jar)

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

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

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

#JAR_NAME=$(ls -tr $REPOSITORY/ | grep *.jar | tail -n 1)
JAR_NAME=$(ls -tr $REPOSITORY/*.jar | tail -n 1)

echo "> JAR Name: $JAR_NAME"

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

chmod +x $JAR_NAME

echo "> $JAR_NAME 실행"

nohup java -jar \
    -Dspring.config.location=/var/www/html/spring/application-real-db.properties,/var/www/html/spring/application.yml \
    -Dspring.profiles.active=real \
    $JAR_NAME 2>&1 &

7.3 build.gradle 파일 수정 -> 안했음

블로그에서는

Spring Boot 2.5 버전부터는 빌드 시 일반 jar 파일 하나와 -plain.jar 파일 하나가 함께 만들어 진다는데, plain jar 파일이 만들어지지 않도록 내용 추가를 하고있지만 왜 해야하는지 몰라서 일단은 수정안함

8. Github Actions Workflow 작성

블로그에서는 github repository에서 action > new workflow 선택 해서 simple workflow 생성하고있지만

우리는 기존의 workflow가 있기 때문에 1.1 의 Spring Boot & Gradle CI/CD WorkFlow 사용

그동안 codedeploy권한이 없어 진행을 못해 빨갛게 에러가 났던 빌드의 현장들 ..

3.1.1 생성된 gradle.yml 파일 과 블로그의 파일을 비교

아래는 블로그의 deploy.yml

name: Deploy to Amazon EC2

on:
  push:
    branches:
      - main

# 본인이 설정한 값을 여기서 채워넣습니다.
# 리전, 버킷 이름, CodeDeploy 앱 이름, CodeDeploy 배포 그룹 이름
env:
  AWS_REGION: ap-northeast-2
  S3_BUCKET_NAME: my-github-actions-s3-bucket
  CODE_DEPLOY_APPLICATION_NAME: my-codedeploy-app
  CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: my-codedeploy-deployment-group

permissions:
  contents: read

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    environment: production

    steps:
    # (1) 기본 체크아웃
    - name: Checkout
      uses: actions/checkout@v3

    # (2) JDK 11 세팅
    - name: Set up JDK 11
      uses: actions/setup-java@v3
      with:
        distribution: 'temurin'
        java-version: '11'

    # (3) Gradle build (Test 제외)
    - name: Build with Gradle
      uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee
      with:
        arguments: clean build -x test

    # (4) AWS 인증 (IAM 사용자 Access Key, Secret Key 활용)
    - 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: ${{ env.AWS_REGION }}

    # (5) 빌드 결과물을 S3 버킷에 업로드
    - name: Upload to AWS S3
      run: |
        aws deploy push \
          --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
          --ignore-hidden-files \
          --s3-location s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip \
          --source .

    # (6) S3 버킷에 있는 파일을 대상으로 CodeDeploy 실행
    - name: Deploy to AWS EC2 from S3
      run: |
        aws deploy create-deployment \
          --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
          --deployment-config-name CodeDeployDefault.AllAtOnce \
          --deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
          --s3-location bucket=$S3_BUCKET_NAME,key=$GITHUB_SHA.zip,bundleType=zip

차이점

블로그는 jobs를 deploy로 시작. 우리 yml은 build 로시작

일단은 차이 그대로 두고 진행해보겠음

진행결과

gradle.yml을 수정해서 action 탭에 들어가보니 실패가 뜬다. 이유는
every step must define a uses or run key 라고 뜬다.

구글링해보니 name 다음에는 해당 하나의 uses만 있어야 한다고 한다.

이부분에 있던 - 를 제거해주었다.

두근 두근 빌드를 기다리고있다

어머나 성공적으로 빌드 배포 되어버렸다

이름은 헷갈리지 않게 Deploy 로 바꿔주는게 좋겠다.

블로그처럼 Deploy로 바꿔도 과연 잘될까?

(jobs 밑에 build: 를 deploy: 로 변경. name에 Deploy 준다)

감격스럽다.

잘 된다

profile
HelloWorld! 같은 실수를 반복하지 말기위해 적어두자..

5개의 댓글

comment-user-thumbnail
2023년 1월 28일

선생님 감사합니다 해결해써요 !

답글 달기
comment-user-thumbnail
2023년 4월 14일

안녕하세요!
블로그 잘 보고 있습니다.
맨 상단에 있는 도식은 어떤 프로그램 혹은 앱을 이용해서 그리신건지 알수있을까요?
너무 이뻐서 그렇습니다.

1개의 답글
comment-user-thumbnail
2023년 6월 5일

혹시 이렇게 배포할 시 ec2의 cpu가 급격하게 올라가진 않으셨었나요??

답글 달기