Information Security Study

롤백 기능 구현하기 본문

네트워크 캠퍼스/2차 프로젝트

롤백 기능 구현하기

gayeon_ 2024. 7. 23. 11:19

롤백 기능 구현하기

 

/users/signup

페이지를 주석처리하고 commit change를 하면 젠킨스에서 자동으로 빌드된다.

 

 

 

백업파일이 덮어씌워졌다고 출력되어서 app01에 접속해 확인하니

 

 

 

잘 덮어씌워졌다.

 

 

 

app01은 롤백에 성공했는데

 

 

 

이상하게 app01-1이 바로 스킵된다.

 

 

 

우선 롤백이 된 app01이 문제가 없는지 확인하기 위해

파워셸에서 java -jar 명령어로 덮어씌워진 .jar 파일을 실행했다.

 

 

 

이미 젠킨스에서 app01이 배포되어 있어 8080 포트를 쓰고 있기 때문에

jar 파일을 실행하기 전 java 관련 프로세스가 돌고 있나 확인하고 kill로 종료시킨 뒤에 해야 한다.

 

 

 

/users/signup

을 주석처리했는데도 백업파일로 잘 실행되었으니 app01은 롤백에 성공한 것이 분명하다.

하지만 이상하게 app01-1은 스킵된다..

 

pipeline {
    tools {
        gradle "GRADLE" // Jenkins에서 설정한 Gradle의 이름
    }
    agent any
    stages {
        stage('Clone') {
            steps {
                git branch: 'master', url: 'git 레포지토리 주소'
            }
        }
        
        stage('Set Permissions') {
            steps {
                sh 'chmod +x ./gradlew'
            }
        }

        stage('Test') {
            steps {
                script {
                    sh './gradlew test'
                }
            }
        }

        stage('Build') {
            steps {
                sh './gradlew clean build'
                sh './gradlew clean build -Pdb.url=jdbc:mysql://서버 아이피:포트/데이터베이스명?useSSL=false -Pdb.username=유저명 -Pdb.password=비밀번호 -Pdb.driver=com.mysql.cj.jdbc.Driver'
                // 빌드 후 JAR 파일을 원격 서버로 복사
                script {
                    copyJarToRemote('app01 ip')
                    copyJarToRemote('app01-1 ip')
                }
            }
        }

        stage('Deploy1') {
            steps {
                script {
                    catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
                        deployAndTest('app01 ip')
                    }
                }
            }
        }
        
        stage('Deploy2') {
            steps {
                script {
                    catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') {
                        deployAndTest('app01-1 ip')
                    }
                }
            }
        }
    }
    post {
        success {
            echo "This will run when the run finishes successfully"
        }
        failure {
            echo "This will run if failed"
        }
    }
}

def copyJarToRemote(targetServerIp) {
    def jarFile = '.jar 경로'
    def deployPath = '/home/ubuntu'

    sshagent(['credential key 이름']) {
        // 새로운 JAR 파일 전송
        def scpCmd = "scp -o StrictHostKeyChecking=no $jarFile ubuntu@$targetServerIp:$deployPath/"
        def scpResult = sh(script: scpCmd, returnStatus: true)
        if (scpResult != 0) {
            echo "Failed to copy jar file to $targetServerIp"
            error "Failed to copy jar file"
        } else {
            echo "Successfully copied jar file to $targetServerIp"
        }
    }
}

