Information Security Study

젠킨스 파이프라인으로 애플리케이션 자원 사용량 자동으로 로깅하기 본문

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

젠킨스 파이프라인으로 애플리케이션 자원 사용량 자동으로 로깅하기

gayeon_ 2024. 7. 24. 17:58

젠킨스 파이프라인으로 애플리케이션 자원 사용량 자동으로 로깅하기

 

해야 할 것

 

1) 깃허브 레포지토리 생성해서 자원 사용량 기록하는 셸 스크립트를 작성한다.

2) 젠킨스 파이프라인에서 app01, app01-1에게 셸 스크립트 전송한다.

3) 각 app은 셸 스크립트를 실행하고 cpu, 메모리, 디스크 사용량 로깅 및 로그로테이션하고 취합 서버인 log로 전송한다.

4) 로그 서버는 이메일로 로그를 보낸다.

 

app01, app01-1에서 로그로테이트 설정파일을 작성해야 한다.

 

 

이메일로 로그파일 보내는 이유

: 그라파나 같은 모니터링 툴로 볼 수 없는 정보들도 있기 때문이다.

 

 

젠킨스에서 주기적 트리거 설정하기

젠킨스 대시보드에서 파이프라인 작업을 클릭해 구성에 들어가 빌드 주기 섹션에서 "Build periodically" 옵션을 선택한다.

이 옵션은 주기적으로 빌드를 트리거하도록 설정한다.

 

매 분마다 자동으로 빌드되고 싶다면

* * * * *

를 입력하면 된다.

 

 

매 분마다 빌드

 

 

 

 

로그 취합 서버 설정

 

젠킨스와 같은 가용영역으로 설정한다.

 

 

 

젠킨스와 같은 vpc, 서브넷이어야 한다!!!

 

 

 

나중에 app01과 app01-1이 로그를 보내줄 것이기 때문에 보안 그룹은 22번만 열어두었다.

 

 

 

접속 완료!

 

 

 

각 애플리케이션 서버의 로테이트 설정에 들어가 아래와 같이 수정한다.

 

 

/var/log/resource_usage.log {
    daily
    rotate 7
    compress
    missingok
    notifempty
    create 0640 root utmp
    postrotate
        systemctl reload rsyslog > /dev/null 2>/dev/null || true
    endscript
}

 

 

 

테스트 모드로 실행

 

 

 

설정파일의 권한은 위와 같이 변경한다.

 

 

 

그리고 패스워드 없이 접근할 수 있도록 visudo에서 설정해주었다.

 

 

 

권한 에러가 떴다.

 

 

 

경로에 권한을 변경하고

 

 

 

적용 되었는지 확인했다.

 

 

 

이번에도 오류가 나서 확인해보니

 

 

 

로그 파일을 안 만들어뒀다.

 

 

 

그래서 로그파일을 생성하고 소유자도 변경했다.

 

 

 

그런데도 해결이 안돼서 곰곰이 생각해보니

젠킨스 rsa 공개키를 로그서버에 저장하지 않았었다..

 

 

 

젠킨스의 공개키를 복사해서

 

 

 

로그서버의 authorized_keys에 저장했다.

 

 

 

그리고 app01, app01-1 인스턴스에서도 확인해보니 디렉토리가 없어서 생성 후 권한 변경을 했다.

 

 

