[CI/CD] Jenkins를 활용한 CI/CD 구축하기-(4) 배포 파트

젤리젤링텀·2025년 10월 15일

CI/CD

목록 보기
6/7
post-thumbnail

저번 포스트에서 CI까지 구현을 성공하였다.
이번 포스트에서는 CD 부분을 구현해보도록 하자.

배포

먼저, Jenkins Pipeline의 Deploy 스텝을 살펴보자

pipeline {
    agent any

    stages {
        
        ...
        
        
        stage('Deploy') {
            steps {
                script {
                    sshagent(['deploy_ssh_key']) {
                        sh 'scp build/libs/*.jar ubuntu@[server instance public ip]:/home/ubuntu/project/'
                        sh 'ssh -tt ubuntu@[server instance public ip] sh ./deploy.sh'
                    }
                }
            }
        }
    }
    
    post {
        success {
            echo 'Deployment was successful.'
        }
        failure {
            echo 'Deployment failed.'
        }
    }
}

CD 과정에서는, 서버가 동작할 EC2 인스턴스에 CI 과정을 통해서 빌드된 jar 파일을 전송하고, EC2 인스턴스에서 jar 파일을 실행하여 서버를 띄우는 동작을 수행한다.

위 스크립트에서는 jar 파일을 EC2 인스턴스에 전송하는 것 까지 수행하며, deploy.sh 파일을 실행하는 것을 통해 서버를 띄우는 동작을 수행한다.

이 과정에서 사용되는 것은 SSH 프로토콜이다.

SSH 프로토콜?

SSH(Secure Shell)네트워크를 통해 데이터를 안전하게 받기 위한 프로토콜이다.
SSH는 데이터를 암호화하여 전송함으로써 네트워크 상에서의 도청이나 데이터 조작을 방지하고, 원격 서버(EC2 등)에 안전하게 접속할 수 있는 방법을 제공한다.

공개 키와 비공개 키를 이용하여 인증과 통신의 과정을 연결한다.

이를 통해 사용자와 서버 간의 통신을 보호하며, 주로 원격 시스템에 로그인이나 명령어 실행, 파일 전송 등에 사용된다.

네트워크가 안전하지 않다?

네트워크를 통해 데이터를 주고받을 때 안전하지 않다는 것은, 누구나 중간에 데이터를 훔쳐봤을 때 비밀번호, 주민등록번호 같은 민감한 정보를 볼 수 있다는 의미이다.

FTP나 Telnet 같은 다른 컴퓨터와 통신을 위해 사용되는 프로토콜을 사용하면, 네트워크로 정보가 그대로 넘어가기 때문에, 누구나 해당 정보를 볼 수 있다.

이때, SSH를 사용하면, 보안적으로 안전한 채널을 구성한 뒤 정보를 교환하기 때문에 훨씬 안전한 네트워크 통신이 가능하다.

배포 설정

1. 서버 EC2 인스턴스 생성

서비스의 규모가 크다면, 여러 개의 서버를 둬야할 것이고, 그렇게되면 Jenkins가 있는 인스턴스가 아닌 다른 EC2 인스턴스에서 띄워야 한다.

Jenkins EC2 인스턴스를 만들었을 때와 똑같이 만들어준다.
보안 그룹도 똑같이 설정해주면 된다.
지금까지 만들었던 인스턴스를 Jenkins 인스턴스라고 칭하고, 방금 만든 배포를 위한 인스턴스는 서버 인스턴스라고 칭하도록 하겠다.

2. SSH 통신 설정

SSH 통신을 위해서는 공개 키-개인 키로 대표되는 비대칭 키가 필요하다.
Git Bash를 통해서 Jenkins 서버에 접속하자.

Jenkins는 Docker 컨테이너 안에서 돌아가고 있으므로, Jenkins 컨테이너에 들어가야 할 필요가 있다. 그 안에서 비대칭 키를 생성할 것이다.

먼저, Docker에 있는 Jenkins 컨테이너를 실행시킨 뒤, 아래 명령어를 입력하자.

sudo docker exec -it jenkins bash

위 명령어를 통해서 Jenkins 컨테이너의 Bash에 접속할 수 있다.

아래 명령어를 입력해서 SSH key를 만들자

ssh-keygen -t rsa -b 4096

