본문 바로가기

🧑🏻‍💻 Dev/Infra

[Jenkins] ERROR: script returned exit code 255

이번에 시작한 프로젝트에서 Jenkins를 사용해서 CI/CD 파이프라인을 구성하고 있습니다.

develop 브랜치에서 작업을 하고, push를 하면 Jenkins Pipeline이 동작하면서 자동화 배포 작업이 진행됩니다.

 

우리 프로젝트에서는 다음과 같은 Pipeline이 동작해서 배포를 진행합니다.

  1. Git Clone
    • Github Repository의 develop 브랜치에 변화가 발생되면 Jenkins 동작이 시작됩니다.
    • develop 브랜치를 Jenkins 서버로 clone 해서 가져옵니다.
  2. DEV-Build
    • 이 과정에서 프로젝트의 Build와 Test를 진행합니다.
  3. Deploy
    • Jenkins 서버에서 Develop 서버로 SSH 통신을 통해 배포 스크립트를 실행합니다.
    • 이 글에서는 이 "Deploy" 과정에서 스크립트 실행 후 발생한 "ERROR: script returned exit code 255"에 대해서 다룰 예정입니다.

 

1. 기존 스크립트


기존 스크립트는 다음과 같습니다.

#!/bin/bash
source ~/.bashrc

REPOSITORY=/home/praise-push
echo "### check running application... ###"
CURRENT_PID=$(pgrep -f praise-push)
echo "### current pid = $CURRENT_PID"

if [[ -z ${CURRENT_PID} ]]; then
    echo "### Not found running application ###"
else
    echo "### Found application!! try to kill process $CURRENT_PID ###"
    kill -15 $CURRENT_PID
    sleep 3
fi
echo "### Ready to deploy ###"

JAR_NAME=$(ls $REPOSITORY | grep 'praise-push' | tail -n 1)

echo "### Source file Name: $JAR_NAME"
chmod +x $REPOSITORY/$JAR_NAME
nohup /usr/bin/java -jar $REPOSITORY/$JAR_NAME >> $REPOSITORY/nohup.out 2>&1 &
echo "success"

 

[ 실행 중인 프로세스가 있는지 확인 ]

간단하게 설명하면 현재 실행 중인 java application의 PID를 찾아서 CURRENT_PID에 저장합니다.

현재 실행중인 프로세스가 있다면 kill 명령어를 통해 중지시키고, 없다면 그냥 넘어가도록 합니다.

 

[ 새로운 프로세스를 실행합니다. ]

Jenkins 서버에서 Build, Test 과정이 끝난 jar 파일을 Develop 서버로 복사해서 가져오게 됩니다.

이 jar 파일을 nohup을 이용해서 서버를 실행합니다.

 

Jenkins에서 Deploy Pipeline은 다음과 같습니다.

stage('Deploy') {
    steps {
        sshagent(credentials: ['dev_server']) {
            sh "ssh -o StrictHostKeyChecking=no -p 9001 root@123.11.11.11 'uptime'"
            sh "scp -P 9001 /var/jenkins_home/workspace/praise-push-dev/build/libs/praise-push-0.0.1-SNAPSHOT.jar root@123.11.11.11:/home/praise-push"
            sh "ssh -t -p 9001 root@123.11.11.11 'cd /home/praise-push && ./deploy.sh'"
        }
    }
}

 

ssh으로 uptime을 요청하여 첫 SSH 연결을 진행합니다.

scp를 이용하여 Jenkins 서버에서 Build, Test 과정을 마치고 생성된 jar 파일을 Develop 서버로 복사합니다.

ssh를 이용하여 원격으로 "/home/praise-push/deploy.sh" 스크립트를 실행하여 배포를 작업을 완료합니다.

 

 

 

2. 문제 분석


먼저 script에서 어떻게 해야 255 exit code가 발생되는지 찾아봤습니다.

 

Remote(원격) 서버에 SSH가 설치되어 있지 않거나, 올바른 Connection이 아닌 경우

 

처음에는 SSH 연결이 잘 안 되고 있구나라고 판단하여 이쪽으로 찾아보기 시작했었습니다.

 

클라이언트(Jenkins)에서 Key Pair를 생성하여 공개키(pub)를 원격서버(Develop)로 보내서 서버의 "authrized_keys"에 클라이언트의 공개키를 저장해야 합니다.

 

SSH 요청 시 클라이언트와 서버 사이의 세션키(대칭키) 생성을 위한 키 교환이 시작되는데, 이때 "authrized_keys"에 있는 이 공개키가 사용됩니다.

 

그래서 저는 SSH 연결이 잘 못 된 것은 아닌지 Jenkins 서버와 Develop 서버를 다시 연결을 해봤습니다.

 

그래도 결과는 달라지지 않았습니다. 다시 한번 확인해 보니 위 스크립트에서 이 부분 이전까지는 스크립트가 실행되는 것을 확인했습니다.

echo "### Ready to deploy ###"

 

이 echo 명령어전까지는 실행이 되었다는 것은 Jenkins -> Develop의 SSH 연결에서 발생한 문제는 아니라는 것을 의미합니다.

 

정상적으로 SSH 연결을 통해 "/home/praise-push/deploy.sh" 명령까지 들어와서 실행을 했지만, 어떤 이유 때문에 중간에 script가 비정상적으로 종료되어 script가 exit 255 code를 리턴했던 것이라는 판단을 하게 되었습니다.

 

즉, 문제는 deploy.sh 스크립트에 있다!

 

deploy.sh를 하나씩 실행하며 디버깅을 해봤습니다.

 

