# 자주 사용하는 값 변수에 저장
REPOSITORY=/home/ubuntu/repository
PROJECT_NAME=Team8_BE
JAR_NAME=Team8_BE
# 배포 시 사용할 포트 결정
if lsof -i :8080; then
IDLE_PORT=8081
else
IDLE_PORT=8080
fi
# IDLE_PORT가 사용 중이라면 종료
if lsof -i :$IDLE_PORT; then
echo "> IDLE_PORT ($IDLE_PORT) 사용 중. 종료합니다."
fuser -k ${IDLE_PORT}/tcp
fi
echo "> IDLE_PORT: $IDLE_PORT 에 새 애플리케이션 배포 시작"
# git clone 받은 위치로 이동
cd $REPOSITORY/$PROJECT_NAME/
# Weekly 브랜치의 최신 내용 받기
echo "> Git Pull"
git pull origin Weekly
# build 수행
echo "> project build start"
echo "> 30초 ~ 2분 사이의 시간이 소요됩니다. 2분이 지날 경우 말씀해주세요."
./gradlew build -x test
echo "> directory로 이동"
cd $REPOSITORY
# build의 결과물 (jar 파일) 특정 위치로 복사
echo "> build 파일 복사"
cp $REPOSITORY/$PROJECT_NAME/build/libs/*.jar $REPOSITORY/
echo "> 새 애플리케이션 배포"
JAR_NAME=$(ls -tr $REPOSITORY/ | grep jar | tail -n 1)
echo "> 배포할 JAR Name: $JAR_NAME"
nohup java -Duser.timezone=Asia/Seoul -jar $REPOSITORY/$JAR_NAME --spring.profiles.active=prod --server.port=$IDLE_PORT>
# 안정적 배포를 위해 30초간 대기
echo "> 안정적 배포를 위해 30초간 대기중..."
sleep 5
echo "> 25초 남았습니다..."
sleep 5
echo "> 20초 남았습니다..."
sleep 5
echo "> 15초 남았습니다..."
sleep 5
echo "> 10초 남았습니다..."
sleep 5
echo "> 5초 남았습니다..."
sleep 5
# Health Check
echo "> Health Check 시작"
for retry_count in {1..10}
do
RESPONSE=$(curl -k -s <https://localhost>:$IDLE_PORT/actuator/health)
UP_COUNT=$(echo $RESPONSE | grep 'UP' | wc -l)
if [ $UP_COUNT -ge 1 ]; then
echo "> Health Check 성공"
break
else
echo "> Health Check 실패, 재시도중... ($retry_count)"
sleep 5
fi
if [ $retry_count -eq 10 ]; then
echo "> Health Check 실패. 배포 중단"
fuser -k ${IDLE_PORT}/tcp
exit 1
fi
done
# Port 전환
echo "> 포트 전환: 기존 포트를 $IDLE_PORT로 변경"
# Nginx 설정 파일에서 proxy_pass 업데이트
sudo sed -i "s|$service_url <https://127.0.0.1>:[0-9]*;|$service_url <https://127.0.0.1>:${IDLE_PORT};|" /etc/nginx/sites-a>
# Nginx 재로드
sudo service nginx reload
# 10초 대기
echo "> 10초간 대기하며 로그 수집중..."
sleep 5
echo "> 5초 남았습니다..."
sleep 5
# deploy.log의 마지막 50줄을 읽어옴
LOG_CONTENT=$(tail -n 50 $REPOSITORY/deploy.log)
# 슬랙 메시지 전송
curl -X POST -H 'Content-type: application/json' --data "{
\\"text\\": \\"*[서버 재부팅 로그 (last 50 lines)]:* \\n\\`\\`\\`$LOG_CONTENT\\`\\`\\`\\"
}" $SLACK_WEBHOOK_URL
echo ""
# 기존 인스턴스 종료
if [ "$IDLE_PORT" -eq 8080 ]; then
echo "> 포트 8081을 사용하는 프로세스 종료"
TERMINATED_PORT=8081
fuser -k 8081/tcp
else
echo "> 포트 8080을 사용하는 프로세스 종료"
TERMINATED_PORT=8080
fuser -k 8080/tcp
fi
echo "> 안정화를 위해 5초간 대기중.."
sleep 5
# 무중단 배포 완료 메시지를 슬랙으로 전송
DEPLOY_MESSAGE="*무중단 배포 완료!*\\n\\n서버가 업데이트 되었습니다.\\n\\n*배포된 포트:* \\`$IDLE_PORT\\`\\n*종료된 포트:* \\`$>
curl -X POST -H 'Content-type: application/json' --data "{
\\"text\\": \\"$DEPLOY_MESSAGE\\"
}" $SLACK_WEBHOOK_URL
echo ""
echo "> 완료"
# SSH 세션 종료
exit 0
nginx 서버 설정 코드 (/etc/nginx/sites-available)
server {
#listen 80 default_server;
#listen [::]:80 default_server;
# SSL configuration
root /var/www/html;
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;
server_name sinitto.site www.sinitto.site;
set $service_url <https://127.0.0.1:8080>;
location / {
proxy_pass $service_url;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/sinitto.site/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/sinitto.site/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
SSL인증서 갱신시 만들어지는 keystore.p12 확장자 Java에 맞게 변환 및 resources 내부로 넣어주는 목적의 update_keystore.sh
# 필요한 파일 경로
KEYSTORE_PATH="/home/ubuntu/repository/Team8_BE/src/main/resources/keystore.p12"
CERTIFICATE_PATH="/etc/letsencrypt/live/sinitto.site/fullchain.pem"
PRIVATE_KEY_PATH="/etc/letsencrypt/live/sinitto.site/privkey.pem"
CHAIN_PATH="/etc/letsencrypt/live/sinitto.site/chain.pem"
KEYSTORE_PASSWORD="sinitto"
# 현재 시간으로 백업 파일 이름 생성
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
BACKUP_KEYSTORE="${KEYSTORE_PATH}.bak.${TIMESTAMP}"
# 기존 keystore.p12 파일 백업
if [ -f "$KEYSTORE_PATH" ]; then
sudo cp "$KEYSTORE_PATH" "$BACKUP_KEYSTORE"
echo "기존 keystore.p12 파일을 백업했습니다: $BACKUP_KEYSTORE"
fi
# 새 keystore.p12 파일 생성
sudo openssl pkcs12 -export \\
-in "$CERTIFICATE_PATH" \\
-inkey "$PRIVATE_KEY_PATH" \\
-out "$KEYSTORE_PATH" \\
-name tomcat \\
-CAfile "$CHAIN_PATH" \\
-caname root \\
-password pass:"$KEYSTORE_PASSWORD"
echo "새 keystore.p12 파일이 생성되었습니다: $KEYSTORE_PATH"
# 새 keystore.p12 파일 권한 변경
sudo chmod 644 "$KEYSTORE_PATH"
echo "새 keystore.p12 파일의 권한을 644로 변경했습니다."
# 로그 파일로 출력 리다이렉션
sudo exec >> /var/log/update_keystore.log 2>&1
echo "Update keystore script executed at $(date)"
# deploy.sh 실행
#DEPLOY_SCRIPT_PATH="/home/ubuntu/deploy.sh"
if [ -f "$DEPLOY_SCRIPT_PATH" ]; then
echo "Deploying application using $DEPLOY_SCRIPT_PATH"
sudo bash "$DEPLOY_SCRIPT_PATH"
else
echo "Error: $DEPLOY_SCRIPT_PATH not found."
fi
서버 백그라운드에서 지속 실행, 서버의 errorlog를 캐치해 slack에 전송하기 위한 errorlog.sh
({Solar-pro 키} 제외)
# 로그 파일 위치
LOG_FILE="/home/ubuntu/repository/deploy.log"
# 로그 감지를 위해 tail 사용
tail -F "$LOG_FILE" | while read LOG_LINE
do
if [[ "$LOG_LINE" == *"ERROR"* ]]; then
(
# 잠금 파일을 이용해 동시 실행 방지
exec 200>/tmp/log_process.lock
flock -n 200 || exit 1
# Solar-pro에 에러 로그 전송해서 해결 방법 가져옴 (최대 3회 재시도)
attempt=0
max_attempts=3
while [ $attempt -lt $max_attempts ]; do
response=$(curl --location '<https://api.upstage.ai/v1/solar/chat/completions>' \\
--header 'Authorization: Bearer {Solar-pro 키}' \\
--header 'Content-Type: application/json' \\
--data '{
"model": "solar-pro",
"messages": [
{
"role": "system",
"content": "에러 코드가 request로 들어오면 해당 에러코드에 대한 설명 및 해결 방안을 제[>
},
{
"role": "user",
"content": "'"$LOG_LINE"'"
}
]
}')
# 응답에서 message.content만 추출
content=$(echo "$response" | jq -r '.choices[0].message.content')
# 응답이 성공적으로 처리되었으면 종료
if [ -n "$content" ]; then
break
fi
# 재시도 횟수 증가
attempt=$((attempt + 1))
sleep 1 # 재시도 전 대기 시간
done
# Slack에 로그 및 AI 응답 전송 (최종 응답에 따른 처리)
if [ -n "$content" ]; then
curl -X POST -H 'Content-type: application/json' --data "{
\\"text\\": \\"*[에러 코드 발생]:* \\n\\`\\`\\`$LOG_LINE\\`\\`\\`\\n *[해결 방법]:* \\n\\`\\`\\`$content\\`\\`\\`\\"
}" $SLACK_WEBHOOK_URL
else
curl -X POST -H 'Content-type: application/json' --data "{
\\"text\\": \\"*[에러]:* \\n응답을 받지 못했습니다. 로그: \\n\\`\\`\\`$LOG_LINE\\`\\`\\`\\"
}" $SLACK_WEBHOOK_URL
fi
# 잠금 해제
flock -u 200
) &
fi
done