위 명령어를 사용하면, 4096 길이의 rsa로 생성된 공개 키와 개인 키 쌍이 생성된다.
입력하면 위와같이 Enter를 하라고 뜰텐데,

  • 첫번째 Enter는 키를 저장할 경로를 정하는 것이다.
    그냥 Enter를 누르면 기본 경로인 /root/.ssh/id_rsa에 저장된다.
    즉, 개인키는 /root/.ssh/id_rsa, 공개키는 /root/.ssh/id_rsa.pub 으로 저장된다.

  • 두번째 Enter는 비밀번호를 정하는 것이다.
    보통은 배포 자동화를 위해 비밀번호를 비워둔다. Enter 두 번을 눌러서 넘어가자.

이제 아래 명령어로 공개 키를 확인해볼 수 있다.

cat ~/.ssh/id_rsa.pub

알파벳과 숫자 등이 섞인 긴 문장이 나올텐데, 이것이 공개 키이다.

이 공개 키를 서버 인스턴스에 추가하면, Jenkins 컨테이너에서 서버 EC2 인스턴스로 비밀번호 없이 SSH 접속이 가능해진다.

ssh-rsa 라고 쓰인것 부터 끝까지 모두 복사하고, 서버 인스턴스로 넘어가자.


서버 인스턴스에 접속한 후, 아래 명령어를 입력하자.

sudo vi ~/.ssh/authorized_keys

위 명령어는 authorized_keys를 vi 에디터로 열라는 것이다.

이때, ~가 어딘지가 중요한데 EC2면 ubuntu로 지정되어 있기 때문에 상관 없지만, 다른 경우 ubuntu로 이동해서 열거나 해당 디렉토리에서 넣고 이후 Jenkins Pipeline에서 스크립트를 수정해야 한다는 것을 유의하자.

  • vi 에디터에서 i를 누르면 insert 모드로 들어가게 된다. insert 모드로 들어간 후, 아까 Jenkins 인스턴스에서 복사했던 공개 키를 붙여넣자.

  • 붙여넣을 때 주의할 점은, 기존 인스턴스의 키와 다른 줄에 붙여넣어야 한다.
    줄바꿈은 몇 번을 해도 상관 없다. 필자는 줄바꿈을 두 번해서, 키를 구분하기 쉽게 해놓았다.

  • 다 붙여넣었으면, ESC를 눌러 Command 모드로 바꾸고, :wq를 입력해서 저장 후 종료하자.


이제, Jenkins 인스턴스의 Jenkins 컨테이너에서 SSH 통신을 해서 정상적으로 진행되는지 확인한다.

ssh -o StrictHostKeyChecking=no ubuntu@[서버 인스턴스 ip]

성공한다면, 위와 같이 서버 인스턴스로 접속하게 되는 것을 볼 수 있다.

만약 실패한다면, 아래의 과정을 통해서 점검해보도록 하자.


점검

1. Jenkins 인스턴스의 Jenkins 컨테이너 안의 비공개키를 확인.

Jenkins 컨테이너 안에서 아래 명령어를 입력해보자.

ls -l ~/.ssh/id_rsa

Jenkins 컨테이너에 개인 키가 있는 것은 확인된다.

2. 공개 키가 서버 인스턴스에 들어갔는지 확인.
서버 인스턴스에 들어가서, 아래 명령어를 입력해보자.

cat ~/.ssh/authorized_keys

아까 입력한 Jenkins의 공개 키가 한 줄로 완전히 들어가있는지 확인한다.

3. 퍼미션(권한) 확인
SSH는 권한에 아주 민감하다. 서버 인스턴스에서 아래 명령어를 입력하여, 권한을 부여하자.

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

Jenkins SSH-agent 설정

통신이 되는 것을 확인했다면, 다시 Jenkins로 돌아와서 Jenkins 관리에서 Credentials로 들어간다.

Credentials에 우리가 만든 공개 키와 개인 키중에서 개인 키를 저장할 것이다.
Credentials란의 global을 누른 후, Add Credentials 버튼을 누른다.

Kind를 눌러 SSH Username with private key를 선택한다.

ID는 스크립트에 있는 sshagent(['deploy_ssh_key'])에 있는 식별자를 의미하며, 여기에서는 deploy_ssh_key 라고 입력한다.
만약 ID를 다르게 하고싶다면, Pipeline의 스크립트에도 수정해야 한다.

Username은 접속할 유저 이름인데, EC2에는 디폴트 값으로 ubuntu 이기 때문에, ubuntu로 입력해놓으면 된다.

