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