CI\CD

Github, Jenkins, Docker를 활용한 CI/CD - 3

KJihun 2025. 5. 19. 09:20
728x90

이전 글에서 CI를 통해 Docker-compose.xml, dockerfiles 및 .jar file 이 jenkins 아카이브에 저장되었었다.

 

아카이브에 저장된 데이터들을 토대로 CD를 구현하기 위해 새로운 pipeline을 만든다.

 

1. 배포할 .jar 파일명

2. public IP 명

과금문제로 인해 IP가 바뀌게 되므로 이 두가지 값을 매개변수로 지정하여 사용할 예정이다.

 

.jar

 

매개변수 명 : 이후에 작성할 script에서 호출하기 위한 이름

Default Value : 초기 파라미터 값을 지정해준다. 잦은 변경이 일어나지 않을경우 유용하다

설명 : 해당 파라미터가 어떤 파라미터인지 알아볼 수 있도록 설명하기 위한 부분

 

위와 동일하지만 public IP는 자주 바뀌기 때문에 default Value를 지정해주지 않았다.

매개변수가 더 필요할 경우 매개변수 추가를 클릭하여 추가할 수 있다.

 

 

 

Script 전체코드

pipeline {
    agent any

    parameters {
        string(name: 'JAR_FILE_NAME', defaultValue: 'build/libs/demo-0.0.1-SNAPSHOT.jar', description: '복사된 .jar 파일명을 입력하세요 (예: build/libs/demo-0.0.1-SNAPSHOT.jar)')
        string(name: 'EC2_PUBLIC_IP', defaultValue: '', description: '배포 대상 EC2 퍼블릭 IP를 입력하세요 (예: 13.209.65.95)')
    }

    environment {
        EC2_USER = 'ubuntu'
        REMOTE_DIR = '/home/ubuntu/deploy'
        REMOTE_JAR_PATH = '/home/ubuntu/deploy/app.jar'
        PROPERTIES_CREDENTIAL_ID = 'properties'
        PROPERTIES_FILE_NAME = 'application.properties'
    }

    stages {
        stage('Copy Artifact from CI') {
            steps {
                copyArtifacts(
                    projectName: 'CI',
                    selector: [$class: 'StatusBuildSelector', stable: false],
                    filter: "${params.JAR_FILE_NAME}",
                    target: '.'
                )
                copyArtifacts(
                    projectName: 'CI',
                    selector: [$class: 'StatusBuildSelector', stable: false],
                    filter: 'docker-artifacts.tar.gz',
                    target: '.'
                )
            }
        }

        stage('Copy Secret Properties') {
            steps {
                withCredentials([file(credentialsId: "${env.PROPERTIES_CREDENTIAL_ID}", variable: 'SECRET_PROPS')]) {
                    sh '''
                        cp $SECRET_PROPS ${PROPERTIES_FILE_NAME}
                    '''
                }
            }
        }

        stage('Fix Artifact Permission') {
            steps {
                echo '🔧 docker-artifacts.tar.gz 파일 권한 및 소유자 변경'
                sh '''
                    chown jenkins:jenkins docker-artifacts.tar.gz || true
                    chmod 644 docker-artifacts.tar.gz || true
                '''
            }
        }

        stage('Validate Input') {
            steps {
                script {
                    if (params.JAR_FILE_NAME == '') {
                        error '❌ JAR_FILE_NAME 파라미터가 비어있습니다.'
                    }
                    if (!fileExists(params.JAR_FILE_NAME)) {
                        error "❌ 파일 '${params.JAR_FILE_NAME}'이 복사되지 않았습니다."
                    }
                    if (!fileExists('docker-artifacts.tar.gz')) {
                        error "❌ docker-artifacts.tar.gz 파일이 복사되지 않았습니다."
                    }
                    if (!fileExists(PROPERTIES_FILE_NAME)) {
                        error "❌ ${PROPERTIES_FILE_NAME} 파일이 존재하지 않습니다."
                    }
                    if (params.EC2_PUBLIC_IP == '') {
                        error '❌ EC2_PUBLIC_IP 파라미터가 비어있습니다.'
                    }
                }
            }
        }

        stage('Deploy to EC2') {
            steps {
                sshagent(['EC2_pem']) {
                    sh """
                        scp -o StrictHostKeyChecking=no docker-artifacts.tar.gz ${EC2_USER}@${params.EC2_PUBLIC_IP}:${REMOTE_DIR}/
                        scp -o StrictHostKeyChecking=no ${params.JAR_FILE_NAME} ${EC2_USER}@${params.EC2_PUBLIC_IP}:${REMOTE_JAR_PATH}
                        scp -o StrictHostKeyChecking=no ${PROPERTIES_FILE_NAME} ${EC2_USER}@${params.EC2_PUBLIC_IP}:${REMOTE_DIR}/

                        printf '%s\n' '
                            set -xe

                            cd ${REMOTE_DIR} || { echo "❌ 디렉토리 이동 실패: ${REMOTE_DIR}"; exit 1; }

                            echo "📍 docker-artifacts.tar.gz 압축 해제 중..."
                            tar -xvzf docker-artifacts.tar.gz || { echo "❌ 압축 해제 실패"; exit 1; }

                            echo "📍 Java 설치 확인 중..."
                            if ! command -v java &> /dev/null; then
                                echo "⚠ Java 미설치 감지. 설치 시작..."
                                sudo apt update && sudo apt install -y openjdk-17-jre-headless || { echo "❌ Java 설치 실패"; exit 1; }
                                echo "✅ Java 설치 완료"
                            else
                                echo "✅ Java 설치되어 있음"
                            fi

                            echo "📍 Docker 설치 확인 중..."
                            if ! command -v docker &> /dev/null; then
                                echo "⚠ Docker 미설치 감지. 설치 시작..."
                                sudo apt update && sudo apt install -y docker.io || { echo "❌ Docker 설치 실패"; exit 1; }
                                sudo systemctl start docker || { echo "❌ Docker 시작 실패"; exit 1; }
                                sudo systemctl enable docker || { echo "❌ Docker 자동시작 설정 실패"; exit 1; }
                                sudo usermod -aG docker ubuntu || { echo "⚠ Docker 그룹 추가 실패"; }
                                echo "✅ Docker 설치 완료"
                            else
                                echo "✅ Docker 설치되어 있음"
                            fi

                            echo "📍 Docker Compose 설치 확인 중..."
                            if ! command -v docker-compose &> /dev/null; then
                                echo "⚠ Docker Compose 미설치 감지. 설치 시작..."
                                sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-\$(uname -s)-\$(uname -m)" -o /usr/local/bin/docker-compose || { echo "❌ Docker Compose 다운로드 실패"; exit 1; }
                                sudo chmod +x /usr/local/bin/docker-compose || { echo "❌ Docker Compose 권한 변경 실패"; exit 1; }
                                echo "✅ Docker Compose 설치 완료"
                            else
                                echo "✅ Docker Compose 설치되어 있음"
                            fi

                            echo "🔁 docker-compose 실행"
                            if [ -f docker-compose.yml ]; then
                            
                                sudo docker-compose down || echo "⚠ docker-compose down 실패 (무시)"
                                sudo docker-compose up -d || echo "⚠ ddocker-compose up 실패 (무시)"
                                echo "✅ docker-compose 실행 완료"
                                
                            else
                                echo "ℹ docker-compose.yml 파일이 없어 실행하지 않음음"
                            fi

                            if [ ! -f ${REMOTE_DIR}/${PROPERTIES_FILE_NAME} ]; then
                                echo "❌ ${PROPERTIES_FILE_NAME} 파일이 누락됨"
                                exit 1
                            fi

                            CURRENT_PID=\$(pgrep -f app.jar || true)
                            if [ -z "\$CURRENT_PID" ]; then
                                echo "ℹ 실행 중인 app.jar 프로세스가 없습니다."
                            else
                                echo "🔻 app.jar 실행 PID: \$CURRENT_PID"
                                kill -15 \$CURRENT_PID || true
                                sleep 5
                                if pgrep -f app.jar > /dev/null; then
                                    echo "⚠ 정상 종료 실패, 강제 종료"
                                    kill -9 \$CURRENT_PID || true
                                fi
                                echo "✅ 기존 app.jar 프로세스 종료 완료"
                            fi

                            echo "▶ 새 JAR 실행 중..."
                            nohup java -jar ${REMOTE_JAR_PATH} \\
                                --spring.config.location=${REMOTE_DIR}/${PROPERTIES_FILE_NAME} \\
                                > ${REMOTE_DIR}/app.log 2>&1 &

                            sleep 10
                            if ! pgrep -f "app.jar" > /dev/null; then
                                echo "❌ JAR 프로세스 실행 실패"
                                cat ${REMOTE_DIR}/app.log || echo "⚠ 로그 없음"
                                echo "⚠ JAR 실행 실패 무시하고 진행"
                            else
                                echo "✅ JAR 프로세스 정상 실행 중"
                            fi
                        ' | ssh -o StrictHostKeyChecking=no ${EC2_USER}@${params.EC2_PUBLIC_IP} bash -xe
                    """
                }
            }
        }

        stage('Check App Status') {
            steps {
                sshagent(['EC2_pem']) {
                    sh """
                        ssh -o StrictHostKeyChecking=no ${EC2_USER}@${params.EC2_PUBLIC_IP} bash -c '
                            if pgrep -f "app.jar" > /dev/null; then
                                echo "✅ 앱 실행 중"
                            else
                                echo "❌ 앱 실행 실패"
                                echo "=== app.log 내용 ==="
                                cat ${REMOTE_DIR}/app.log || echo "⚠ 로그 없음"
                                echo "=== 끝 ==="
                                exit 1
                            fi
                        '
                    """
                }
            }
            post {
                always {
                    echo 'Check App Status stage finished.'
                }
            }
        }
    }

    post {
        success {
            echo '🚀 배포 완료'
        }
        failure {
            echo '❌ 배포 실패'
        }
    }
}