Private Key는 Jenkins 컨테이너에서 아래 명령어를 입력해서 나오는 개인 키를 복사해서 붙여넣어준다.

cat ~/.ssh/id_rsa

주의할 점은 -----BEGIN OPENSSH PRIVATE KEY----- 와 -----END OPENSSH PRIVATE KEY-----까지 모두 포함해서 복사 붙여넣기를 해야 한다.

이제 Create를 눌러 Credential을 등록해주자.

Deploy.sh 작성

Jenkins Piepline 스크립트를 보면, Deploy 스텝에 'ssh -tt ubuntu@[server-instance-ip] sh ./deploy.sh' 부분이 있을 것이다.

이 deploy.sh 파일이 서버 인스턴스에서 jar 파일을 실행하는 역할을 수행한다. 내용은 다음과 같다.

pid=$(pgrep java)
echo current spring pid is ${pid}

if [ -n "${pid}" ]
then
        sudo kill -9 ${pid}
        echo kill process ${pid}
        sleep 5
else
        echo no process
fi

echo "Deployment Start..."

JAR_PATH=$(ls -t /home/ubuntu/project/*.jar | head -1)
sudo chmod +x ${JAR_PATH}
sudo nohup sudo java -jar ${JAR_PATH} >> /home/ubuntu/project/application.log &

sleep 5

echo "Done"
  • 이 쉘 스크립트는 이미 jar 파일이 실행되고 있는 경우. 즉, 이미 돌고 있는 서버가 존재한다면 해당 서버를 종료시킨다.
  • 이후, 새로 복사되어 들어온 jar 파일을 실행 가능하게 한 뒤, 다시 백그라운드에서 실행하도록 하는 스크립트이다.

해당 스크립트를 서버 인스턴스의 /home/ubuntu/에 만들 것이다.
아래 명령어를 서버 인스턴스에 입력하여 vi 에디터를 열자.

sudo vi deploy.sh

i를 눌러 insert 모드로 들어가서, 위의 스크립트를 붙여넣고 저장 후 종료하자.
해당 파일은 실행 권한이 없기 때문에, chmod를 통해 실행 가능하도록 해준다.

sudo chmod 700 deploy.sh

Jenkins Pipeline 스크립트 수정

  • 이제 Jenkins Pipeline 스크립트에서 Deploy 스텝에 주석을 해제하고, [server instance public ip] 라고 되어잇는 부분을 모두 서버 인스턴스의 ip로 바꿔주자.

  • 서버 인스턴스에서 /home/ubuntu로 들어가면 project 폴더가 없다. 우리는 Pipeline 스크립트에 경로를 /home/ubuntu/project로 설정해줬기 때문에, 스크립트를 수정하거나, mkdir 명령어를 통해 project 폴더를 생성해주자.

  • 여기까지 완료하면, Deploy 및 전체 Jenkins Pipeline이 완성된다.

이제 Jenkins를 실행해보도록 하자.

테스트

오류발생!

Deploy 과정에서 오류 발생
위 오류 내용은 현재 Jenkins 작업 디렉토리 내에 build/libs/.jar 경로가 존재하지 않는다는 뜻이다. 즉, scp 명령으로 보낼 파일이 아예 없다는 뜻.

원인 후보
1.Gradle 빌드에 실패했거나 실행되지 않음.

  • Jenkins에서 로그 확인 결과, "BUILD SUCCESSFUL" 메시지가 나왔으므로 빌드는 성공했음.

2.빌드 산출물 확인

  • Jenkins 컨테이너에 접속하여 /build/libs/ 경로를 찾아간 결과, .jar 파일은 존재했음. 경로를 /build/libs/ 말고, 실제 찾은 경로인 var/jenkins_home/workspace/gaseyola_pipeline/TradingMatchingService/build/libs/로 수정했다.

이를 이해하기 위해서는 Jenkins의 기본 동작을 이해해야 한다.

Jenkins는 파이프라인이 실행될 때마다 /var/jenkins_home/workspace/[파이프라인 이름] 디렉토리를 작업 디렉토리(workspace)로 사용한다. (Jenkins의 파이프라인은 기본적으로 workspace 디렉토리에서 실행된다.)

나의 경우에는 /var/jenkins_home/workspace/gaseyola_pipeline/까지를 기본 디렉토리로 사용하는 것.

결국, /build/libs/ 경로를 찾지 못한 이유는, 현재 디렉토리를 기본적으로 /var/jenkins_home/workspace/gaseyola_pipeline/로 잡고있는데, build/libs/가 그 아래에 없었기 때문이다.

나의 경우에는 /TradingMatchingService라는 폴더가 하나 더 있었기 때문에!!

따라서, 스크립트의 경로를 /build/libs/ 에서 TradingMatchingService/build/libs/로 변경해주면 해결된다.

참고로 앞에 /가 붙으면 절대경로, Jenkins 컨테이너의 루트 디렉터리를 의미하므로 파일을 찾지 못한다.
따라서 /TradingMatchingService/... ❌ → TradingMatchingService/... ⭕ 로 수정해야 한다.


오류발생!
경로 문제는 해결되었고, 위 오류는 SSH 신뢰관계(known_hosts) 때문에 발생한 것이다.
Host key verification failed는 Jenkins가 원격 서버(서버 인스턴스)를 신뢰할 수 없다고 판단해서 접속을 막는 상황.

아마, 서버 인스턴스를 종료했다가 다시 시작해서 퍼블릭 IP가 변경되어 발생한 문제라고 생각된다.

스크립트에 IP는 갱신해주었는데도 오류가 발생했으므로, Jenkins 컨테이너에서 아래 명령어로 다시 한번 연결해서, 서버를 신뢰하도록 해주었다.

ssh -o StrictHostKeyChecking=no ubuntu@[server instance ip]

빨간 줄은 SSH가 처음 해당 서버에 연결할 때, "이 서버의 공개키를 신뢰하겠다" 라는 것을 의미한다. 즉, ~/.ssh/known_hosts 파일에 위 서버의 서명이 등록된 것이다.


오류발생!
scp까지는 성공했고, 이 오류는 SSH 배포 단계에서 흔하게 발생하는 에러라고 한다. EC2에서 실행하려는 deploy.sh 파일에 실행 권한이 없어서 생긴 문제이다.

서버 인스턴스에 접속해서 아래 명령어를 입력해 deploy.sh 파일에 대한 권한을 알아보자.

ls -l /home/ubuntu/deploy.sh

1.-rwx------

  • 파일 타입: - → 일반 파일
  • 소유자 권한: rwx → 소유자(root)는 읽기, 쓰기, 실행 가능
  • 그룹 권한: --- → 그룹(root)는 아무 권한 없음
  • 다른 사용자 권한: --- → 다른 사용자(ubuntu 등)는 아무 권한 없음

2.1 root root

  • 소유자: root
  • 그룹: root

즉, 현재 이 파일은 root만 읽고, 쓰고, 실행할 수 있다.
→ ubuntu 계정으로 실행하려고 하면 Permission denied가 발생하는 이유가 바로 이것.

아래 명령어를 입력하여 모든 사용자가 읽고, 실행 가능하도록 권한을 추가해주자.

sudo chmod +r /home/ubuntu/deploy.sh
sudo chmod +x /home/ubuntu/deploy.sh

현재 ubuntu 계정으로 접속중이기 때문에, 파일 권한을 바꾸려면 sudo를 붙여야 할 것이다.

권한 부여 후

1.-rwxr-xr-x

  • 소유자(root): rwx → 읽기, 쓰기, 실행 가능
  • 그룹(root): r-x → 읽기, 실행 가능
  • 다른 사용자(ubuntu 등): r-x → 읽기, 실행 가능

2.소유자/그룹

  • 소유자: root
  • 그룹: root

이제 다른 사용자(ubuntu)도 ./deploy.sh를 실행할 수는 있지만, 내용을 읽거나 수정할 수는 없다.


많은 디버깅을 끝내고, 결국 Deploy까지 성공했다!

여기까지 하면, CI/CD 파이프라인을 다 만든 것이다.

다음 포스팅에서는 Github Webhook을 이용해서, 깃허브에 push를 하면 Jenkins 파이프라인이 동작하도록 만들어보자.

참고자료
https://hstory0208.tistory.com/entry/SSH-Secure-SHell-%EB%9E%80-%EC%89%BD%EA%B2%8C-%EC%9D%B4%ED%95%B4%ED%95%B4%EB%B3%B4%EC%9E%90
https://sehun5515.tistory.com/162

profile
열심히 살자

0개의 댓글