본문 바로가기
공부/CI & CD

GiftFunding) GitHub Actions CI/CD 적용하기[2] - CD 적용(2)

by son_i 2024. 6. 29.
728x90

GiftFunding) GitHub Actions CI/CD 적용하기[2] - CD 적용(1) (tistory.com)

 

GiftFunding) GitHub Actions CI/CD 적용하기[2] - CD 적용(1)

지난 포스팅에서 CI 를 적용해서 main 브랜치로 PR 요청 시 빌드(+ 테스트)를 자동으로 실행하도록 하였다.GiftFunding) GitHub Actions CI/CD 적용하기(1) - CI 적용 (tistory.com) 원격 repository" data-og-host="soni-devel

soni-developer.tistory.com

 

이전 AWS 설정에 이어 CD를 위한 WorkFlow.yml, appspec.yml, deploy.sh 파일 작성과정을 정리한다 !

 

6. CD_WorkFlow.yml에 배포 동작 추가

<CD_WorkFlow.yml>

name : Java CD

on:
  push:
    branches: [ "main" ]

env:
  PROJECT_NAME: giftfunding_project #프로젝트 이름 설정. S3 경로 설정에 사용됨.
  BUCKET_NAME: giftfunding-cicd-bucket #앞에서 생성한 S3 버킷 이름을 넣어줌.
  CODE_DEPLOY_APP_NAME: giftfunding-cicd #CodeDeploy '애플리케이션 이름'
  DEPLOYMENT_GROUP_NAME: giftfunding_production #CodeDeploy '배포 그룹 이름'

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

        #프로젝트 파일 압축
      - name: Make Zip File
        run: zip -qq -r ./$GITHUB_SHA.zip
        shell: bash

        #AWS 인증
      - name: Configure AWS credentials
        uses: aws-actions.configure-aws-credential@v2
        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에 압축파일 업로드
      - name: Upload to S3
        run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$BUCKET_NAME/$PROJECT_NAME/$GITHUB_SHA.zip

        #CodeDeploy 배포
      - name: Code Deploy To EC2 instance
        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=$PROJECT_NAME/$GITHUB_SHA.zip

on : 어떤 상황에서 workflow가 동작할 지 정의

jobs: on에 명시한 상황 발생 시 어떤 작업을 처리할 지 정의.

 필수 사항으로 runs-on 속성을 이용해 실행환경을 정의해야 함.

 jobs는 여러 개의 steps로 구성되며 각 steps는 시퀀스 타입을 사용하므로 -로 구분.

 커맨드나 스크립트는 run, 액션은 uses 속성 사용.

 

각 steps 별 동작

- Make Zip File : zip 파일 생성

 

- Configure AWS Credentials : AccessToken 통해 AWS 접근 구너한 얻음.

 

- Upload To S3 : S3 버킷에 zip 파일 업로드

 

- CodeDeploy To EC2 : CodeDeploy에 배포 요청 날림

 가독성을 위해 줄바꿈 했지만 한 줄로 작성하면 됨. 

  배포 관련 명령어는 아래 공식 문서에서 확인

https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/deployments-create-cli.html

 

create-deployment 명령을 호출하고 아래 항목들 설정.

--application-name : 애플리케이션 이름.

--deployment-config-name : 사용할 배포구성의 이름.  

--deployment-group-name : Amazon EC2 배포 그룹 이름.

--S3-location : Amazon S3에 있는 변경사항에 대한 정보 직접 지정. (version과 eTag는 선택 사항)

      bucket : 버킷 이름

      key : 업로드된 개정의 이름

      bundleType : 업로드된 개정의 파일 유형

      

+ main 브랜치로 pull_request 시에도 CD WorkFlow를 동작하도록 추가해주었다. 처음엔 PR 시에 배포를 왜 해야하지 하면서 뺐었는데 그렇다 보니까 merge 하기 전까지 CD WorkFlow 파일의 오류를 잡아주지 못 한다.

 

 

[Trouble Shooting]

1. Error Unable to resolve action aws-actions/configure-aws-credential, repository not found

-> 단순 오타

 

2. Error: Process completed with exit code 12.

-> run: zip -qq -r ./$GITHUB_SHA.zip 코드 맨 뒤에 .을 안 찍어서 발생. '.'이 무슨 의미냐면

 

