DevOps & Cloud발행일 2025. 9. 5.원본 https://blog.naver.com/jword_/223996728208 ↗

Jenkins 2. VM 수동 배포 자동화하기

Jenkins 2. VM 수동 배포 자동화하기 — #개발자의도구들 #ElasticSearch #Logstash AI스쿨 msa기반 java 백엔드 코스 중에 공부한 내용을 작...

#DevOps/cloudOps#Naver Blog

#개발자의도구들 #ElasticSearch #Logstash

​

AI스쿨 msa기반 java 백엔드 코스 중에 공부한 내용을 작성하였습니다

\* 현재 Spring x 프로젝트를 진행하고 있습니다. 전체 목차를 보시려면 여기를 눌러주세요.

\* Devops stack: 여기

\* 리눅스 기본기: 여기

Before

  • Jenkins 1. 워커 노드 등록 및 키 설정

​

Jenkins 적용 이전 수동배포 로직

Jenkins에 배포하기전에 진행한 VM 수동 배포 로직은 다음과 같습니다.

​

👉 기본 셋팅

: https://m.blog.naver.com/jword\_/223725860934

이미지

[Linux에서 Spring 프로젝트 돌리기 (JDK, Docker, mariadb 설치)
#개발자의도구들 #LinuxSpring #리눅스스프링 #리눅스자바 #리눅스자바다운로드 🚨📝 📖 📒✏️...
blog.naver.com](https://blog.naver.com/jword_/223725860934)

javascript 코드 예제
                                    사용한 명령어
sudo apt update
sudo apt search openjdk // 설치 가능한 jdk 버전 확인 명령어
sudo apt install openjdk-17-jdk-y
sudo apt install git -y
git --version // 생략가능

git clone <원격 프로젝트 주소>

// 빌드
sudo ./gradlew build
sudo chmod +x gradlew 👉 ❌ 권한에러 발생시 추가

// 실행
cd build/libs
java -jar ---SNAPSHOT.jar

총 사용된 명령어: 8회 (중복 및 불필요한 것들 제외)

​

📌 서버당 8회의 명령어 → MSA의 경우 수 백대의 서버가 사용된다.

​

❌ 문제점

  • 각 서버 마다 매번 빌드 진행
  • 서버가 많아질 수록 더 많은 시간이 소요되는 구조

​

​

✅ 이를 해결하기 위해 Jenkins를 도입하여 개발자가 직접 관리해야할 것을 줄이자!

​

빌드 & 배포 구조 변경

이미지

  • 기존 구조
  • 개발자가 직접 매번 스크립트를 실행하는 번거로움
  • 개별 노드에서 진행되는 Build의 오버헤드

이미지

  • 새로운 구조
  • Jenkins를 사용하여 Master에 워커 노드 등록, 파이프라인 등록
  • Worker노드는 파이프라인을 제공 받아 작업 진행
  • 자기 자신은 Build를
  • 각 배포 서버에 SSH 연결을 통해 JAR파일 배포 및 Run

​

🚀 명령어 사용 횟수 감소

javascript 코드 예제
                                    이전: 평균 8회 x n번 반복

-> 8회로 고정 - 나머지는 기존 환경 설정만
               - ssh키 등록, IP설정 변경

​

파이프라인 구축하기

  • 파이프라인?
  • CI/CD 프로세스를 코드로 정의하게 해주는 기능입니다.

​

  • Groovy 기반 DSL 언어를 사용합니다.
  • Groovy 언어를 알고 있으면 사용에 유리!
javascript 코드 예제
                                    🎮 로직

1. gitclone

2. Build
- 필요한 것: jvm

3. ssh 접속 및 배포
- 실행 노드는 워커노드와 분리됨

​

worker node에 미리 build를 할 수 있는 셋팅을 해두면 좋다. 없는 경우 로직만 추가해주면 됩니다.

👉검증 로직이 번거롭기 때문에 Build 노드 환경을 수동적으로 한번만 셋팅해주면 됩니다.

​

수동적으로 미리 Build 환경이 되어있다고 가정하고 스크립트를 작성하겠습니다.

​

📌 1. Build stage

javascript 코드 예제
                                    pipeline {
    agent {
        label 'worker' //📌 worker 노드 식별자 - 이름
    }

    stages {
        stage('Checkout') {
            steps { // github 주소 및 branch 등록
                git url: 'https://github.com/keyveloper/X-frontServer', branch: 'master'
            }
        }

        stage('Build') {
            steps {
                sh 'chmod +x ./gradlew'
                sh './gradlew clean build'
            }
        }
    }
}

이렇게 하면 worker node에서 build가 완료되고 .JAR 파일이 생성됩니다. 이를 각 실행 노드에 옮겨주기만 하면 완료입니다.

​

​

📌 2. Deploy Stage

​

📦 JAR 파일 옮기기

scp 명령어를 사용하여 네트워크로 직접 파일을 전송합니다.

​

  • 역시나 SSH 접속이기 때문에 SSH키를 미리 등록해둬야 합니다.
  • 여기서는 Worker의 공개키를 배포당할 Node에서 가지고 있으면 됩니다.
javascript 코드 예제
                                    pipeline {
    agent {
        label 'worker'
    }

    stages {
        --- 생략 ---
        stage('Deploy') {
            steps {
                stage('Deploy') {
            steps {
                // 📌 보낼 파일 경로: ./build/libs/X-FrontServer-0.0.1-SNAPSHOT.jar
                // 📌 저장할 위치 경로: /home/tod/
                sh 'scp -i ./build/libs/X-FrontServer-0.0.1-SNAPSHOT.jar tod@192.168.0.13:/home/tod/'
            }
        }
        }
    }
}
javascript 코드 예제
                                    📌 worker노드 ssh키 생성

sudo -u worker ssh-keygen -t ed25519

🎯 deploy 전용 노드에 복사
sudo cat /home/worker/.ssh/id_ed25519.pub // 공개키 확인
ssh-copy-id -i ~/.ssh/id_ed25519 worker@<deploy-node-ip>

🎮 Test
worker노드에서 ssh로 접속해보기!
-> 비번 입력 필요 없다면 완성

​

배포 테스트

빌드 진행시 worker 노드의 내부 상황

javascript 코드 예제
                                    worker@worker:~$ ls
 caches  '~p'   remoting   remoting.jar   workspace

worker@worker:~$ cd workspace

worker@worker:~/workspace$ ls
review-vm-manual  review-vm-manual@tmp  //📌 workspace내에 pipeline 작업 이름으로 폴더가 생성

​

🚀배포 완료

javascript 코드 예제
                                    tod@x:~$ ls
elasticsearch  kibana  logstash    spring            X-FrontServer-0.0.1-SNAPSHOT.jar
kafka          logs    prometheus  tmp_elastic_data

.jar 파일이 잘들어온 것을 볼 수 있다.

​

실행하기

🤔 worker 스코프 밖에서 서버를 실행하기?

  • 현재 worker가 서버 실행을 위해 직접 ssh로 접속하여야 한다.
  • 여기서 직접 실행을 하면 파이프라인이 서버가 종료될 때까지 영원히 안끝난다.

​

이때 nohup을 사용할 수 있다.

​

​

nohup이란?

  • 사용자가 로그아웃 하거나, 터미널 세션이 종료되어도 실행중인 프로세스가 계속 실행되도록 하는 유틸리티
javascript 코드 예제
                                    stage('Run') {
            steps {
                sh '''
ssh tod@192.168.0.13 << 'EOF'
nohup java -jar X-FrontServer-0.0.1-SNAPSHOT.jar --spring.profiles.active=beta
EOF
            '''
            }
        }
  • 들여쓰기 없이 넣기!
  • nohup으로 서버 실행

​

javascript 코드 예제
                                    2025-09-05T09:49:14.424Z  INFO 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Node -1 disconnected.
2025-09-05T09:49:14.424Z  WARN 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Connection to node -1 (localhost/127.0.0.1:9092) could not be established. Node may not be available.
2025-09-05T09:49:14.424Z  WARN 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Bootstrap broker localhost:9092 (id: -1 rack: null) disconnected
2025-09-05T09:49:15.432Z  INFO 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Node -1 disconnected.
2025-09-05T09:49:15.432Z  WARN 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Connection to node -1 (localhost/127.0.0.1:9092) could not be established. Node may not be available.
2025-09-05T09:49:15.432Z  WARN 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Bootstrap broker localhost:9092 (id: -1 rack: null) disconnected
2025-09-05T09:49:16.434Z  INFO 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Node -1 disconnected.
2025-09-05T09:49:16.434Z  WARN 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Connection to node -1 (localhost/127.0.0.1:9092) could not be established. Node may not be available.
2025-09-05T09:49:16.435Z  WARN 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Bootstrap broker localhost:9092 (id: -1 rack: null) disconnected
2025-09-05T09:49:17.241Z  INFO 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Node -1 disconnected.
2025-09-05T09:49:17.242Z  WARN 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Connection to node -1 (localhost/127.0.0.1:9092) could not be established. Node may not be available.
2025-09-05T09:49:17.242Z  WARN 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Bootstrap broker localhost:9092 (id: -1 rack: null) disconnected
2025-09-05T09:49:18.241Z  INFO 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Node -1 disconnected.
2025-09-05T09:49:18.242Z  WARN 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Connection to node -1 (localhost/127.0.0.1:9092) could not be established. Node may not be available.
2025-09-05T09:49:18.242Z  WARN 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Bootstrap broker localhost:9092 (id: -1 rack: null) disconnected
2025-09-05T09:49:19.215Z  INFO 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Node -1 disconnected.
2025-09-05T09:49:19.215Z  WARN 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Connection to node -1 (localhost/127.0.0.1:9092) could not be established. Node may not be available.
2025-09-05T09:49:19.215Z  WARN 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Bootstrap broker localhost:9092 (id: -1 rack: null) disconnected
2025-09-05T09:49:20.222Z  INFO 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Node -1 disconnected.
2025-09-05T09:49:20.223Z  WARN 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Connection to node -1 (localhost/127.0.0.1:9092) could not be established. Node may not be available.
2025-09-05T09:49:20.223Z  WARN 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Bootstrap broker localhost:9092 (id: -1 rack: null) disconnected
2025-09-05T09:49:21.106Z  INFO 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Node -1 disconnected.
2025-09-05T09:49:21.106Z  WARN 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Connection to node -1 (localhost/127.0.0.1:9092) could not be established. Node may not be available.
2025-09-05T09:49:21.106Z  WARN 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Bootstrap broker localhost:9092 (id: -1 rack: null) disconnected
2025-09-05T09:49:21.978Z  INFO 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Node -1 disconnected.
2025-09-05T09:49:21.979Z  WARN 3727 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient   : [Producer clientId=producer-1] Connection to node -1 (localhost/1
  • 정상적으로 spring 실행이 된다.
  • 하지만 무한 로딩
  • Kafka를 일부러 안켜뒀음
  • 이거와 별개로 Spring 실행은 백그라운드에서 실행되므로 pipeline은 꺼져야 하는데..?

​

​

​

파이프라인 수정하기

nohup 명령어에 아래 추가

javascript 코드 예제
                                    > /home/jenkins-agent/x-front-server/app_8080.log 2>&1 < /dev/null &
리다이렉션 대상의미
>stdout 로그 파일로
2>&1stderr → stdout으로 합치기
< /dev/nullstdin을 null로 줘서 입력 대기 차단
&백그라운드 실행

stdout과 stderr을 명시적으로 리다이렉션 하지 않으면, Jenkins sh block에서 그 프로세스의 출력을 계속 기다린다고 한다.

​

  • 거기다가 난 &를 안줬다.
javascript 코드 예제
                                    sh '''
nohup java -jar app.jar --some-profile=beta &
'''

이렇게만 해도 되는것 아닌가?

  • background 실행은 된다.
  • Jenkins는 nohup커맨드가 출력을 완료할 때까지 기다림
  • 실제로 해보니 pipeline에서 계속 출력을 기다림
  • Kafka 무한 연결 출력을 계속 기다린다.

​

이미지

빌드 성공 로그

​

이미지

Spring 서버가 정상적으로 동작하고 있다!

🚀 추가적인 고려사항

​

  • 기존 Spring을 종료하고 다시 실행
javascript 코드 예제
                                    spring 실행시 pid를 적은 파일을 따로 생성
-> 다시 실행전 pid를 읽어서 kill
-> 실행

​

📌 불편한 점

  • 파일 경로를 일일히 지정해주는데 여기서 오타가 많이 발생한다.
  • 배포할 때 각 서버 마다 SSH키를 수동 적으로 등록해줘야하는데 많이 번거롭다.

트러블 슈팅

🤔 Gradle 설치 생략

  • 수동 배포에 환경 설정이 하나 더 늘었다. Gradle을 따로 설치해줘야 한다.

이미지

  • worker에 gradle을 셋팅 안해뒀다
  • pipeline내에서 자동으로 다운로드 해준다.

​

❌ Toolchain JDK 버전 오류

javascript 코드 예제
                                    A problem occurred configuring root project 'X-FrontServer'.
> Failed to calculate the value of task ':compileJava' property 'javaCompiler'.
  > Cannot find a Java installation on your machine matching this tasks requirements:
    {languageVersion=17, vendor=any vendor, implementation=vendor-specific} for LINUX on x86_64.
      > No locally installed toolchains match and toolchain download repositories have not been configured.
  • JVM을 설치했는데 왜 오류가 날까?
javascript 코드 예제
                                    java {
    toolchain {
        languageVersion.set(JavaLanguageVersion.of(17))
    }
}

우선 내 build.grade.kts안에 위와 같은 설정이 포함되어있다.

​

  • 하지만 worker노드에는 Java 17이 설치되어 있지 않거나, 설치되어있어도 Gradle이 찾을 수 있는 경로에 없을 가능성이 있음

​

-

  • 📌 이렇게 자동 배포시에는 버전을 신경쓰는 것이 매우 번거롭다...
  • 실제 다른 노드가 JDK 버전이 다르다면 빌드 및 실행이 안될 것

​

👉 JVM을 올바른 버전으로 재설치하니 해결되었다.