def deployAndTest(targetServerIp) {
    def jarFile = '/home/ubuntu/파일명.jar'
    def deployPath = '/home/ubuntu'
    def runAppCommand = "nohup java -jar $jarFile > $deployPath/log.log 2>&1 &"
    def checkLogCommand = "grep -q \"Started\" $deployPath/log.log"
    def maxAttempts = 40
    def sleepInterval = 3 // 3 seconds
    def backupJarFile = '파일명-backup.jar'

    sshagent(['credentail key 이름']) {
        sh script: "ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp 'pgrep -f 파일명.jar && pkill -f 파일명.jar || echo \"No process found\"'", returnStatus: true

        // 백업 파일 생성
        def checkBackupCmd = "ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp '[ ! -f $deployPath/$backupJarFile ]'"
        def checkBackupResult = sh(script: checkBackupCmd, returnStatus: true)
        if (checkBackupResult == 0) {
            def backupCmd = "ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp 'cp $deployPath/파일명.jar $deployPath/$backupJarFile'"
            def backupResult = sh(script: backupCmd, returnStatus: true)
            if (backupResult != 0) {
                echo "Failed to create backup file on $targetServerIp"
                error "Failed to create backup file"
            }
        } else {
            echo "Backup file already exists on $targetServerIp"
        }

        // 애플리케이션 실행
        def runCmd = "ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp '$runAppCommand'"
        def runResult = sh(script: runCmd, returnStatus: true)
        if (runResult != 0) {
            echo "Failed to start application on $targetServerIp"
            error "Failed to start application"
        }

        def attempts = 0
        def deploymentSuccess = false
        while (attempts < maxAttempts) {
            int result = sh script: "ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp '$checkLogCommand'", returnStatus: true
            if (result == 0) {
                echo "Deployment to $targetServerIp was successful."
                deploymentSuccess = true
                break
            }
            attempts++
            sleep sleepInterval
        }

        if (!deploymentSuccess) {
            echo "Deployment to $targetServerIp failed. Rolling back to previous version."
            rollbackToPreviousVersion(targetServerIp, backupJarFile, deployPath, runAppCommand, checkLogCommand)
            error "Deployment to $targetServerIp failed."
        }

        // CD 테스트
        def cdTestResult = sh script: "curl -s -o /dev/null -w '%{http_code}' http://$targetServerIp:8080", returnStdout: true
        echo "cdTestResult: '${cdTestResult.trim()}'"
        if (cdTestResult.trim() != "200") {
            echo "CD test failed for $targetServerIp. Rolling back to previous version."
            rollbackToPreviousVersion(targetServerIp, backupJarFile, deployPath, runAppCommand, checkLogCommand)
        } else {
            echo "CD test passed for $targetServerIp."
        }
    }
}

def rollbackToPreviousVersion(targetServerIp, backupJarFile, deployPath, runAppCommand, checkLogCommand) {
    def maxAttempts = 40
    def sleepInterval = 3 // 3 seconds

    sshagent(['JENKINS-KEY']) {
        // 현재 실행 중인 애플리케이션 프로세스 종료
        sh script: "ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp 'pgrep -f 파일명.jar && pkill -f 파일명.jar || echo \"No process found\"'", returnStatus: true

        // 백업 파일을 원본 파일로 덮어씌우기
        sh script: """
            ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp 'if [ -f $deployPath/$backupJarFile ]; then
                mv -f $deployPath/$backupJarFile $deployPath/파일명.jar && echo "Backup file moved successfully."
            else
                echo "Backup file does not exist."
            fi'
        """, returnStatus: true

        // 원본 파일에 실행 권한 설정
        sh script: """
            ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp 'chmod +x $deployPath/파일명.jar && echo "Execution permission set on original file."'
        """, returnStatus: true

        // 애플리케이션 재실행
        sh "ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp '$runAppCommand'"

        // 롤백 완료 여부 확인
        def attempts = 0
        def rollbackSuccess = false
        while (attempts < maxAttempts) {
            int result = sh script: "ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp '$checkLogCommand'", returnStatus: true
            if (result == 0) {
                echo "Rollback to previous version on $targetServerIp was successful."
                rollbackSuccess = true
                break
            }
            attempts++
            sleep sleepInterval
        }

        if (!rollbackSuccess) {
            error "Rollback to previous version on $targetServerIp failed."
        }
    }
}

 

찾아보니 error는 파이프라인 이후의 단계가 모두 스킵될 수 있다고 해서

rollbackToPreviousVersion 함수에 있는 error를 catchError로 변경했다.

 

 

 

주석처리한 /users/signup 페이지에 잘 접속도 되고

각 app의 log.log를 봐도 정상 실행 중이다.

 

 

 

이 명령어로 nginx가 app01과 app01-1 모두 보여주고 있는지 확인할 수 있다.

 

 

 

backend_server를 보면 app01, app01-1의 주소가 모두 나오고 있다.

 

 

 

콘솔 output에도 롤백이 성공적으로 되었다고 하지만

 

 

 

배포에 실패했다고 뜬다..

분명 배포가 되었기 때문에 회원가입 페이지도 접속되는 걸 텐데.. 뭐지

 

 

 

상태도 unstable로 뜬다.

 

 

스크립트를 다시 자세히 살펴보니

괜히 배포 단계에 try catch문을 써서 그런 거였다..

 

 

app01 백업 파일 덮어씌우기

 

 

 

app 01 롤백 성공

 

 

 

app01 CD 테스트 통과

 

 

 

app01-1 백업 파일 덮어씌우기

 

 

 

app01-1 롤백 성공

 

 

 

app01-1 CD 테스트 통과

 

 

 

롤백 성공~~!!

 

 

롤백 성공한 코드(수정)

pipeline {
    tools {
        gradle "GRADLE" // Jenkins에서 설정한 Gradle의 이름
    }
    agent any
    stages {
        stage('Clone') {
            steps {
                git branch: 'master', url: 'git 레포지토리 주소'
            }
        }
        
        stage('Set Permissions') {
            steps {
                sh 'chmod +x ./gradlew'
            }
        }

        stage('Test') {
            steps {
                script {
                    sh './gradlew test'
                }
            }
        }

        stage('Build') {
            steps {
                sh './gradlew clean build'
                sh './gradlew clean build -Pdb.url=jdbc:mysql://주소:포트번호/데이터베이스명?useSSL=false -Pdb.username=유저명 -Pdb.password=비밀번호 -Pdb.driver=com.mysql.cj.jdbc.Driver'
                script {
                    copyJarToRemote('app01 ip')
                    copyJarToRemote('app01-1 ip')
                }
            }
        }

        
        stage('Deploy1') {
            steps {
                script {
                    deployAndTest('app01 ip')
                }
            }
        }
        
        stage('Deploy2') {
            steps {
                script {
                    deployAndTest('app01-1 ip')
                }
            }
        }
    }
    post {
        success {
            echo "Build and deployment succeeded."
        }
        failure {
            echo "Build or deployment failed."
        }
    }
}

def copyJarToRemote(targetServerIp) {
    def jarFile = 'jar 경로'
    def deployPath = '/home/ubuntu'

    sshagent(['credential key 이름']) {
        def scpCmd = "scp -o StrictHostKeyChecking=no $jarFile ubuntu@$targetServerIp:$deployPath/"
        def scpResult = sh(script: scpCmd, returnStatus: true)
        if (scpResult != 0) {
            echo "Failed to copy jar file to $targetServerIp"
            error "Failed to copy jar file"
        } else {
            echo "Successfully copied jar file to $targetServerIp"
        }
    }
}