7. Appspec.yml 생성

배포할 프로젝트 최상단 경로에 appspec.yml 파일 생성.

appspec 파일은 배포 시, codedeploy가 참조하는 파일.

version: 0.0 # 버전 고정
os: linux

# 서버의 destination 경로에 source 파일을 배포하겠다는 얘기. 현재는 서버의 ~/build 위치에 디렉토리 전체룰 올리겠다는 말
files:
  - source: /
    destination: /home/ec2-user/build

permissions:
  - object: /home/ec2-user/build/
    owner: root
    group: root

hooks: # CodeDeploy의 프로세스 진행 도중, hook을 사용하여 특정 이벤트에 수행 될 작업 지정
  AfterInstall: # AfterInstall은 CodeDeplooy의 프로젝트 배포가 완ㄹ됴ㅚㄴ 시점의 이ㅔㄴ트
    - location: deploy.sh #프로젝트 배포가 완료되면 location으로 지정된 파일 실행
    - timeout: 60 #timeout 초단위
      runas: root

- version : appspec.yml 파일 버전을 정의. 

 

- files : 배포할 파일 및 디렉토리 정의. 

    source 위치의 파일 및 디렉토리를 destination 서버 경로에 복사 

    (source는 appspec.yml 기준 상대경로, destination은 절대 경로.)

 

   => 애플리케이션을 루트 디렉토리에서 가져와 /home/ec2-user/build 디렉토리로 복사.

 

 

 - permissions : 애플리케이션 파일에 대한 권한 정의 

      object: 권한 지정될 디렉토리

 

  => /home/ec2-user/build 디렉토리의 그룹, 소유자를 root로 설정.

 

- hooks: 배포 단계에서 실행할 훅 스크립트 정의

  AfterInstall: CodeDeploy의 프로젝트 배포가 완료된 시점의 이벤트.

    - location : 배포 완료시 loaqation으로 지정된 파일 실행.

       timeout: 스크립트 실행 시간 제한 (sec)

       runas: 명령을 실행할 사용자 지정.

 

8. 배포 스크립트(deploy.sh) 생성

S3로부터 빌드 파일을 다운로드 받은 이후 배포 스크립트가없으면 아무 일도 일어나지 않음. 

 

#!/usr/bin/env bash

