Some checks failed
main-branch-frovide/pipeline/head There was a failure building this commit
236 lines
13 KiB
Groovy
236 lines
13 KiB
Groovy
pipeline {
|
|
agent any
|
|
tools {
|
|
nodejs 'nodejs23' // 'NodeJS_18'는 Jenkins Global Tool Configuration에서 설정한 이름
|
|
}
|
|
environment {
|
|
// Jenkins Credentials ID (SSH 접속용)
|
|
JENKINS_SSH_CREDENTIAL_ID = 'credential-for-ssh' // Jenkins Credentials에 등록된 ID로 변경
|
|
|
|
// 배포 서버 정보
|
|
DEPLOY_USER = 'kegorii' // 배포 서버 SSH 사용자
|
|
DEPLOY_HOST = 'frovide.com' // 배포 서버 IP 또는 호스트명
|
|
APP_NAME = 'frovide-production' // PM2에서 사용할 애플리케이션 이름
|
|
|
|
// 사용할 두 개의 포트
|
|
PORT_A = 7051
|
|
PORT_B = 7052
|
|
|
|
// 배포 서버 내 프로젝트 경로
|
|
REMOTE_APP_ROOT_DIR = "/home/${env.DEPLOY_USER}/${env.APP_NAME}" // 프로젝트 루트 디렉토리
|
|
REMOTE_BUILD_DIR = "${env.REMOTE_APP_ROOT_DIR}/build" // SvelteKit 빌드 결과물이 위치할 디렉토리
|
|
|
|
// 배포 서버 내 Node.js 설치 및 PM2 캐시 경로
|
|
REMOTE_NPM_CACHE_DIR = "/home/${env.DEPLOY_USER}/.npm" // npm 캐시 경로 (최적화용)
|
|
REMOTE_SCRIPTS_DIR = "/opt/scripts" // switch_nginx_port.sh 스크립트 경로
|
|
|
|
// 헬스 체크 URL
|
|
HEALTH_CHECK_URL = "http://${env.DEPLOY_HOST}" // Nginx 80포트로 접근하여 헬스 체크
|
|
HEALTH_CHECK_PATH = "/health" // SvelteKit 앱에 구현할 헬스 체크 경로 (아래 참고)
|
|
HEALTH_CHECK_TIMEOUT_SECONDS = 30
|
|
HEALTH_CHECK_INTERVAL_SECONDS = 5
|
|
}
|
|
|
|
stages {
|
|
stage('Checkout Source Code') {
|
|
steps {
|
|
git branch: 'main', url: 'https://git.frovide.com/kegorii/frovide.com.git'
|
|
// 만약 main 브랜치가 아닌 다른 브랜치라면 해당 브랜치 이름으로 변경해주세요.
|
|
// 예: git branch: 'develop', url: '...'
|
|
}
|
|
}
|
|
|
|
stage('Install Dependencies and Build (SvelteKit)') {
|
|
steps {
|
|
// nvm 활성화를 위해 . (점) 명령을 사용
|
|
sh '''
|
|
echo "DATABASE_URL: $DATABASE_URL" // Global Env에 설정된 API_URL이 자동으로 사용됩니다.
|
|
npm install
|
|
npm run build
|
|
'''
|
|
echo "SvelteKit project built successfully."
|
|
}
|
|
}
|
|
|
|
stage('Determine Active Port') {
|
|
steps {
|
|
echo "before sshagent"
|
|
echo "JENKINS_SSH_CREDENTIAL_ID: ${env.JENKINS_SSH_CREDENTIAL_ID}" // 이 부분을 추가
|
|
sshagent(credentials: [env.JENKINS_SSH_CREDENTIAL_ID]) {
|
|
echo "after sshagent"
|
|
script {
|
|
// 현재 Nginx가 바라보고 있는 포트를 배포 서버에서 가져옴
|
|
def nginxConfPath = "/etc/nginx/sites-available/frovide.conf" // Nginx 설정 파일 경로
|
|
def currentActivePortOutput = sh(script: "ssh ${env.DEPLOY_USER}@${env.DEPLOY_HOST} 'grep -oP \"server 127.0.0.1:\\\\K[0-9]+\" ${nginxConfPath} | head -n 1'", returnStdout: true).trim()
|
|
|
|
if (currentActivePortOutput == "") {
|
|
// 초기 배포이거나 Nginx 설정에 활성화된 포트가 없는 경우
|
|
env.ACTIVE_PORT = env.PORT_A // 첫 배포 시 PORT_A를 활성 포트로 가정
|
|
env.INACTIVE_PORT = env.PORT_B // 다음 배포 대상은 PORT_B
|
|
echo "Initial deployment or no active port found. Setting active port to ${env.ACTIVE_PORT} and inactive to ${env.INACTIVE_PORT}."
|
|
} else if (currentActivePortOutput == "${env.PORT_A}") {
|
|
env.ACTIVE_PORT = env.PORT_A
|
|
env.INACTIVE_PORT = env.PORT_B
|
|
echo "Current active port is ${env.ACTIVE_PORT}. Next deployment target port will be ${env.INACTIVE_PORT}."
|
|
} else if (currentActivePortOutput == "${env.PORT_B}") {
|
|
env.ACTIVE_PORT = env.PORT_B
|
|
env.INACTIVE_PORT = env.PORT_A
|
|
echo "Current active port is ${env.ACTIVE_PORT}. Next deployment target port will be ${env.INACTIVE_PORT}."
|
|
} else {
|
|
error "Unexpected active port found: ${currentActivePortOutput}. Please check Nginx configuration and app_upstream.conf."
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
stage('Transfer Built Application') {
|
|
steps {
|
|
sshagent(credentials: [env.JENKINS_SSH_CREDENTIAL_ID]) {
|
|
script {
|
|
// 배포 서버에 프로젝트 루트 디렉토리 생성
|
|
sh "ssh ${env.DEPLOY_USER}@${env.DEPLOY_HOST} 'mkdir -p ${env.REMOTE_APP_ROOT_DIR}'"
|
|
|
|
// SvelteKit 빌드 결과물 (build 디렉토리)과 package.json, package-lock.json (PM2 의존성 확인용) 전송
|
|
echo "Transferring SvelteKit build artifacts and package files to ${env.REMOTE_APP_ROOT_DIR}..."
|
|
// rsync를 사용하여 변경된 파일만 전송하여 효율성을 높입니다.
|
|
sh "rsync -avz ./build/ ${env.DEPLOY_USER}@${env.DEPLOY_HOST}:${env.REMOTE_BUILD_DIR}"
|
|
sh "rsync -avz ./package.json ${env.DEPLOY_USER}@${env.DEPLOY_HOST}:${env.REMOTE_APP_ROOT_DIR}"
|
|
sh "rsync -avz ./package-lock.json ${env.DEPLOY_USER}@${env.DEPLOY_HOST}:${env.REMOTE_APP_ROOT_DIR}"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
stage('Deploy New Version (Inactive Port)') {
|
|
steps {
|
|
sshagent(credentials: [env.JENKINS_SSH_CREDENTIAL_ID]) {
|
|
script {
|
|
// PM2를 사용하여 새로운 버전의 애플리케이션을 Inactive Port에 시작
|
|
echo "Starting new version of ${env.APP_NAME} on port ${env.INACTIVE_PORT} using PM2..."
|
|
|
|
// SvelteKit adapter-node의 기본 실행 파일은 build/index.js 입니다.
|
|
// PM2는 프로젝트 루트에서 실행되어야 package.json을 찾을 수 있습니다.
|
|
def pm2StartCommand = "cd ${env.REMOTE_APP_ROOT_DIR} && NODE_ENV=production PORT=${env.INACTIVE_PORT} pm2 start build/index.js --name \"${env.APP_NAME}-${env.INACTIVE_PORT}\" --output /dev/null --error /dev/null --time --watch --ignore-watch=\"node_modules\" --max-memory-restart 500M"
|
|
|
|
// 이전 프로세스가 남아있을 경우를 대비하여 stop/delete 후 시작 (선택 사항)
|
|
sh "ssh ${env.DEPLOY_USER}@${env.DEPLOY_HOST} 'pm2 stop ${env.APP_NAME}-${env.INACTIVE_PORT} || true'"
|
|
sh "ssh ${env.DEPLOY_USER}@${env.DEPLOY_HOST} 'pm2 delete ${env.APP_NAME}-${env.INACTIVE_PORT} || true'"
|
|
sh "ssh ${env.DEPLOY_USER}@${env.DEPLOY_HOST} '${pm2StartCommand}'"
|
|
echo "PM2 process started for ${env.APP_NAME}-${env.INACTIVE_PORT}."
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
stage('Health Check New Version') {
|
|
steps {
|
|
sshagent(credentials: [env.JENKINS_SSH_CREDENTIAL_ID]) {
|
|
script {
|
|
echo "Performing health check on new version at port ${env.INACTIVE_PORT}..."
|
|
def isHealthy = false
|
|
def attempts = 0
|
|
def maxAttempts = env.HEALTH_CHECK_TIMEOUT_SECONDS.toInteger() / env.HEALTH_CHECK_INTERVAL_SECONDS.toInteger()
|
|
|
|
while (!isHealthy && attempts < maxAttempts) {
|
|
try {
|
|
// SvelteKit 앱이 직접 해당 포트에서 헬스 체크 엔드포인트에 응답하는지 확인
|
|
def healthCheckTarget = "http://localhost:${env.INACTIVE_PORT}${env.HEALTH_CHECK_PATH}"
|
|
def responseCode = sh(script: "ssh ${env.DEPLOY_USER}@${env.DEPLOY_HOST} 'curl -s -o /dev/null -w \"%{http_code}\" ${healthCheckTarget}'", returnStdout: true).trim()
|
|
|
|
if (responseCode == "200") {
|
|
isHealthy = true
|
|
echo "Health check passed for new version on port ${env.INACTIVE_PORT}."
|
|
} else {
|
|
echo "Health check failed (HTTP ${responseCode}) for new version on port ${env.INACTIVE_PORT}. Retrying..."
|
|
sleep env.HEALTH_CHECK_INTERVAL_SECONDS.toInteger()
|
|
}
|
|
} catch (e) {
|
|
echo "Error during health check: ${e}. Retrying..."
|
|
sleep env.HEALTH_CHECK_INTERVAL_SECONDS.toInteger()
|
|
}
|
|
attempts++
|
|
}
|
|
|
|
if (!isHealthy) {
|
|
error "Health check failed for new version on port ${env.INACTIVE_PORT} after ${attempts} attempts. Aborting deployment."
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
stage('Switch Nginx (Traffic Shift)') {
|
|
steps {
|
|
sshagent(credentials: [env.JENKINS_SSH_CREDENTIAL_ID]) {
|
|
script {
|
|
echo "Switching Nginx to new version on port ${env.INACTIVE_PORT}..."
|
|
// 배포 서버에서 Nginx 스위치 스크립트 실행
|
|
sh "ssh ${env.DEPLOY_USER}@${env.DEPLOY_HOST} '${env.REMOTE_SCRIPTS_DIR}/switch_nginx_port.sh ${env.INACTIVE_PORT}'"
|
|
echo "Nginx successfully switched to port ${env.INACTIVE_PORT}."
|
|
|
|
// 최종적으로 Nginx 80포트로 헬스 체크하여 새로운 버전이 정상 서비스되는지 확인
|
|
echo "Final health check via Nginx (port 80)..."
|
|
def isNginxHealthy = false
|
|
def nginxAttempts = 0
|
|
def maxNginxAttempts = 10 // Nginx 스위치 후 짧게 확인
|
|
while (!isNginxHealthy && nginxAttempts < maxNginxAttempts) {
|
|
try {
|
|
def responseCode = sh(script: "curl -s -o /dev/null -w \"%{http_code}\" ${env.HEALTH_CHECK_URL}${env.HEALTH_CHECK_PATH}", returnStdout: true).trim()
|
|
if (responseCode == "200") {
|
|
isNginxHealthy = true
|
|
echo "Nginx health check passed. Traffic successfully shifted."
|
|
} else {
|
|
echo "Nginx health check failed (HTTP ${responseCode}). Retrying..."
|
|
sleep 5
|
|
}
|
|
} catch (e) {
|
|
echo "Error during Nginx health check: ${e}. Retrying..."
|
|
sleep 5
|
|
}
|
|
nginxAttempts++
|
|
}
|
|
|
|
if (!isNginxHealthy) {
|
|
error "Final Nginx health check failed after ${nginxAttempts} attempts. Potential issue with Nginx switch."
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
stage('Stop Old Version') {
|
|
steps {
|
|
sshagent(credentials: [env.JENKINS_SSH_CREDENTIAL_ID]) {
|
|
script {
|
|
echo "Stopping old version of ${env.APP_NAME} on port ${env.ACTIVE_PORT}..."
|
|
// PM2를 사용하여 이전 버전의 애플리케이션 종료
|
|
sh "ssh ${env.DEPLOY_USER}@${env.DEPLOY_HOST} 'pm2 stop ${env.APP_NAME}-${env.ACTIVE_PORT} || true'"
|
|
sh "ssh ${env.DEPLOY_USER}@${env.DEPLOY_HOST} 'pm2 delete ${env.APP_NAME}-${env.ACTIVE_PORT} || true'"
|
|
echo "Old version on port ${env.ACTIVE_PORT} stopped."
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
post {
|
|
always {
|
|
cleanWs() // Jenkins 워크스페이스 정리
|
|
echo "Deployment pipeline finished."
|
|
}
|
|
success {
|
|
echo "Deployment successful!"
|
|
// 알림 보내기 (예: Slack)
|
|
// slackSend channel: '#deploy-notifications', message: "Deployment of ${env.APP_NAME} to ${env.DEPLOY_HOST} successful!"
|
|
}
|
|
failure {
|
|
echo "Deployment failed! Rolling back or investigate manually."
|
|
// 롤백 로직 추가 (선택 사항)
|
|
// 예: Nginx를 이전 포트로 다시 전환하는 스크립트 실행
|
|
// ssh ${env.DEPLOY_USER}@${env.DEPLOY_HOST} '${env.REMOTE_SCRIPTS_DIR}/switch_nginx_port.sh ${env.ACTIVE_PORT}'
|
|
// 알림 보내기
|
|
// slackSend channel: '#deploy-notifications', message: "Deployment of ${env.APP_NAME} to ${env.DEPLOY_HOST} FAILED!"
|
|
}
|
|
}
|
|
} |