Skip to content

Instantly share code, notes, and snippets.

@hyeon0208
Last active March 1, 2024 15:58
Show Gist options
  • Save hyeon0208/5a68041ffc4dd1f4969a80a6f5239b65 to your computer and use it in GitHub Desktop.
Save hyeon0208/5a68041ffc4dd1f4969a80a6f5239b65 to your computer and use it in GitHub Desktop.
무중단 배포
# CodeDeploy 버전 (테스트 모드 : 0.0 )
version: 0.0
# 배포할 서버의 운영체제
os: linux
files:
- source: / # CodeDeploy에서 전달해 준 파일 중 destination으로 이동시킬 대상을 지정 (루트 경로 : 전체 파일)
destination: /home/ec2-user/app/deploy/zip/ # source에서 지정된 파일을 받을 위치
overwrite: yes # 기존 파일들을 덮어 쓰기
# CodeDeploy에서 EC2로 넘겨준 파일들을 모두 ec2-user 권한 부여.
permissions:
- object: /
pattern: "**"
owner: ec2-user
group: ec2-user
# CodeDeploy 배포 단계에서 실행할 명령어를 지정. (차례 대로 스크립트들이 실행 )
hooks:
AfterInstall:
- location: stop.sh # 엔진엑스와 연결되어 있지 않은 스프링 부트를 종료.
timeout: 60
runas: ec2-user
ApplicationStart:
- location: start.sh # Nginx와 연결되어 있지 않은 Port로 새 버전의 스프링 부트 시작.
timeout: 60
runas: ec2-user
ValidateService:
- location: health.sh # 새 스프링 부트가 정상적으로 실행됬는지 확인.
timeout: 60
runas: ec2-user
name: deploy to ec2
on:
push:
branches: [ "master" ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
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 bootJar
- name: Generate deployment package
run: |
mkdir -p before-deploy
cp scripts/*.sh before-deploy/
cp appspec.yml before-deploy/
cp build/libs/*.jar before-deploy/
cd before-deploy && zip -r before-deploy *
cd ../ && mkdir -p deploy
mv before-deploy/before-deploy.zip deploy/fruit-mall.zip
shell: bash
- name: Make zip file
run: zip -r ./fruit-mall.zip .
shell: bash
- name: Deliver to AWS S3
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: us-east-1
- name: Upload to S3
run: aws s3 cp ./deploy/fruit-mall.zip s3://fruit-mall-s3/jar-folder/
- name: Code Deploy
run: |
aws deploy create-deployment \
--application-name fruit-mall-codedeploy \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--deployment-group-name fruit-mall-codedeploy-group \
--file-exists-behavior OVERWRITE \
--s3-location bucket=fruit-mall-s3,bundleType=zip,key=jar-folder/fruit-mall.zip \
--region us-east-1
#!/usr/bin/env bash
# Nginx와 연결되지 않은 포트로 스프링 부트가 잘 수행되었는지 체크
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh
source ${ABSDIR}/switch.sh
IDLE_PORT=$(find_idle_port)
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 'real' | wc -l)
if [ ${UP_COUNT} -ge 1 ]
then # $up_count >= 1 ("real" 문자열이 있는지 검증)
echo "> Health check 성공"
switch_proxy # 잘 떳다면, switch.sh의 switch_proxy로 Nginx 프록시 설정을 변경
break
else
echo "> Health check의 응답을 알 수 없거나 혹은 실행 상태가 아닙니다."
echo "> Health check: ${RESPONSE}"
fi
if [ ${RETRY_COUNT} -eq 10 ]
then
echo "> Health check 실패. "
echo "> 엔진엑스에 연결하지 않고 배포를 종료합니다."
exit 1
fi
echo "> Health check 연결 실패. 재시도..."
sleep 10
done
#!/usr/bin/env bash
# 쉬고 있는 profile 찾기: real1이 사용중이면 real2가 쉬고 있고, 반대면 real1이 쉬고 있음
function find_idle_profile()
{ # 현재 엔진엑스가 바라보고 있는 스프링부트가 정상적으로 수행 중인지 확인.
RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://localhost/profile)
if [ ${RESPONSE_CODE} -ge 400 ] # 400 보다 크면 (즉, 40x/50x 에러 모두 포함)
then # 오류가 발생하면 real2를 현재 profile로 사용
CURRENT_PROFILE=real2
else
CURRENT_PROFILE=$(curl -s http://localhost/profile)
fi
if [ ${CURRENT_PROFILE} == real1 ]
then # IDLE_PROFILE 은 엔진엑스와 연결되지 않은 프로필 , 스프링 부트 프로젝트를 이 프로필로 연결
IDLE_PROFILE=real2
else
IDLE_PROFILE=real1
fi
# bash는 return value가 안되니 *제일 마지막줄에 echo로 해서 결과 출력*후, 클라이언트에서 값을 사용한다
echo "${IDLE_PROFILE}"
}
# 쉬고 있는 profile의 port 찾기
function find_idle_port()
{
IDLE_PROFILE=$(find_idle_profile)
if [ ${IDLE_PROFILE} == real1 ]
then
echo "8081"
else
echo "8082"
fi
}
#!/usr/bin/env bash
# Nginx와 연결되지 않은 포트로 스프링 부트가 잘 수행되었는지 체크
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh
source ${ABSDIR}/switch.sh
IDLE_PORT=$(find_idle_port)
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 'real' | wc -l)
if [ ${UP_COUNT} -ge 1 ]
then # $up_count >= 1 ("real" 문자열이 있는지 검증)
echo "> Health check 성공"
switch_proxy # 잘 떳다면, switch.sh의 switch_proxy로 Nginx 프록시 설정을 변경
break
else
echo "> Health check의 응답을 알 수 없거나 혹은 실행 상태가 아닙니다."
echo "> Health check: ${RESPONSE}"
fi
if [ ${RETRY_COUNT} -eq 10 ]
then
echo "> Health check 실패. "
echo "> 엔진엑스에 연결하지 않고 배포를 종료합니다."
exit 1
fi
echo "> Health check 연결 실패. 재시도..."
sleep 10
done
#!/usr/bin/env bash
ABSPATH=$(readlink -f $0) # 절대경로
ABSDIR=$(dirname $ABSPATH ) # 현재 stop.sh가 속해있는 경로를 찾는다.
source ${ABSDIR}/profile.sh # 자바로 보면 일종의 import 구문, stop.sh에서도 profile.sh의 여러 function을 사용 가능
IDLE_PORT=$(find_idle_port)
echo "> $IDLE_PORT 에서 구동중인 애플리케이션 pid 확인"
IDLE_PID=$(lsof -ti tcp:${IDLE_PORT})
if [ -z ${IDLE_PID} ]
then
echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다."
else
echo "> kill -15 $IDLE_PID"
kill -15 ${IDLE_PID}
sleep 5
fi
#!/usr/bin/env bash
ABSPATH=$(readlink -f $0)
ABSDIR=$(dirname $ABSPATH)
source ${ABSDIR}/profile.sh
function switch_proxy() {
IDLE_PORT=$(find_idle_port)
echo "> 전환할 Port: $IDLE_PORT"
echo "> Port 전환"
# 하나의 문장을 만들어 파이프라인(|)으로 넘겨 주기 위해 echo를 사용. tee 는 앞의 문장을 읽어 해당 경로의 파일에 저장.
echo "set \$service_url http://127.0.0.1:${IDLE_PORT};" | sudo tee /etc/nginx/conf.d/service-url.inc
echo "> 엔진엑스 Reload"
# restart와 달리 끊김 없이 다시 불러온다. (중요한 설정을 반영해야한다면 restart 사용)
# 여기선 외부의 설정 파일인 service-url을 다시 불러오기 때문에 reload로 가능
sudo service nginx reload
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment