imhamburger 님의 블로그

데이터엔지니어 부트캠프 - 젠킨스(Jenkins) 이해하기&실행해보기 (16주차) 본문

데이터엔지니어 부트캠프

데이터엔지니어 부트캠프 - 젠킨스(Jenkins) 이해하기&실행해보기 (16주차)

imhamburger 2024. 10. 22. 12:03

Jenkins는 오픈 소스 자동화 서버로, 주로 소프트웨어 개발에서 CI/CD(지속적 통합/지속적 배포)를 구현하는 데 사용된다. Jenkins는 빌드, 테스트, 배포 등의 프로세스를 자동화하여 개발자와 운영팀이 소프트웨어의 변경 사항을 빠르고 안정적으로 배포할 수 있게 도와준다.

 

 

CI/CD?

  • 지속적 통합(CI, Continuous Integration): Jenkins는 개발자들이 코드 변경 사항을 Git과 같은 버전 관리 시스템에 자주 병합할 수 있게 돕는다. Jenkins는 이러한 병합이 일어날 때마다 자동으로 빌드를 실행하고, 테스트를 진행하여 코드의 품질을 유지할 수 있도록 한다.
  • 지속적 배포(CD, Continuous Delivery): Jenkins는 애플리케이션을 자동으로 서버에 배포할 수 있는 기능도 제공한다. 이렇게 함으로써 변경 사항이 빠르게 프로덕션 환경에 배포될 수 있다.

 

 

Springboot jar파일을 jenkins로 자동 실행시키기

 

 

 jar파일을 nohup을 이용해 실행할 건데 이거를 jenkins를 이용해서 자동적으로 실행시키게 할 것이다.

Jenkins에서 Execute shell 을 이용해 명령어를 실행할 것이다.

 

 

그리고 sh start 실행파일도 만들어줬다.

 

start.sh

#!/bin/bash

#Start the application
nohup java \
    -jar build/libs/<jar파일명> & #~~SNAPSHOT.jar

echo "> Application started with PID $!"

 

 

+ stop 실행파일도 만들어줬다.

 

stop.sh

#!/bin/bash

JAR_FILE_NAME=<jar파일명> #~~SNAPSHOT.jar

#Get the PID from the file
PID=$(pgrep -f $JAR_FILE_NAME)

if [ -z "$PID"]; then
    echo "> 현재구동중인 애플리케이션이 없으므로 종료하지 않습니다."

else
    echo "> kill -9 $PID"
    kill -9 $PID
    echo "> 실행중인 애플리케이션을 종료하고 있습니다..."
    sleep 10
fi

 

참고로 나는 Jenkins 비밀번호을 잊어먹어서... 비밀번호 확인하는 방법은 아래와 같다.

 

젠킨스 비밀번호 찾기

cat <홈경로>/.jenkins/secrets/initialAdminPassword

 

지금보니 Jenkins 처음 시작할 때 비밀번호 경로를 알려줬었네...

나의 경우는 위의 경로에서 찾을 수 있었지만 컴퓨터마다 다를수 있으니 참고!!

 

실행파일을 다 만들어주고 Jenkins에서 설정을 해준다.

 

Source Code Management는 Git에 있는 소스코드를 불러오게 하였다.

 

 

그리고 Build Triggers 는 Poll SCM 으로 cron을 이용하여 5분마다 실행하는걸로 스케줄러를 설정해주었다.

 

그리고 Execute shell 을 추가하여 위에서 만들어준 실행파일을 실행해주는 명령어를 입력해주었다.

 

프로젝트의 기존 빌드 결과물을 삭제하기

./gradlew clean

 

프로젝트 빌드하기

./gradlew bootJar

 

start.sh 실행하기

sh <start.sh 파일경로>/start.sh #실행

 

stop.sh 실행하기

sh <stop.sh 파일경로>/stop.sh #중단

 

 

그리고 Jenkins를 실행해주면 된다.

참고로 Jenkins 실행 시 로그는 Build History - Consol output 에서 확인할 수 있다.

 

 

이번엔 실행을 로컬이 아닌 AWS EC2서버에서 해보자.

 

 

 

Springboot jar파일을 AWS에서 jenkins를 이용하여 실행시키기

 

위에서는 로컬에서 jar파일을 실행시켰었는데 이번에는 AWS EC2에서 실행시켜보자.

 

