무중단 배포 및 결과 슬랙 전송을 위한 핵심 쉘스크립트

deploy.sh

# 자주 사용하는 값 변수에 저장
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

무중단배포 Green/Blue 서버 라우팅을 위한 코드

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
}

HTTPS SSL인증서 관련 코드

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