pipeline {
    agent any
    
    environment {
        LOG_FILE = "/var/log/resource_usage.log"
        ROTATED_LOG_FILE = "/var/log/resource_usage.log.1.gz"
        REMOTE_USER = "ubuntu"
        REMOTE_HOST_APP1 = "ip"
        REMOTE_HOST_APP2 = "ip"
        REMOTE_DIR = "/var/logs/resource_usage"
        GIT_REPO = "레포지토리명"
        SSH_CREDENTIALS_ID = "credential key" // Jenkins에서 등록한 SSH 자격 증명 ID
    }
    
    stages {
        stage('Clone Scripts') {
            steps {
                // GitHub 저장소에서 스크립트를 클론합니다.
                git url: env.GIT_REPO, branch: 'main'
            }
        }
        
        stage('Prepare Scripts') {
            steps {
                // 클론한 스크립트에 실행 권한을 부여합니다.
                sh 'chmod +x resource_monitor.sh'
            }
        }
        
        stage('Collect Resource Usage') {
            steps {
                // 클론한 스크립트를 실행합니다.
                sh 'bash resource_monitor.sh'
            }
        }
        
        stage('Remote Log Rotation') {
            steps {
                sshagent(credentials: [env.SSH_CREDENTIALS_ID]) {
                    script {
                        def remoteHosts = [env.REMOTE_HOST_APP1, env.REMOTE_HOST_APP2]
                        for (remoteHost in remoteHosts) {
                            sh """
                            echo "Performing log rotation on ${remoteHost}..."
                            ssh ${REMOTE_USER}@${remoteHost} '
                                if [ -f /etc/logrotate.d/resource_usage ]; then 
                                    sudo /usr/sbin/logrotate -f /etc/logrotate.d/resource_usage > /tmp/logrotate.log 2>&1
                                    sudo cat /tmp/logrotate.log || echo "Failed to read logrotate log"
                                else 
                                    echo "Logrotate configuration file not found." 
                                    exit 1 
                                fi'
                            """
                        }
                    }
                }
            }
        }
        
        stage('Print and Send Logs') {
            steps {
                sshagent(credentials: [env.SSH_CREDENTIALS_ID]) {
                    script {
                        def localHostname = sh(script: 'hostname', returnStdout: true).trim()
                        sh """
                        echo "Sending log file to remote server..."
                        rsync -avz $LOG_FILE ${REMOTE_USER}@${REMOTE_HOST_APP1}:$REMOTE_DIR/${localHostname}-resource_usage.log
                        """

                        
                        // 로테이션된 로그 파일을 콘솔에 출력합니다.
                        sh '''
                        if [ -f $ROTATED_LOG_FILE ]; then
                            echo "Rotated log file content:"
                            gzip -d -c $ROTATED_LOG_FILE
                        else
                            echo "No rotated log file found."
                        fi
                        '''
                    }
                }
            }
        }
    }
    
    post {
        always {
            echo 'Pipeline completed.'
        }
    }
}

 

위 스크립트로 success가 떴지만

 

 

 

기록이 안돼서 깃 클론 후 셸 스크립트가 실행이 안되는 건가 싶어

직접 인스턴스에 test.sh을 만들어 실행이 되나 봤다.

 

 

 

직접 파워셸에 셸 스크립트 작성하고 실행권한 부여 후 실행하면

로그파일에 잘 저장되는데... 젠킨스로 하면 안된다.

 

 

 

그래서 여러 경로를 모두 찾아보니 app이 아닌 젠킨스가 젠킨스의 자원 사용량을 로깅하고 있었다..

 

app01, app01-1의 자원 사용량을 로깅할 수 있도록 수정할 것이다.

 

 

 

우선 app01, app01-1의 jar 파일을 실행시켜 서비스가 돌아가도록 했다.

그리고 grep으로 프로세스의 pid를 출력했다.

 

그리고 app01, app01-1이 로그서버에게 전송해야 하니

각 애플리케이션 인스턴스에서 rsa 키를 발급받고 각각의 공개키를 로그서버의 authorized_keys 파일에 저장해야 한다.

 

 

 

두 애플리케이션 서버의 공개키를 로그 서버의 authorized_keys에 저장 후

 

 

 

셸을 실행하면

 

 

 

전송에 성공했다고 뜬다.

 

 

 

실제로 로그 서버에 들어가보면 로그 파일을 볼 수 있다.

 

이제 젠킨스를 통해서도 로그가 전송이 되는지 확인할 것이다.

그 전에 로그서버에서 어떤 애플리케이션의 로그인지 쉽게 구분하기 위해 

