Information Security Study

젠킨스 파이프라인으로 배포 시 로그 파일이 다른 위치에 생기는 문제 해결 본문

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

젠킨스 파이프라인으로 배포 시 로그 파일이 다른 위치에 생기는 문제 해결

gayeon_ 2024. 8. 9. 16:22

젠킨스 파이프라인으로 배포 시 로그 파일이 다른 위치에 생기는 문제 해결

 

 

젠킨스 파이프라인을 통해 배포 시 로그가 ~ 하위에 생기는 문제가 생겼다.

원래는 ~/log-tracking-app/build/custom-libs/logs에 생성되어야 한다.

logs 디렉토리를 생성한 적이 없는데 이 디렉토리가 생기면서 로그 파일이 쌓였다.

 

 

젠킨스 배포 말고 터미널에서 직접 git clone해서 생긴 jar를 실행하면 위와 같이 로그가 잘 생성된다.

 

 

 

멋대로 생성된 logs 디렉토리를 지우고 젠킨스로 다시 빌드하고나니 logs 디렉토리가 생긴 것을 확인했다.

이것으로 젠킨스 스크립트에 적은 배포 경로에 로그가 저장하는 것을 알 수 있었다.

(쌓인 jar는 모두 삭제했다.)

 

 

 

그래서 정확한 경로에 로깅될 수 있도록 젠킨스 스크립트에서 로그 저장 경로를 shell 스크립트에 적은 work_dir로 수정할 것이다.

 

 

log 저장 경로 수정한 코드 

pipeline {
    tools {
        gradle "GRADLE"
    }
    agent any
    stages {
        stage('Clone') {
            steps {
                git branch: 'main', url: 'git 레포지토리 주소'
            }
        }

        stage('Set Permissions') {
            steps {
                sh 'chmod +x ./gradlew'
            }
        }

        stage('Test') {
            steps {
                script {
                    sh './gradlew test'
                }
            }
        }
        
        stage('Build') {
            steps {
                script {
                sh './gradlew clean build'
                    def jarFilePattern = 'jar 경로'
                    def jarFile = sh(script: "ls $jarFilePattern | grep -v plain", returnStdout: true).trim()
                    echo "Built JAR file: $jarFile"
                    // Save the jarFile path to environment variable
                    env.JAR_FILE = jarFile
                }
            }
        }


        stage('Deploy') {
            steps {
                script {
                    def appServerIps = ['app ip', 'app2 ip']
                    def nginxIp = 'ip'

                    // Deploy on first server
                    deployOnServer(appServerIps[0], nginxIp)

                    // Copy JAR to the second server
                    copyJarToRemote(appServerIps[1], env.JAR_FILE)

                    // Deploy on second server
                    deployOnServer(appServerIps[1], nginxIp)
                }
            }
        }
    }
    post {
        success {
            echo "Build and deployment succeeded."
        }
        failure {
            echo "Build or deployment failed."
        }
    }
}