def deployAndTest(targetServerIp) {
    def jarFile = '/home/ubuntu/파일명.jar'
    def deployPath = '/home/ubuntu'
    def runAppCommand = "nohup java -jar $jarFile > $deployPath/log.log 2>&1 &"
    def checkLogCommand = "grep -q 'Started' $deployPath/log.log"
    def maxAttempts = 10
    def sleepInterval = 3 // 3 seconds
    def backupJarFile = '파일명.jar'

    sshagent(['credentail key 이름']) {
        sh script: "ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp 'pgrep -f 파일명.jar && pkill -f 파일명.jar || echo \"No process found\"'", returnStatus: true

        def checkBackupCmd = "ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp '[ ! -f $deployPath/$backupJarFile ]'"
        def checkBackupResult = sh(script: checkBackupCmd, returnStatus: true)
        if (checkBackupResult == 0) {
            def backupCmd = "ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp 'cp $deployPath/파일명.jar $deployPath/$backupJarFile'"
            def backupResult = sh(script: backupCmd, returnStatus: true)
            if (backupResult != 0) {
                echo "Failed to create backup file on $targetServerIp"
                error "Failed to create backup file"
            }
        } else {
            echo "Backup file already exists on $targetServerIp"
        }

        def runCmd = "ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp '$runAppCommand'"
        def runResult = sh(script: runCmd, returnStatus: true)
        if (runResult != 0) {
            echo "Failed to start application on $targetServerIp"
            error "Failed to start application"
        }

        def attempts = 0
        def deploymentSuccess = false
        while (attempts < maxAttempts) {
            int result = sh script: "ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp '$checkLogCommand'", returnStatus: true
            if (result == 0) {
                echo "Deployment to $targetServerIp was successful."
                deploymentSuccess = true
                break
            }
            attempts++
            sleep sleepInterval
        }

        if (!deploymentSuccess) {
            echo "Deployment to $targetServerIp failed. Rolling back to previous version."
            rollbackToPreviousVersion(targetServerIp, backupJarFile, deployPath, runAppCommand, checkLogCommand)
            error "Deployment to $targetServerIp failed."
        }

        def cdTestResult = sh script: "curl -s -o /dev/null -w '%{http_code}' http://$targetServerIp:8080", returnStdout: true
        echo "CD test result: '${cdTestResult.trim()}'"
        if (cdTestResult.trim() != "200") {
            echo "CD test failed for $targetServerIp. Rolling back to previous version."
            rollbackToPreviousVersion(targetServerIp, backupJarFile, deployPath, runAppCommand, checkLogCommand)
        } else {
            echo "CD test passed for $targetServerIp."
        }
    }
}

def rollbackToPreviousVersion(targetServerIp, backupJarFile, deployPath, runAppCommand, checkLogCommand) {
    def maxAttempts = 10
    def sleepInterval = 3 // 3 seconds

    sshagent(['JENKINS-KEY']) {
        sh script: "ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp 'pgrep -f 파일명.jar && pkill -f 파일명.jar || echo \"No process found\"'", returnStatus: true

        sh script: """
            ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp 'if [ -f $deployPath/$backupJarFile ]; then
                mv -f $deployPath/$backupJarFile $deployPath/파일명.jar && echo "Backup file moved successfully."
            else
                echo "Backup file does not exist."
            fi'
        """, returnStatus: true

        sh script: """
            ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp 'chmod +x $deployPath/파일명.jar && echo "Execution permission set on original file."'
        """, returnStatus: true

        sh "ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp '$runAppCommand'"

        def attempts = 0
        def rollbackSuccess = false
        while (attempts < maxAttempts) {
            int result = sh script: "ssh -o StrictHostKeyChecking=no ubuntu@$targetServerIp '$checkLogCommand'", returnStatus: true
            if (result == 0) {
                echo "Rollback to previous version on $targetServerIp was successful."
                rollbackSuccess = true
                break
            }
            attempts++
            sleep sleepInterval
        }

        if (!rollbackSuccess) {
            error "Rollback to previous version on $targetServerIp failed."
        }
    }
}

 

위에서 app01과 app01-1 둘 다 롤백에 성공한 코드와 다른 코드이다.

이 코드는 app01이 롤백을 성공했다면 app01-1은 새 배포를 스킵하는 코드이다.

왜 이렇게 수정했냐면

app01이 새로 배포할 애플리케이션에 오류가 있어서 롤백을 하게 된 건데 굳이 잘 돌아가던 app01-1까지 롤백시킬 이유가 없기 때문이다.

오류가 있는 새 버전이라면 두번째로 다시 빌드될 app01-1에서는 건너뛰도록 바꿨다.