Information Security Study
롤백 기능 구현하기 본문
롤백 기능 구현하기
/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에서는 건너뛰도록 바꿨다.
'네트워크 캠퍼스 > 2차 프로젝트' 카테고리의 다른 글
blue/green 방식 무중단배포 구현하기 (1) | 2024.07.23 |
---|---|
롤링방식 무중단배포 구현하기 (1) | 2024.07.23 |
기본에서 새 vpc로 변경하기 (0) | 2024.07.23 |
240718 private 인스턴스 bastion로 접속, db 설정, 접속 성공, 아틸러리 테스트(부하 분산 확인), 롤백 확인, 보안그룹 분리, 무중단 배포 (0) | 2024.07.18 |
보안그룹 분리하기 (0) | 2024.07.18 |