BUILD_PATH=$(ls /home/ec2-user/build/*.jar)
JAR_NAME=$(basename $BUILD_PATH)
echo "> build 파일명: $JAR_NAME"

echo "> build 파일 복사"
DEPLOY_PATH=/home/ec2-user/
cp $BUILD_PATH $DEPLOY_PATH

echo "> springboot-deploy.jar 교체"
CP_JAR_PATH=$DEPLOY_PATH$JAR_NAME
APPLICATION_JAR_NAME=springboot-deploy.jar
APPLICATION_JAR=$DEPLOY_PATH$APPLICATION_JAR_NAME

ln -Tfs $CP_JAR_PATH $APPLICATION_JAR

echo "> 현재 실행 중인 애플리케이션 pid 확인"
CURRENT_PID=$(pgrep -f $APPLICATION_JAR_NAME)

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

echo "> $APPLICATION_JAR_NAME 배포"
nohup java -jar $APPLICATION_JAR > /dev/null 2> /dev/null < /dev/null &

 

#!/usr/bin/env bash

  #! SheBang 은 스크립트 파일에서 어떤 프로그램으로 해당 파일을 실행시킬지 결정

  문법 : #! <Interpreter> [optional-arg]
                 여기서 Interpreter에는 절대 경로가 와야한다.

 

* Interpreter : 프로그래밍 언어의 소스코드를 바로 실행하는 컴퓨터 프로그래밍/환경

 

/usr/bin/env 설정 시 절대 경로에 상관없이 인터프리터 위치를 찾아서 실행

     -> 여러 시스템 환경에서 사용할 때 유용.


빌드 파일명 찾기

BUILD_PATH=$(ls /home/ec2-user/build/*.jar)

JAR_NAME=$(basename $BUILD_PATH)

echo "> build 파일 명 $JAR_NAME"

 

$(...) 는 괄호 안 명령 실행 후 결과 대입
basename : 경로를 제외한 파일 명을 남긴다. (옵션 추가시 확장자도 제거 가능)

 

BUILD_PATH에는 만들어진 JAR 파일 경로가 대입.

JAR_NAME에는 ~.jar 이 대입.

 


빌드 결과물(jar 파일)을 특정위치로 복사 

echo "> build 파일 복사"

DEPLOY_PATH=/home/ec2-user/

cp $BUILD_PATH $DEPLOY_PATH

 

cp A B : A를 B로 복사

JAR 파일이 DEPLOY_PATH로 복사 됨.

 


DEPLOY_PATH로 복사된 ~~.jar 파일을 springboot-deploy.jar 이름으로 링크 걸어줌.

echo "> springboot-deploy.jar 교체"
CP_JAR_PATH=$DEPLOY_PATH$JAR_NAME
APPLICATION_JAR_NAME=springboot-deploy.jar
APPLICATION_JAR=$DEPLOY_PATH$APPLICATION_JAR_NAME


ln -Tfs $CP_JAR_PATH $APPLICATION_JAR

 

CP_JAR_PATH에는 배포위치로 복사된 JAR 파일의 경로가 들어가게 됨. (/home/ec2-user/~~.jar)

APPLICATION_JAR_NAME에는 지정해 줄 이름 'springboot-deploy.jar'가 들어감.

APPLICATION_JAR에는 배포 위치와 springboot-deploy.jar가 들어감. (/home/ec2-user/springboot-deploy.jar)

 

ln : link 파일 만들 때 사용
문법 : ln -s [링크 걸 파일] [링크 이름]
옵션
     -T : 일반 파일 같은 링크 생성
      f  : 존재하는 링크 제거하고 새로 생성
      s : 링크 생성

 

ln -Tfs $CP_JAR_PATH $APPLICATION_JAR_NAME

기존 빌드에서 배포경로($DEPLOY_PATH)로 복사해온 ~~~.jar 파일의 경로가 지금 CP_JAR_PATH임.

그 파일을 APPLICATION_JAR_NAME인 springboot-deploy.jar 이름으로 링크를 걸어주는 과정 !

 


현재 애플리케이션의 PID 확인 후 실행 중이면 종료
echo "> 현재 실행 중인 애플리케이션 pid 확인"
CURRENT_PID=$(pgrep -f $APPLICATION_JAR_NAME)

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

 

CURRENT_PID=$(pgrep -f $APPLICATION_JAR_NAME)

pgrep : ps와 grep 명령어를 합쳐서 원하는 정보를 편리하게 출력.
-> 특정 조건에 부합하는 PID를 찾음.
옵션
   -f : 프로세스 이름으로 찾기. 파일이 존재하고 일반 파일인 경우(디렉토리나 장치 파일이 아닌 경우)

CURRENT_PID에는 springboot-deploy.jar의 pid가 들어가게 됨.

 

if [ -z $CURRENT_PID ]

-z 옵션 : 문자열이 Null, 길이가 0인 경우

 

kill -15 $CURRENT_PID

-15 : 정상 종료
-9 : 강제 종료

 


Application 배포

echo "> $APPLICATION_JAR_NAME 배포"
nohup java -jar $APPLICATION_JAR > /dev/null 2> /dev/null < /dev/null &

 

nohup [쉘에서 수행하고자 하는 명령어] : ssh 접속이 끊어져도(터미널을 종료해도) 실행 중인 프로그램 종료 안 함.

& : 백그라운드 실행 명령어와 함께 쓰임. 프로세스를 백그라운드에서 동작하도록 하는 명령어

java -jar : 일반적인 자바 실행 명령어

/dev/null : 출력을 표시하지 않기 위함

> : 왼쪽 명령을 오른쪽 명령으로 리디렉션

 

nohup java -jar $APPLICATION_JAR > /dev/null 2> /dev/null < /dev/null &

결국 위 명령은 jar를 실행 시키고 실행에 따른 출력 값(오류, 정상출력모두)을 보지 않겠다는 의미.

 

 

PR에서 오류가 없이 모두 통과했다.

 

 

 

정상적으로 서버가 올라갔는지 확인하는 부분에서 정말 많은 오류와 해결 과정을 겪었기에 다음글로 작성하려고 한다.

일단 아직까지 위 과정으로 하면 배포 파일이 정상적으로 올라가있지 않는다 !