본문 바로가기
카테고리 없음

spring boot + nginx + github action runner

by 용용이아바이 2024. 2. 1.
728x90
workflow 작성

1. github에서 Actions를 누르고 Java with Gradle을 선택한다. 

 

2. .github > workflows > gradle.yml 을 수정한다.

name: Java CI with Gradle
# 사용할 브랜치 master에 push되거나 pull_request가 있을 시에 실행됨
on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ "master" ] 

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
      run: ./gradlew clean build

 

 

3. Commit을 한다.

 

4. 실수로 브랜치명을 잘못 쳐서 업데이트 했지만 파일을 만들고 다시 Actions를 누르면 workflow가 실행된다.

 

5. Settings > Runners > New self-hosted runner를 클릭한다.


 

github action runner 설치

1. 리눅스를 선택하고 사용하는 서버에서 차례대로 입력하고 실행한다.

-bash: shasum: command not found를 보게 된다면 sudo yum install perl-Digest-SHA -y 으로 설치를 한다.

 

설치가 끝난 뒤 다시 이어서 실행한다.runner 이름이나 라벨은 짓고 싶은대로 지으면 된다. 나중에 gradle.yml에서 사용할 이름이다.

마지막으로  ./run.sh & 를 입력한다.

2. runner 설치 확인


shell script 작성

 

shell script는 /home/ec2-user에 작성한다.

1. config.ini

## prod(production), dev(development) ## $PROFILE, ${PROFILE}1, ${PROFILE}2
PROFILE = dev
DOMAIN_URL = {url 주소} ## http://localhost
PORT1 = 8083
PORT2 = 8084
REPOSITORY = /home/ec2-user
PROJECT_NAME = {프로젝트 이름}
JAR_FILE_ASTERISK_NAME = {프로젝트 이름}-0.0.1*.jar
NGINX_SERVICE_URL = /usr/local/nginx-1.20.1/conf.d/service-{프로젝트 이름}-url.inc

 

2. profile.sh

#!/usr/bin/env bash
# bash는 return value가 안되니 *제일 마지막줄에 echo로 해서 결과 출력*후, 클라이언트에서 값을 사용한다
# 놀고 있는 profile 찾기: prod1이 사용중이면 prod2가 쉬고 있고, 반대면 prod1이 쉬고 있음
CURRENT_PATH=$(pwd)
CONFIG_FILE=${CURRENT_PATH}/config.ini
PROFILE=$(awk '/^PROFILE/{print $3}' ${CONFIG_FILE})
PORT1=$(awk '/^PORT1/{print $3}' ${CONFIG_FILE})
PORT2=$(awk '/^PORT2/{print $3}' ${CONFIG_FILE})
DOMAIN_URL=$(awk '/^DOMAIN_URL/{print $3}' ${CONFIG_FILE})
function find_idle_profile()
{
    RESPONSE_CODE=$(curl -Ls -o /dev/null -w "%{http_code}" ${DOMAIN_URL}/profile)
    # 404, 500 등 에러 확인
    if [ ${RESPONSE_CODE} -ge 400 ] # 400 보다 크면 (즉, 40x/50x 에러 모두 포함)
    then
        CURRENT_PROFILE=${PROFILE}2 ##dev2
    else
        CURRENT_PROFILE=$(curl -Ls ${DOMAIN_URL}/profile)
    fi    
    # 현재 프로파일 확인
    if [ ${CURRENT_PROFILE} == ${PROFILE}1 ]   ##dev1
    then
         IDLE_PROFILE=${PROFILE}2 ##dev2
    else
        IDLE_PROFILE=${PROFILE}1 ##dev1
    fi
    # echo 로 결과 출력
    echo "${CURRENT_PROFILE}"
}
# 놀고 있는 profile의 port 찾기
function find_idle_port()
{
    IDLE_PROFILE=$(find_idle_profile)
    # 놀고 있는 프로파일 확인
    if [ ${IDLE_PROFILE} == ${PROFILE}1 ]  ##dev1
    then
        echo ${PORT2}
    else
        echo ${PORT1}
    fi
}

 