EC2 서버에 위에서 만든 start.sh 와 stop.sh 를 복사해온다. 그리고 실행시킬 jar파일도 복사해온다.

 

 

실행파일과 jar파일 복사해오기

scp -i ~/key/samdulko.pem /Users/seon-u/code/java/demo/*.sh ubuntu@<퍼블릭 IP주소>:~/app/hi/
scp -i ~/key/samdulko.pem /Users/seon-u/code/java/demo/build/libs/demo-0.0.1-SNAPSHOT.jar ubuntu@<퍼블릭 IP주소>:~/app/hi/app.jar

 

 

그리고 jenkins에서 Execute shell을 수정해주어야 한다. 실행파일이 AWS 서버에 있기 때문!

나의 경우 AWS 서버에 app/hi 라는 폴더를 만들어 거기에 실행파일과 jar파일을 복사하였다.

ssh -i ~/key/samdulko.pem ubuntu@<퍼블릭 IP주소> "cd ~/app/hi;sudo sh stop.sh"
#sh <start.sh 파일경로>/stop.sh
ssh -i ~/key/samdulko.pem ubuntu@<퍼블릭 IP주소> "cd ~/app/hi;sudo sh start.sh"
#sh <stop.sh 파일경로>/start.sh

 

 

그리고 Jenkins를 실행해주면 끝!

 

실행결과

 

 

 

Springboot jar파일을 AWS에서 jenkins를 이용하여 실행시키기 (도커를 곁들인..)

 

이번엔 jar파일을 도커 이미지화해서 Jenkins로 실행시켜보자.

 

도커이미지를 빌드하기 위해선 Dockerfile을 설정해줘야 한다.

 

Dockerfile 설정 (공식문서 참고)

FROM openjdk:8-jdk-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

 

 

도커 이미지 빌드하기

docker build --build-arg JAR_FILE=build/libs/\*.jar -t demo:0.0.1 .

 

 

테스트를 위해 빌드완료 후 RUN해보기

docker run -d -p 8888:8080 --name demo001 demo:0.0.1

 

그리고 맞딱드린 에러.....

Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" java.lang.UnsupportedClassVersionError: org/springframework/boot/loader/launch/JarLauncher has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 52.0
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:468)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:74)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:369)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:363)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:362)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)

 

에러가 난 이유

해당 에러는 Dockerfile에는 자바 버전이 8인데 나는 자바 17버전으로 jar파일을 만들었기 때문에 버전 충돌로 인해 발생한 에러였다.

 

에러해결

따라서, Dockerfile에 자바 버전을 변경해주니 해결할 수 있었다.

FROM openjdk:17-jdk-alpine #버전변경
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

 

그리고나서 다시 빌드하고 도커 이미지를 푸시하였다.

 

다시 Jenkins로 돌아와서 Execute Shell을 변경해주어야 한다.

 

 

도커를 빌드하고 푸시해주는 명령어

docker build --build-arg JAR_FILE=build/libs/\*.jar -t hamsunwoo/demo:0.0.1 /Users/seon-u/code/java/demo
docker push hamsunwoo/demo:0.0.1

 

 

docker_start.sh 실행해주는 명령어

ssh -i ~/key/samdulko.pem ubuntu@<퍼블릭 IP주소> "cd ~/app/hi;sudo sh docker_start.sh"

 

참고로 나는 docker_start.sh 파일을 AWS에 만들어주었는데 코드는 다음과 같다.

 

 

docker_start.sh

#!/bin/bash

echo "DOCKER STOP"
docker stop app

echo "DOCKER RM"
docker rm app

echo "DOCKER RUN"
docker run -d -p 8080:8080 --name demo001 hamsunwoo/demo:0.0.1

echo "DONE"

 

포트번호를 8080으로 준 이유는 EC2 접근할 수 있는 포트를 8080으로 주었기 때문이다.

 

이제 거의 다 왔다...

Jenkins를 실행하기 전에 EC2서버에 docker가 설치되어 있어야 한다.

 

 

리눅스에 도커 설치하기

sudo apt update
sudo apt install docker.io

 

 

 

또 한가지 더 해줘야할 건...

리눅스는 도커를 실행할 때 앞에 "sudo"를 붙여줘야 하는데 이는 Jenkins에서 실행시킬 때 에러가 나기 때문에 "sudo"를 안붙여도 도커가 실행될 수 있게끔 설정해줘야 한다.

 

 

도커 실행시 "sudo" 없이 실행하게 설정하기

# 현 사용자 도커 그룹에 추가
sudo usermod -aG docker $USER
newgrp docker

#그래도 실행이 안될 시
sudo service docker restart
sudo chmod 666 /var/run/docker.sock

 

이제 모든 설정을 마쳤으니 Jenkins를 실행시키면 끝!

 

실행결과

 

 

도커까지 다 마치긴하였는데 사실 내가 쓴 openjdk:17-jdk-alpine 은 가끔 Alpine에서의 특정 라이브러리 지원 문제로 인해 사용이 줄어들거나 권장되지 않을 수 있다.

대체로 Docker에서는 eclipse-temurin 이미지를 많이 사용하고 있다. eclipse-temurin은 OpenJDK 빌드 프로젝트로, 다양한 JDK 버전을 공식적으로 지원하며, 안정성과 성능 면에서 많이 선호된다. 또한 Alpine 기반 이미지를 원한다면 eclipse-temurin:17-jdk-alpine과 같은 대체 이미지를 사용하면 된다.

 

그리하여...

 

 

Dockerfile에 openjdk:17-jdk-alpine 를 바꿔주자!!

 

수정된 Dockerfile

FROM eclipse-temurin:17-jdk-alpine AS build #openjdk:17-jdk-alpine
WORKDIR /land
COPY . .
RUN ./gradlew clean bootJar

#run
FROM eclipse-temurin:17-jre-alpine as run
COPY --from=build /land/build/libs/*.jar app.jar
#ARG JAR_FILE=build/libs/*.jar
#COPY ${JAR_FILE} app.jar

ENTRYPOINT ["java","-jar","/app.jar"]

 

 

eclipse-temurin:17-jre-alpine 이미지는 Java 애플리케이션을 실행할 수 있는 경량 Java 런타임 환경(JRE)만 포함된 이미지로 애플리케이션을 실행하는 데 필요한 최소한의 Java 환경을 제공한다.

도커 이미지화하였을 때 SIZE가 347MB -> 189MB 로 줄어든 것을 확인할 수 있다.

 

COPY --from=build /land/build/libs/*.jar app.jar ??
이전 build 스테이지에서 생성된 JAR 파일을 현재 스테이지로 복사한다.
구체적으로 /land/build/libs/ 디렉토리의 JAR 파일을 app.jar로 복사. --from=build는 첫 번째 빌드 스테이지에서 파일을 가져오는 부분.

 

결론적으로,

이 Dockerfile은 두 개의 스테이지로 나뉘며, 첫 번째 스테이지에서 애플리케이션을 빌드하고, 두 번째 스테이지에서 경량화된 JRE 환경에서 애플리케이션을 실행하는 방식이다. 이를 통해 빌드 환경과 실행 환경을 분리하고, 최종 이미지를 경량화하는 효과를 얻을 수 있다.

 

 

위 Dockerfile로 Jenkins 설정해주기

./gradlew clean

 

sh파일을 위에서는 AWS에 만들어줬었는데 이번엔 로컬에 만들어주고 복사해오는 걸로 수정하였다. (docker_start.sh)

ssh -i ~/key/samdulko.pem ubuntu@<퍼블릭 IP주소> "cd ~/app/hi;rm -rf *"
scp -i ~/key/samdulko.pem /Users/seon-u/code/java/demo/*.sh ubuntu@<퍼블릭 IP주소>:~/app/hi/

 

그리고 sh파일 실행

ssh -i ~/key/samdulko.pem ubuntu@<퍼블릭 IP주소> "cd ~/app/hi;sudo sh docker_start.sh"

 

docker_start.sh은 다음과 같이 수정하였다.

 

docker_start.sh

#!/bin/bash

echo "DOCKER BUILD" #빌드명령어까지 추가
docker build -t hamsunwoo/demo:0.2.0 .

echo "DOCKER STOP"
docker stop demo002

echo "DOCKER RM"
docker rm demo002

echo "DOCKER RUN"
docker run --name demo002 -p 8080:8080 -d hamsunwoo/demo:0.2.0

echo "DONE"

 

 

그리고 다시 Jenkins를 실행하면 끝!! 진짜 끝!

 

실행결과