Jenkins 2. VM 수동 배포 자동화하기
Jenkins 2. VM 수동 배포 자동화하기 — #개발자의도구들 #ElasticSearch #Logstash AI스쿨 msa기반 java 백엔드 코스 중에 공부한 내용을 작...
#개발자의도구들 #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)
사용한 명령어
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
🚀 명령어 사용 횟수 감소
이전: 평균 8회 x n번 반복
-> 8회로 고정 - 나머지는 기존 환경 설정만
- ssh키 등록, IP설정 변경
파이프라인 구축하기
- 파이프라인?
- CI/CD 프로세스를 코드로 정의하게 해주는 기능입니다.
- Groovy 기반 DSL 언어를 사용합니다.
- Groovy 언어를 알고 있으면 사용에 유리!
🎮 로직
1. gitclone
2. Build
- 필요한 것: jvm
3. ssh 접속 및 배포
- 실행 노드는 워커노드와 분리됨
worker node에 미리 build를 할 수 있는 셋팅을 해두면 좋다. 없는 경우 로직만 추가해주면 됩니다.
👉검증 로직이 번거롭기 때문에 Build 노드 환경을 수동적으로 한번만 셋팅해주면 됩니다.
수동적으로 미리 Build 환경이 되어있다고 가정하고 스크립트를 작성하겠습니다.
📌 1. Build stage
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에서 가지고 있으면 됩니다.
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/'
}
}
}
}
}
📌 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 노드의 내부 상황
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 작업 이름으로 폴더가 생성
🚀배포 완료
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이란?
- 사용자가 로그아웃 하거나, 터미널 세션이 종료되어도 실행중인 프로세스가 계속 실행되도록 하는 유틸리티
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으로 서버 실행
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 명령어에 아래 추가
> /home/jenkins-agent/x-front-server/app_8080.log 2>&1 < /dev/null &
| 리다이렉션 대상 | 의미 |
|---|---|
| > | stdout 로그 파일로 |
| 2>&1 | stderr → stdout으로 합치기 |
| < /dev/null | stdin을 null로 줘서 입력 대기 차단 |
| & | 백그라운드 실행 |
stdout과 stderr을 명시적으로 리다이렉션 하지 않으면, Jenkins sh block에서 그 프로세스의 출력을 계속 기다린다고 한다.
- 거기다가 난 &를 안줬다.
sh '''
nohup java -jar app.jar --some-profile=beta &
'''
이렇게만 해도 되는것 아닌가?
- background 실행은 된다.
- Jenkins는 nohup커맨드가 출력을 완료할 때까지 기다림
- 실제로 해보니 pipeline에서 계속 출력을 기다림
- Kafka 무한 연결 출력을 계속 기다린다.
빌드 성공 로그
Spring 서버가 정상적으로 동작하고 있다!
🚀 추가적인 고려사항
- 기존 Spring을 종료하고 다시 실행
spring 실행시 pid를 적은 파일을 따로 생성
-> 다시 실행전 pid를 읽어서 kill
-> 실행
📌 불편한 점
- 파일 경로를 일일히 지정해주는데 여기서 오타가 많이 발생한다.
- 배포할 때 각 서버 마다 SSH키를 수동 적으로 등록해줘야하는데 많이 번거롭다.
트러블 슈팅
🤔 Gradle 설치 생략
- 수동 배포에 환경 설정이 하나 더 늘었다. Gradle을 따로 설치해줘야 한다.
- worker에 gradle을 셋팅 안해뒀다
- pipeline내에서 자동으로 다운로드 해준다.
❌ Toolchain JDK 버전 오류
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을 설치했는데 왜 오류가 날까?
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
우선 내 build.grade.kts안에 위와 같은 설정이 포함되어있다.
- 하지만 worker노드에는 Java 17이 설치되어 있지 않거나, 설치되어있어도 Gradle이 찾을 수 있는 경로에 없을 가능성이 있음
-
- 📌 이렇게 자동 배포시에는 버전을 신경쓰는 것이 매우 번거롭다...
- 실제 다른 노드가 JDK 버전이 다르다면 빌드 및 실행이 안될 것
👉 JVM을 올바른 버전으로 재설치하니 해결되었다.