def copyJarToRemote(targetServerIp, jarFile) {
    def deployPath = '/home/ubuntu/프로젝트명/build/custom-libs'

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

def deployOnServer(ip, nginxIp) {
    def deployPath = '/home/ubuntu/프로젝트명/build/custom-libs'
    def logPath = "$deployPath/logs"
    def jarFilePattern = "파일명.jar"
    def runAppCommand = "nohup java -jar $deployPath/$jarFilePattern > $logPath/log.log 2>&1 &"
    def checkLogCommand = "grep -q 'Started' $logPath/log.log"
    def backupJarFile = '파일명-backup.jar'
    def maxAttempts = 10
    def sleepInterval = 5

    sshagent(['credential key 이름']) {
        echo "Starting deployment on $ip"

        updateLoadBalancer(ip, nginxIp, 'remove')

        // Create logs directory if it doesn't exist
        sh script: """
            ssh -o StrictHostKeyChecking=no ubuntu@$ip '
            mkdir -p $logPath
            '
        """, returnStatus: true

        // 1. Stop existing process and free port
        stopExistingProcess(ip)

        // 2. Create backup if not exists
        def backupCmd = """
            ssh -o StrictHostKeyChecking=no ubuntu@$ip '
            # Find the JAR file matching the pattern
            jarFile=\$(find $deployPath -name "$jarFilePattern" | head -n 1)
            if [ -n "\$jarFile" ]; then
                # Check if the backup file already exists
                if [ ! -f $deployPath/$backupJarFile ]; then
                    # If not, copy the found JAR file to the backup
                    cp "\$jarFile" "$deployPath/$backupJarFile"
                else
                    echo "Backup file already exists on $ip"
                fi
            else
                echo "No JAR file found to back up"
                exit 1
            fi
            '
        """
        def backupResult = sh(script: backupCmd, returnStatus: true)
        if (backupResult != 0) {
            error "Failed to create backup file on $ip"
        }

        // 3. Deploy new version
        def runCmd = "ssh -o StrictHostKeyChecking=no ubuntu@$ip '$runAppCommand'"
        def runResult = sh(script: runCmd, returnStatus: true)
        if (runResult != 0) {
            error "Failed to start application on $ip"
            rollbackToPreviousVersion(ip, backupJarFile, deployPath, runAppCommand, checkLogCommand)
        }

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

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

        // 5. Run CD test
        def cdTestResult = checkApplicationStatus(ip)
        echo "CD test result for $ip: '${cdTestResult}'"
        if (cdTestResult != "200") {
            error "CD test failed for $ip. Rolling back to previous version."
            rollbackToPreviousVersion(ip, backupJarFile, deployPath, runAppCommand, checkLogCommand)
        } else {
            echo "CD test passed for $ip."
        }

        // 6. Update load balancer with new server IP
        updateLoadBalancer(ip, nginxIp, 'add')

        // Wait before moving to the next server to ensure stability
        sleep 10
    }
}

def stopExistingProcess(ip) {
    // 현재 실행 중인 프로세스를 찾고 종료
    sshagent(['credential key 이름']) {
        sh script: """
            ssh -o StrictHostKeyChecking=no ubuntu@$ip '
            # Kill the process using the JAR file
            ps aux | grep 파일명.jar | grep -v grep | awk "{print \$2}" | xargs sudo kill -9 || true
            # Kill any process using port 8080
            sudo lsof -ti:8080 | xargs sudo kill -9 || true
            '
        """, returnStatus: true
    }
}

def checkApplicationStatus(ip) {
    def checkStatusCmd = "ssh -o StrictHostKeyChecking=no ubuntu@$ip 'curl -s -o /dev/null -w \"%{http_code}\" http://localhost:8080/products'"
    def statusCode = sh(script: checkStatusCmd, returnStdout: true).trim()
    return statusCode
}

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

    sshagent(['credential 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
                $runAppCommand
            else
                echo "Backup file not found on $targetServerIp"
                exit 1
            fi'
        """, returnStatus: true

        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."
        }
    }
}

def updateLoadBalancer(serverIp, nginxIp, action) {
    def nginxConfigPath = '/etc/nginx/nginx.conf'

    def serverCommand = action == 'remove' ?
        """
        ssh -o StrictHostKeyChecking=no ubuntu@$nginxIp 'bash -c "
# 서버 IP 제거
            sudo sed -i \\"/server $serverIp:8080/d\\" $nginxConfigPath && \\

# Nginx 구성을 테스트
            sudo nginx -t && \\

# Nginx를 재로드
            sudo systemctl reload nginx
        "'
        """ :
        """
        ssh -o StrictHostKeyChecking=no ubuntu@$nginxIp 'bash -c "
# 서버 IP 추가
            sudo sed -i \\"/upstream nginx {/a \\
            \\            server $serverIp:8080 weight=100 max_fails=3 fail_timeout=3s;\\" $nginxConfigPath && \\

# Nginx 구성을 테스트
            sudo nginx -t && \\

# Nginx를 재로드
            sudo systemctl reload nginx
        "'
        """

    def result = sh(script: serverCommand, returnStatus: true)
    if (result != 0) {
        error "Failed to ${action == 'remove' ? 'remove' : 'add'} $serverIp ${action == 'remove' ? 'from' : 'to'} load balancer"
    } else {
        echo "Successfully ${action == 'remove' ? 'removed' : 'added'} $serverIp ${action == 'remove' ? 'from' : 'to'} load balancer"
    }
}

 

그러나 젠킨스 스크립트에 저장 경로를 명시해도 ~/logs 디렉토리가 생긴다.

 

왜 직접 git clone 했을 때는 로깅이 경로에 맞게 잘 되는데 왜 젠킨스로 배포할 때는 안될까?

 

 

 

혹시 애플리케이션 내에 설정이 되어있나 싶어 application.properties를 확인했더니 로깅 관련 설정을 logging-control.xml에 작성한 것을 알 수 있었다.

 

 

 

해당 파일을 확인하니 logs 하위에 applicaton.log를 생성하도록 하는 부분이 있고 

logs는 애플리케이션이 실행된 디렉토리 내에 생기도록 설정되어 있었다.

 

그래서 로그가 /home/ubuntu/logs에 쌓이므로 관리할 수 있도록 shell 스크립트 내의 작업 경로들을 수정해주었다.

 

 

작업 경로 수정한 shell 스크립트(1차)

#!/bin/sh

# 1. 작업 경로 설정
bk_dir="/home/ubuntu/log_backups"
work_dir="/home/ubuntu/logs"
aggr_dir="/home/ubuntu/log_aggr"

# 작업 디렉토리 생성 (디렉토리가 없을 경우에만 생성)
mkdir -p "$bk_dir"
mkdir -p "$work_dir"
mkdir -p "$aggr_dir"

# 2. 작업 대상 날짜 설정
work_target_ymd=$(date +%F)
# 작업 대상 날짜 디렉토리 생성 (디렉토리가 없을 경우에만 생성)
mkdir -p "$work_dir"/"$work_target_ymd"

# 3. 작업 대상 로그 복사 및 압축 해제
# 로그 파일을 지정된 디렉토리로 복사
cp "$bk_dir"/logcontroller."$work_target_ymd"_* "$work_dir"/"$work_target_ymd"/

# 4. 날짜별 파일 집계
# 날짜별 파일명을 설정 (파일명: YYYYMMDD.log)
aggr_file="$aggr_dir"/$(date +%Y%m%d).log

# /log_backups 디렉토리의 모든 파일을 처리
for file in "$bk_dir"/*; do
    # 파일이 로그 파일인지 확인
    if [ -f "$file" ]; then
        # 파일의 내용을 날짜별 파일에 추가
        cat "$file" >> "$aggr_file"
    fi
done

 

 

로그파일과 날짜별 디렉터리도 생겼다.

 

 

그런데.. 무언가 잘못되었다.

log_backups에 application.log와 logcontroller.log가 생겨야 하는데 작업 디렉토리로 생각하고 만든 logs에 생긴다..

셸 스크립트 대로라면 로그 백업 디렉토리(스프링서버에서 직접 저장)에 있는 log.날짜 파일들이 작업 디렉토리인 logs 디렉토리로 이동해야 하는데

현재는 로그 파일이 바로 logs 디렉토리에 들어간다..

 

 

 

logging-control.xml에는 logs 하위에 applicaion.log가 생기게 작성되어있으므로

그래서 logs 파일을 작업이 아닌 backup 디렉토리로 취급하고 log_backups를 작업디렉토리(logs_work)로 변경할 것이다.

 

 

적절한 위치에 로그 파일이 저장되고 집계되도록 작업 경로 수정한 shell 스크립트(최종)

- 애플리케이션에서 로그가 바로 저장되는 경로: bk_dir

- 작업 경로: work_dir

- 집계 경로: aggr_dir

#!/bin/sh

# 1. 작업 경로 설정
bk_dir="/home/ubuntu/logs"
work_dir="/home/ubuntu/logs_work"
aggr_dir="/home/ubuntu/log_aggr"

# 작업 디렉토리 생성 (디렉토리가 없을 경우에만 생성)
mkdir -p "$bk_dir"
mkdir -p "$work_dir"
mkdir -p "$aggr_dir"

# 2. 작업 대상 날짜 설정
work_target_ymd=$(date +%F)
# 작업 대상 날짜 디렉토리 생성 (디렉토리가 없을 경우에만 생성)
mkdir -p "$work_dir"/"$work_target_ymd"

# 3. 작업 대상 로그 복사 및 압축 해제
# 로그 파일을 지정된 디렉토리로 복사
cp "$bk_dir"/logcontroller."$work_target_ymd"_* "$work_dir"/"$work_target_ymd"/

# 4. 날짜별 파일 집계
# 날짜별 파일명을 설정 (파일명: YYYYMMDD.log)
aggr_file="$aggr_dir"/$(date +%Y%m%d).log

# /log_backups 디렉토리의 모든 파일을 처리
for file in "$bk_dir"/*; do
    # 파일이 로그 파일인지 확인
    if [ -f "$file" ]; then
        # 파일의 내용을 날짜별 파일에 추가
        cat "$file" >> "$aggr_file"
    fi
done

 

백업 디렉토리인 logs에 로그파일이 잘 쌓이고

 

 

 

작업 디렉토리에는 날짜별로 하위 디렉토리가 생기며 로그 파일이 모인다.

 

 

 

그리고 마지막으로 집계 디렉토리에서는 날짜별로 로그 파일을 병합해서 하나의 파일로 생성한다.

 

성공!

 

 

롤링 배포 및 로깅 성공 최종 스크립트

pipeline {
    tools {
        gradle "GRADLE"
    }
    agent any
    stages {
        stage('Clone') {
            steps {
                git branch: 'main', url: 'git 레포지토리 주소'
            }
        }

        stage('Set Permissions') {
            steps {
                sh 'chmod +x ./gradlew'
            }
        }

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

        stage('Build') {
            steps {
                script {
                    sh './gradlew clean build'
                    def jarFilePattern = 'jar 경로'
                    def jarFile = sh(script: "ls $jarFilePattern | grep -v plain", returnStdout: true).trim()
                    echo "Built JAR file: $jarFile"
                    // Save the jarFile path to environment variable
                    env.JAR_FILE = jarFile
                }
            }
        }

        stage('Deploy') {
            steps {
                script {
                    def appServerIps = ['app ip', 'app2 ip']
                    def nginxIp = 'ip'

                    //copyJarToRemote(appServerIps[0], env.JAR_FILE)

                    // Deploy on first server
                    deployOnServer(appServerIps[0], nginxIp)

                    // Copy JAR to the second server
                    //copyJarToRemote(appServerIps[1], env.JAR_FILE)

                    // Deploy on second server
                    deployOnServer(appServerIps[1], nginxIp)
                }
            }
        }
    }
    post {
        success {
            echo "Build and deployment succeeded."
        }
        failure {
            echo "Build or deployment failed."
        }
    }
}

def copyJarToRemote(targetServerIp, jarFile) {
    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) {
            error "Failed to copy jar file to $targetServerIp"
        } else {
            echo "Successfully copied jar file to $targetServerIp"
        }
    }
}

def deployOnServer(ip, nginxIp) {
    def deployPath = '/home/ubuntu'
    def jarFilePattern = "파일명.jar"
    def runAppCommand = "nohup java -jar $deployPath/$jarFilePattern > $deployPath/log.log 2>&1 &"
    def checkLogCommand = "grep -q 'Started' $deployPath/log.log"
    def backupJarFile = '파일명-backup.jar'
    def maxAttempts = 10
    def sleepInterval = 5

    sshagent(['credential key 이름']) {
        echo "Starting deployment on $ip"
        
        updateLoadBalancer(ip, nginxIp, 'remove')

        // 1. Stop existing process and free port
        stopExistingProcess(ip)
        
        def deleteOldJarsCmd = """
            ssh -o StrictHostKeyChecking=no ubuntu@$ip '
            # Delete all JAR files matching the pattern
            rm -f $deployPath/$jarFilePattern
            '
        """
        def deleteResult = sh(script: deleteOldJarsCmd, returnStatus: true)
        if (deleteResult != 0) {
            error "Failed to delete old JAR files on $ip"
        } else {
            echo "Old JAR files deleted on $ip"
        }
        
        copyJarToRemote(ip, env.JAR_FILE)
        

        // 2. Create backup if not exists
        def backupCmd = """
            ssh -o StrictHostKeyChecking=no ubuntu@$ip '
            # Find the JAR file matching the pattern
            jarFile=\$(find $deployPath -name "$jarFilePattern" | head -n 1)
            if [ -n "\$jarFile" ]; then
                # Check if the backup file already exists
                if [ ! -f $deployPath/$backupJarFile ]; then
                    # If not, copy the found JAR file to the backup
                    cp "\$jarFile" "$deployPath/$backupJarFile"
                    echo "Backup created: $deployPath/$backupJarFile"
                else
                    echo "Backup file already exists on $ip"
                fi
            else
                echo "No JAR file found to back up"
            fi
            '
        """
        def backupResult = sh(script: backupCmd, returnStatus: true)
        if (backupResult != 0) {
            error "Failed to create backup file on $ip"
        }

        // 3. Deploy new version
        def runCmd = "ssh -o StrictHostKeyChecking=no ubuntu@$ip '$runAppCommand'"
        def runResult = sh(script: runCmd, returnStatus: true)
        if (runResult != 0) {
            error "Failed to start application on $ip"
            rollbackToPreviousVersion(ip, backupJarFile, deployPath, runAppCommand, checkLogCommand)
        }

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

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

        // 5. Run CD test
        def cdTestResult = checkApplicationStatus(ip)
        echo "CD test result for $ip: '${cdTestResult}'"
        if (cdTestResult != "200") {
            error "CD test failed for $ip. Rolling back to previous version."
            rollbackToPreviousVersion(ip, backupJarFile, deployPath, runAppCommand, checkLogCommand)
        } else {
            echo "CD test passed for $ip."
        }

        // 6. Update load balancer with new server IP
        updateLoadBalancer(ip, nginxIp, 'add')

        // Wait before moving to the next server to ensure stability
        sleep 10
    }
}


def stopExistingProcess(ip) {
    // 현재 실행 중인 프로세스를 찾고 종료
    sshagent(['credential key 이름']) {
        sh script: """
            ssh -o StrictHostKeyChecking=no ubuntu@$ip '
            # Kill the process using the JAR file
            ps aux | grep 파일명.jar | grep -v grep | awk "{print \$2}" | xargs sudo kill -9 || true
            # Kill any process using port 8080
            sudo lsof -ti:8080 | xargs sudo kill -9 || true
            '
        """, returnStatus: true
    }
}

def checkApplicationStatus(ip) {
    def checkStatusCmd = "ssh -o StrictHostKeyChecking=no ubuntu@$ip 'curl -s -o /dev/null -w \"%{http_code}\" http://localhost:8080/products'"
    def statusCode = sh(script: checkStatusCmd, returnStdout: true).trim()
    return statusCode
}

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

    sshagent(['credential 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
                $runAppCommand
            else
                echo "Backup file not found on $targetServerIp"
                exit 1
            fi'
        """, returnStatus: true

        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."
        }
    }
}