애플리케이션의 name별로(app01, app01-1) 디렉토리를 생성하고

로그 파일명에도 name을 표시하도록 수정했다.

 

 

 

젠킨스에서도 success가 떴고

 

 

 

각 애플리케이션 서버에서 로깅이 잘 되고 있는 것을 확인할 수 있었다.

 

 

 

로그서버에서도 app01, app01-1 디렉토리에 전송이 잘 되었다.

 

 

셸 스크립트

#!/bin/bash

# 홈 디렉토리 아래의 로그 파일
USER_LOG_DIR="/home/ubuntu/logs"
LOG_FILE="${USER_LOG_DIR}/resource_usage_${HOSTNAME}.log"
LOG_ROTATE_CONFIG="/home/ubuntu/logrotate.conf"

# 원격 서버 설정
REMOTE_USER="ubuntu"
REMOTE_HOST="ip"
REMOTE_PATH="/home/ubuntu/logs-1"

# 현재 날짜 및 시간
timestamp=$(date '+%Y-%m-%d %H:%M:%S')

# 애플리케이션 프로세스 ID 찾기 (JAR 파일 기준)
app_pid=$(ps -ef | grep '[j]ava -jar 파일명.jar' | awk '{print $2}' | head -n 1)

if [[ -n "$app_pid" && "$app_pid" =~ ^[0-9]+$ ]]; then
    # app의 CPU 및 메모리 사용량
    cpu_usage=$(ps -p $app_pid -o %cpu=)
    mem_usage=$(ps -p $app_pid -o %mem=)
else
    cpu_usage="N/A"
    mem_usage="N/A"
fi

# 사용자 디렉토리 아래의 로그 디렉토리 생성
mkdir -p $USER_LOG_DIR

# 로그 파일에 기록
echo "$timestamp, app CPU: $cpu_usage%, app Memory: $mem_usage%" >> $LOG_FILE

# 사용자 홈 디렉토리에 로그 로테이션 설정 파일 생성
cat <<EOF > $LOG_ROTATE_CONFIG
$USER_LOG_DIR/resource_usage_*.log {
    daily
    rotate 7
    compress
    missingok
    notifempty
    create 0644 ubuntu ubuntu
}
EOF

# 로그 로테이션 수행
logrotate -f $LOG_ROTATE_CONFIG

# 로테이션된 로그 파일을 원격 서버로 전송
# 새로 생성된 로그 파일을 전송
scp $LOG_FILE ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/${HOSTNAME}/

# 로테이션된 로그 파일을 전송
for log_file in $(find $USER_LOG_DIR -name "resource_usage_${HOSTNAME}.log.*" ! -name "resource_usage_${HOSTNAME}.log"); do
    scp $log_file ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/${HOSTNAME}/
done

 

 

파이프라인 스크립트

pipeline {
    agent any

    environment {
        SSH_KEY_ID = 'credential key'
        REPO_URL = '레포지토리명'
        SCRIPT_NAME = 'resource_monitor.sh'
    }

    stages {
        stage('Clone and Deploy Script') {
            steps {
                script {
                    def servers = [
                        [name: 'app01', host: 'ip', user: 'ubuntu'],
                        [name: 'app01-1', host: 'ip', user: 'ubuntu']
                    ]

                    sshagent([SSH_KEY_ID]) {
                        servers.each { server ->
                            sh """
                                ssh -o StrictHostKeyChecking=no ${server.user}@${server.host} '
                                    export HOSTNAME=${server.name} &&
                                    rm -rf /tmp/repository &&
                                    git clone ${REPO_URL} /tmp/repository || echo "Repository already exists" &&
                                    cd /tmp/repository &&
                                    chmod +x ${SCRIPT_NAME} &&
                                    ./${SCRIPT_NAME}
                                '
                            """
                        }
                    }
                }
            }
        }
    }

    post {
        always {
            echo 'Pipeline completed.'
        }
    }
}

 

 

다음에는 로그 서버에서 메일로 보내는 기능을 구현할 것이다.