1. 첫 번째 시도

#!/bin/bash
source ~/.bashrc
REPOSITORY=/home/praise-push

 

당연히 문제는 없겠지만, 이틀 넘게 SSH가 원인인 줄 알고 삽질을 해서 아주 꼼꼼하게... 진행해 보기 위해서 스크립트의 시작 부분부터 진행했습니다.

 

결과는 당연히 성공입니다!

 

 

2. 두 번째 시도

#!/bin/bash
source ~/.bashrc
REPOSITORY=/home/praise-push
echo "### check running application... ###"
CURRENT_PID=$(pgrep -f praise-push)
echo "### current pid = $CURRENT_PID"

 

이 부분도 별 다른 특별한 명령어는 없었기 때문에 성공했습니다!

 

 

3. 세 번째 시도

#!/bin/bash
source ~/.bashrc
REPOSITORY=/home/praise-push
echo "### check running application... ###"
CURRENT_PID=$(pgrep -f praise-push)
echo "### current pid = $CURRENT_PID"

if [[ -z ${CURRENT_PID} ]]; then
    echo "### Not found running application ###"
else
    echo "### Found application!! try to kill process $CURRENT_PID ###"
    kill -15 $CURRENT_PID
    sleep 3
fi

 

예상했던대로 세 번째 시도에서 실패했습니다. 그럼 이 if 블록 내부에 문제가 있다는 것을 의미합니다.

문법적인 오류는 아닌 것으로 보였고, 의심이 갔던 부분은 "kill -15 $CURRENT_PID" 부분이었습니다.

 

 

 

4. 네 번째 시도

#!/bin/bash
source ~/.bashrc
REPOSITORY=/home/praise-push
echo "### check running application... ###"
CURRENT_PID=$(pgrep -f praise-push)
echo "### current pid = $CURRENT_PID"

if [[ -z ${CURRENT_PID} ]]; then
    echo "### Not found running application ###"
else
    echo "### Found application!! try to kill process $CURRENT_PID ###"
    sleep 3
fi

 

의심이 들었던 kill 명령어를 제거한 후 다시 시도해봤는데, Deploy Pipeline이 성공했습니다.

역시 원인은 이 부분이었습니다.

 

 

 

3. 원인 분석


kill 명령어로 CURRENT_PID를 종료하고 있었기 때문에 Deploy Pipeline을 실행시켜 두고, Develop 서버에서 계속 "ps -ef | grep praise-push"을 실행하며 콘솔을 확인해 봤습니다.

root@praise-push-dev:/home/praise-push# ps -ef | grep praise-push
root     22623     1  3 19:01 pts/0    00:00:16 /usr/bin/java -jar /home/praise-push/praise-push-0.0.1-SNAPSHOT.jar
root     23741 16549  0 19:09 pts/0    00:00:00 grep --color=auto praise-push
root@praise-push-dev:/home/praise-push# ps -ef | grep praise-push
root     22623     1  3 19:01 pts/0    00:00:16 /usr/bin/java -jar /home/praise-push/praise-push-0.0.1-SNAPSHOT.jar
root     23789 23720  0 19:09 ?        00:00:00 bash -c                              cd /home/praise-push                             ./deploy.sh
root     23794 16549  0 19:09 pts/0    00:00:00 grep --color=auto praise-push

 

이상한 점을 확인했습니다. Deploy Pipeline이 실행되는 중간에 "praise-push"가 포함된 새로운 프로세스가 하나 실행되는 것을 보았습니다.

root     23789 23720  0 19:09 ?        00:00:00 bash -c                              cd /home/praise-push                             ./deploy.sh

 

살펴보니 Deploy Pipeline에서 실행했던 "cd /home/priase-push" 명령어에서 시작되었던 프로세스가 kill의 대상인 "pgrep -f praise-push"에 발견되어 계속 종료되고 있었습니다.

 

찾은 원인은 이렇습니다.

 

  1. Jenkins에서 SSH를 통해 Develop 서버의 cd /home/praise-push/deploy.sh를 실행 (PID 1)
  2. deploy.sh 스크립트에서 현재 실행 중인 praise-push-0.0.1.jar(PID 2)를 찾기 위해서 "pgrep -f praise-push"을 실행
  3. "pgrep -f praise-push"의 결과를 CURRENT_PID 변수에 저장합니다.
    • 여기서 CURRENT_PID에는 의도대로라면 (PID 2)만 있어야 합니다.
    • 하지만, 결과에는 (PID 1)(PID 2)가 들어가게 됩니다.
    • 이유는 "cd /home/praise-push/deploy.sh"에 praise-push가 포함되어 탐색 대상이 되었던 것입니다.
  4. "kill -15 $CURRENT_PID"에서 (PID 1) (PID 2)를 모두 죽이게 됩니다.
  5. 여기서 deploy.sh의 비정상적인 종료가 되고, exit code 255를 리턴하게 됩니다.

 

 

 

4. 해결


"pgrep -f praise-push" 스크립트를 통해 찾으려는 대상은 "praise-push-0.0.1.jar"인 jar 프로세스이기 때문에 아래와 같이 변경했습니다.

CURRENT_PID=$(pgrep -f praise-push-*.jar)

 

이 부분만 이렇게 수정한 후 Jenkins CI/CD Pipeline을 실행시켜 보니 모든 stage가 통과되었고, 정상적으로 배포에 성공했습니다!

 

 

 

생각보다 굉장히 사소한 실수로 시작된 문제인데, 꽤나 오랜 시간 잡고 있었어서 기록해 봤습니다.