def updateLoadBalancer(serverIp, nginxIp, action) {
    def nginxConfigPath = '/etc/nginx/nginx.conf'

    def serverCommand = action == 'remove' ?
        """
        ssh -o StrictHostKeyChecking=no ubuntu@$nginxIp 'bash -c "
# 서버 IP 제거
            sudo sed -i \\"/server $serverIp:8080/d\\" $nginxConfigPath && \\

# Nginx 구성을 테스트
            sudo nginx -t && \\

# Nginx를 재로드
            sudo systemctl reload nginx
        "'
        """ :
        """
        ssh -o StrictHostKeyChecking=no ubuntu@$nginxIp 'bash -c "
# 서버 IP 추가
            sudo sed -i \\"/upstream nginx {/a \\
            \\            server $serverIp:8080 weight=100 max_fails=3 fail_timeout=3s;\\" $nginxConfigPath && \\

# Nginx 구성을 테스트
            sudo nginx -t && \\

# Nginx를 재로드
            sudo systemctl reload nginx
        "'
        """

    def result = sh(script: serverCommand, returnStatus: true)
    if (result != 0) {
        error "Failed to ${action == 'remove' ? 'remove' : 'add'} $serverIp ${action == 'remove' ? 'from' : 'to'} load balancer"
    } else {
        echo "Successfully ${action == 'remove' ? 'removed' : 'added'} $serverIp ${action == 'remove' ? 'from' : 'to'} load balancer"
    }
}

 

shell 스크립트에 작성한 로그 디렉토리에 맞춰 배포 경로도 /home/ubuntu로 수정했다.

 

 

로드밸런싱도 잘 되고 있다.