Information Security Study
젠킨스 파이프라인으로 배포 시 로그 파일이 다른 위치에 생기는 문제 해결 본문
젠킨스 파이프라인으로 배포 시 로그 파일이 다른 위치에 생기는 문제 해결
젠킨스 파이프라인을 통해 배포 시 로그가 ~ 하위에 생기는 문제가 생겼다.
원래는 ~/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로 수정했다.
로드밸런싱도 잘 되고 있다.
'네트워크 캠퍼스 > 3차 프로젝트' 카테고리의 다른 글
Infile Load 방식으로 병합된 로그 MySQL에 저장하기 (0) | 2024.08.12 |
---|---|
반복문으로 파일 내에 있는 모든 로그를 DB에 저장하기(시도중) (0) | 2024.08.12 |
롤링 방식으로 무중단 배포하기, 젠킨스-nginx 설정파일 수정을 위한 공개키 복사 (0) | 2024.08.09 |
일반 배포 방식으로 앱 서버 두 개에 배포하기 (0) | 2024.08.09 |
서버 내 로그 관리 정책 수립 및 구현 (0) | 2024.08.07 |