본문 바로가기

토이프로젝트/리뷰어(영화 리뷰 사이트)

백엔드 cicd 구축하기3

jenkins를 이용해서 cicd를 모두 구현했다.

처음 계획과 달라진 점이 있다면 처음에는 한 서버에서 jenkins를 통해 ci를 진행하고, 동시에 실행까지 하는게 목표였는데,

진행하다보니 몇가지 문제점이 있어서 jenkins 서버와 spring boot 서버를 나누기로 한 점이다.

일단 첫번째 이유로, 프리티어로 진행하다보니 jenkins와 spring boot를 동시에 실행하는게 불가능했다. 간단한 build에만 5분 넘는 시간이 소모되었고, 만약에 cicd가 진행되는 동안 api 요청까지 실행한다면 상당한 딜레이가 발생할 것이라 판단했다.

두번째 이유는 크롤링이다. 아직 서버에 올리는 단계는 아니지만, 나중에 스케줄링을 통해 실행할 계획을 가지고있기 때문에 한 서버에서 jenkins, springboot, 크롤링 세가지가 동시에 실행되는것 역시 불가능 하다는 판단이었다.

이렇게 된 김에 제대로 진행해보고자 spring boot서버와 jenkins 서버를 분리했다. 크롤링 서버 또한 분리하여 각 서버마다 역할을 수행하고 jenkins에서 전체적인 흐름을 관리하도록 구현할 생각이다.

현재까지 완료된 cicd 구성도이다.


중간중간 몇가지의 어려운 점이 있었다.

원격 서버로 파일 전달

jenkins 서버와 spring boot 서버를 분리했기 때문에

jenkins에서 테스트, 빌드 -> jar 파일 생성 -> spring boot 서버로 전달 과정이 필요했다.

파일 전달을 위해서는 도착 서버의 ssh 접속키가 필요했기 때문에 rsa키를 발급받고, 공개키 정보를 jenkins 서버에 입력해 주는 방식으로 파일 전송 권한을 부여했다.

권한과 함께 파일 전송을 위한 파이프라인을 작성해서 추가해주었다.

stage('SSH Transfer') {
            steps {
                sshPublisher(publishers: [
                    sshPublisherDesc(
                        configName: 'publisher', 
                        transfers: [
                            sshTransfer(
                                cleanRemote: false, 
                                excludes: '', 
                                execCommand: 'sh deploy.sh',
                                execTimeout: 120000, 
                                flatten: false, 
                                makeEmptyDirs: false, 
                                noDefaultExcludes: false, 
                                patternSeparator: '[, ]+', 
                                remoteDirectory: '/', 
                                remoteDirectorySDF: false, 
                                removePrefix: 'build/libs', 
                                sourceFiles: 'build/libs/*.jar'
                            )
                        ], 
                        usePromotionTimestamp: false, 
                        useWorkspaceInPromotion: false, 
                        verbose: false
                    )
                ])
            }
        }

스크립트는 윗 코드와 같다.

스크립트 실행 순서는 SSH Transfer stage 실행 차례에 build/libs 폴더에 있는 jar 파일을 지정한 서버로 전송한 후,

execCommand에 작성한 코드를 실행하는 것이다.

jenkins 서버에서 jar 파일의 실행까지 하기 위해서 deploy.sh 를 따로 작성해줬다.

#!/bin/bash
CURRENT_PID=$(ps -ef | grep java | grep reviewer | grep -v nohup | awk '{print $2}')

echo "$CURRENT_PID"

if [ -z ${CURRENT_PID} ] ;
then
        echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다."
else
        echo "> sudo kill -9 $CURRENT_PID"
        sudo kill -9 $CURRENT_PID
        sleep 3
fi

echo "> reviewer 배포"

JAR_PATH=$(ls -t /home/ec2-user/*.jar | head -1)

echo "> java_path : $JAR_PATH"

nohup java -jar $JAR_PATH > nohup.out &

작성한 쉘 스크립트이다. 대부분의 내용은 인터넷의 코드들을 참고했다.

실행중인 java jar파일이 있으면 해당 프로세스의 PID를 찾아 종료하고, 새롭게 다운받은 jar 파일을 실행하는 코드이다.

jenkins에서 작성한 파이프라인을 실행해봤는데, ssh publisher로 원격서버에서 접속까지는 됬지만 정상적으로 종료가 되지않았고 timeout이 되어서 종료되는 문제가 발생했다.

그렇기 때문에 로그를 보여주는 verbose 옵션을 true로 두고 실행해봤는데, 원격 서버에서 nohup 명령어로 실행이 정상적으로 됐지만

백그라운드 실행인 & 옵션이 정상적으로 되지 않는 모습으로 jenkins에서 로그가 늘어나는것을 확인했다.

이 문제를 해결하는 방법은 간단했다.

마지막 줄의 jar 파일 실행 명령어에 약간의 수정을 넣어주면 됐다.


#!/bin/bash
CURRENT_PID=$(ps -ef | grep java | grep reviewer | grep -v nohup | awk '{print $2}')

echo "$CURRENT_PID"

if [ -z ${CURRENT_PID} ] ;
then
        echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다."
else
        echo "> sudo kill -9 $CURRENT_PID"
        sudo kill -9 $CURRENT_PID
        sleep 3
fi

echo "> reviewer 배포"

JAR_PATH=$(ls -t /home/ec2-user/*.jar | head -1)

echo "> java_path : $JAR_PATH"

nohup java -jar $JAR_PATH > nohup.out 2>&1 &

마지막 줄에 nohup.out 2>&1 & 를 추가했다.

어떤 차이이길래 원하는 의도대로 실행됐는지 살펴봤다.

해당 명령어의 의미는 jar 파일을 실행했을 때 2>&1에 의해 표준에러가 표준출력으로 출력되며,
nohup.out 파일에 에러메시지가 저장되고, 해당 동작은 백그라운드로 실행한다는 의미이다.

linux에서는 파일 디스크립터를 할당하는데, (0:표준입력, 1:표준출력, 2:표준에러) 상태가 있다.

즉, 2>&1은 2를 &1로 리다이렉트 한다는 의미이고, 표준 에러를 표준 출력으로 내보내라는 뜻 이다.

이로 미루어봤을 때, 이 전 명령에서는 표준에러가 처리되는동안 프로세스를 대기하는 상태였던 것 같고, 표준에러를 표준출력으로 바꾸어 출력되는 모든 로그를 백그라운드 실행으로 전환할 수 있어 정상적으로 실행됐던 것으로 보인다.