3.  rollback.sh

#!/usr/bin/env bash
## 3rd_health_new_app.sh 와 동일하다. 이전 스프링 애플리케이션 실행으로 돌아간다.
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh
source ${ABSDIR}/switch.sh
# 놀고 있는 PORT
IDLE_PORT=$(find_idle_port)
# profile 이 prod 인지 dev 인지 확인
CONFIG_FILE=${CURRENT_PATH}/config.ini
PROFILE=$(awk '/^PROFILE/{print $3}' ${CONFIG_FILE})
# 정보 출력
echo "> Health Check Start!"
echo "> IDLE_PORT: $IDLE_PORT"
echo "> curl -s http://localhost:$IDLE_PORT/profile "
sleep 10
# 반복구문
for RETRY_COUNT in {1..10}
do
    RESPONSE=$(curl -s http://localhost:${IDLE_PORT}/profile)
    UP_COUNT=$(echo ${RESPONSE} | grep ${PROFILE} | wc -l) # profile (설정값)은 prod 또는 dev
    # profile 문자열 검증
    if [ ${UP_COUNT} -ge 1 ]
    then # $up_count >= 1 (profile 문자열이 있는지 검증)
        echo "> Health check 성공"
        switch_proxy
        break
    else
        echo "> Health check의 응답을 알 수 없거나 혹은 실행 상태가 아닙니다."
        echo "> Health check: ${RESPONSE}"
    fi
    # Retry 가 10 번이면 실패.
    if [ ${RETRY_COUNT} -eq 10 ]
    then
        echo "> Health check 실패. "
        echo "> 엔진엑스에 연결하지 않고 배포를 종료합니다."
        exit 1
    fi
    # 재시도
    echo "> Health check 연결 실패. 재시도..."
    sleep 10
done

 

4.  switch.sh

#!/usr/bin/env bash
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
CONFIG_FILE=${CURRENT_PATH}/config.ini
NGINX_SERVICE_URL=$(awk '/^NGINX_SERVICE_URL/{print $3}' ${CONFIG_FILE})
source ${ABSDIR}/profile.sh
# 함수
function switch_proxy() {
    IDLE_PORT=$(find_idle_port)
    # 에코
    echo "> 전환할 Port: $IDLE_PORT"
    echo "> Port 전환"
    echo "set \$service_url http://127.0.0.1:${IDLE_PORT};" | sudo tee ${NGINX_SERVICE_URL}
    # 엔진엑스 리로드
    echo "> 엔진엑스 Reload"
    sudo /usr/local/nginx-1.20.1/sbin/nginx -s reload
}

 

5.  sh_run_web_application.sh

#!/usr/bin/env bash
CURRENT_PATH=$(pwd)
CONFIG_FILE=${CURRENT_PATH}/config.ini
PROFILE=$(awk '/^PROFILE/{print $3}' ${CONFIG_FILE})
# 레파지토리와 프로젝트 이름
REPOSITORY=$(awk '/^REPOSITORY/{print $3}' ${CONFIG_FILE})
PROJECT_NAME=$(awk '/^PROJECT_NAME/{print $3}' ${CONFIG_FILE})
NGINX_SERVICE_URL=$(awk '/^NGINX_SERVICE_URL/{print $3}' ${CONFIG_FILE})
PORT1=$(awk '/^PORT1/{print $3}' ${CONFIG_FILE})
PORT2=$(awk '/^PORT2/{print $3}' ${CONFIG_FILE})
# 가장 최근에 빌드한 jar 파일
JAR_NAME=$(ls -tr $REPOSITORY/*.jar | tail -n 1)
# 가장 최근에 사용했던 프로파일을 구하기 위한 계산
LINE=$(grep -n $PORT1 $NGINX_SERVICE_URL | cut -d: -f1 | head -1)
NUM1=1
ACTIVE_PROFILE=${PROFILE}1 ## dev1
if [ x$LINE != x ] && [ $LINE -eq $NUM1 ]
then
    echo "port=${PORT1}"
else
    echo "port=${PORT2}"
    ACTIVE_PROFILE=${PROFILE}2  ## dev2
fi
# jar 파일을 nohup jar 로 실행
sudo bash -c "nohup java -jar $JAR_NAME \
    > ${REPOSITORY}/nohup.out 2>&1 \
    --spring.profiles.active=$ACTIVE_PROFILE \
    &"

 

6. 1st_stop_non_nginx_app.sh

#!/usr/bin/env bash
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh
# 놀고 있는 PORT
IDLE_PORT=$(find_idle_port)
# 놀고 있는 PID
echo "놀고 있음(idle) > $IDLE_PORT 에서 구동중인 애플리케이션 pid 확인"
IDLE_PID=$(sudo lsof -ti tcp:${IDLE_PORT})
# 놀고 있는 PID 종료
if [ -z ${IDLE_PID} ]
then
	echo "> 현재 놀고 있고(idle) 구동 중인 애플리케이션이 없으므로 종료하지 않습니다."
else
    echo "> kill -15 $IDLE_PID"
    sudo kill -15 ${IDLE_PID}
    sleep 5
fi

 

7. 2nd_start_new_app.sh

#!/usr/bin/env bash
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh
# 레파지토리와 프로젝트 이름
CONFIG_FILE=${ABSDIR}/config.ini
REPOSITORY=$(awk '/^REPOSITORY/{print $3}' ${CONFIG_FILE})
PROJECT_NAME=$(awk '/^PROJECT_NAME/{print $3}' ${CONFIG_FILE})
JAR_FILE_ASTERISK_NAME=$(awk '/^JAR_FILE_ASTERISK_NAME/{print $3}' ${CONFIG_FILE})
# GIT PULL
cd $REPOSITORY/$PROJECT_NAME/
echo "> Git Pull"
git stash && git pull origin master && git stash pop
echo "> 기존 Build 경로의 jar 파일 삭제"
rm -rf $REPOSITORY/$PROJECT_NAME/build/libs/*.jar
echo "> 프로젝트 Build 시작"
./gradlew build
echo ">springboot_app 디렉토리로 이동"
cd $REPOSITORY
echo "> Build jar 파일 복사"
cp $REPOSITORY/$PROJECT_NAME/build/libs/*.jar $REPOSITORY/
echo "> 새 어플리케이션 배포"
JAR_NAME=$(ls -tr $REPOSITORY/*.jar | tail -n 1)
echo "> JAR Name: $JAR_NAME"
echo "> $JAR_NAME 에 실행권한 추가"
chmod +x $JAR_NAME
echo "build 후 최근 파일 15개 제외하고 실행 jar 파일 삭제"
ls -td1 ${JAR_FILE_ASTERISK_NAME} | tail -n +16
ls -td1 ${JAR_FILE_ASTERISK_NAME} | tail -n +16 | xargs rm -f
echo "> $JAR_NAME 실행"
IDLE_PROFILE=$(find_idle_profile)
# nohup 으로 JAR 실행
echo "> $JAR_NAME 를 profile=$IDLE_PROFILE 로 실행합니다."
sudo bash -c "nohup java -jar $JAR_NAME \
    > ${REPOSITORY}/nohup.out 2>&1 \
    --spring.profiles.active=$IDLE_PROFILE \
    &"

 

8. 3rd_health_new_app.sh

#!/usr/bin/env bash
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh
source ${ABSDIR}/switch.sh
# 놀고 있는 PORT
IDLE_PORT=$(find_idle_port)
# profile 이 prod 인지 dev 인지 확인
CONFIG_FILE=${CURRENT_PATH}/config.ini
PROFILE=$(awk '/^PROFILE/{print $3}' ${CONFIG_FILE})
# 정보 출력
echo "> Health Check Start!"
echo "> IDLE_PORT: $IDLE_PORT"
echo "> curl -s http://localhost:$IDLE_PORT/profile "
sleep 10
# 반복구문
for RETRY_COUNT in {1..10}
do
    RESPONSE=$(curl -s http://localhost:${IDLE_PORT}/profile)
    UP_COUNT=$(echo ${RESPONSE} | grep ${PROFILE} | wc -l)  ## ${PROFILE} 은 'prod' 또는 'dev'
    # PROFILE 문자열 검증
    if [ ${UP_COUNT} -ge 1 ]
    then # $up_count >= 1 (PROFILE 문자열이 있는지 검증)
        echo "> Health check 성공"
        switch_proxy
        break
    else
        echo "> Health check의 응답을 알 수 없거나 혹은 실행 상태가 아닙니다."
        echo "> Health check: ${RESPONSE}"
    fi
    # Retry 가 10 번이면 실패.
    if [ ${RETRY_COUNT} -eq 10 ]
    then
        echo "> Health check 실패. "
        echo "> 엔진엑스에 연결하지 않고 배포를 종료합니다."
        exit 1
    fi
    # 재시도
    echo "> Health check 연결 실패. 재시도..."
    sleep 10
done

 

9.  4th_kill_inactive_pid.sh

#!/usr/bin/env bash
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)

CURRENT_PATH=$(pwd)
CONFIG_FILE=${CURRENT_PATH}/config.ini

PORT1=$(awk '/^PORT1/{print $3}' ${CONFIG_FILE})
PORT2=$(awk '/^PORT2/{print $3}' ${CONFIG_FILE})
PROFILE1=$(awk '/^PROFILE1/{print $3}' ${CONFIG_FILE})

source ${ABSDIR}/profile.sh

IDLE_PORT=$(find_idle_port)
# echo "${IDLE_PORT}"

# 활동하지 않는 pid
echo "활동하지 않는 $IDLE_PORT 의 애플리케이션 pid 확인"
IDLE_PID=$(lsof -ti tcp:${IDLE_PORT})

# 활동하지 않는 pid 종료
if [ -z ${IDLE_PID} ]
then
    echo "> 활동하지 않는  애플리케이션이 없으므로 종료하지 않습니다."
else
    echo "> kill -15 $IDLE_PID"
    kill -15 ${IDLE_PID}
    sleep 5
fi

 

10. deploy.sh

#!/usr/bin/env bash
echo "1. Stop non nginx app (idle)"
./1st_stop_non_nginx_app.sh
sleep 2
echo "2. Start new app (idle)"
./2nd_start_new_app.sh
echo "Sleep 1 min"
sleep 60
echo "3. health new app (switch active <-> idle)"
./3rd_health_new_app.sh
sleep 10
echo "4. kill inactive pid"
./4th_kill_inactive_pid.sh

 

workflow 수정

 

name: Java CI with Gradle

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

jobs:
  build:
    runs-on: hibricks-dev
    env:
      DEV_PATH: /home/ec2-user
    steps:
      - uses: actions/checkout@v3
      - name: Deploy to server
        run: ./deploy.sh
  alarm:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Get current date
        run: echo "NOW=$(date -u -d '+9 hours' +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV
      - name: Discord Webhook Action
        uses: tsickert/discord-webhook@v5.3.0
        with:
          webhook-url: ${{ secrets.DISCORD_WEBHOOK }}
          content: |
            🌟 **배포 완료!** 🌟
            👏 배포 내용을 확인해 주세요!!
            ---
            🚀 *배포 일자:* ${{ env.NOW }}
            🌐 *서버:* {웹주소}
            🎉 *GIT 주소:* [GIT 바로가기](깃 주소)

 

728x90