리눅스 Java 설치 및 스프링부트 프로젝트 내장 톰캣 실행 방법
스프링부트 프로젝트는 내장 톰캣이 있으므로, 톰캣을 따로 설치하지 않고 java로 실행할 수 있습니다.
리눅스 Java 설치
리눅스 JDK 압축 파일 다운
https://www.oracle.com/java/technologies/downloads/archive
오라클 로그인 후, 위 경로 우측에서 프로젝트 JDK 버전을 선택합니다.
jdk-17.0.15_linux-x64_bin.tar.gz 같은 압축 파일로 받으면 설치가 간편합니다.
rpm, deb 같은 패키지 설치 방식을 이용하는 것도 가능합니다.
JDK 설치 (압축 파일 해제)
sudo mkdir -p /opt/java
tar -xvzf jdk-17.0.15_linux-x64_bin.tar.gz -C /opt/java
JDK 파일을 파일질라로 리눅스 서버 유저 Home에 올린 뒤, /opt/java 폴더를 생성하고 압축 해제합니다.
Java 실행파일 심볼릭 링크 생성
ln -s /opt/java/jdk-17.0.15/bin/java /usr/bin/java
/usr/bin/java 경로에 java 실행 파일로 연결되는 심볼릭 링크를 생성합니다.
JAVA 버전 확인
java -version
심볼릭 링크 생성 후, 설치된 Java 버전을 확인할 수 있습니다.
Java 환경변수 설정
sudo vi /etc/profile
profile 파일 하단에 아래와 같이 입력 후 저장합니다.
export JAVA_HOME=/opt/java/jdk-17.0.15
export PATH=$JAVA_HOME/bin:$PATH
Java 폴더 경로를 환경변수로 설정합니다.
환경변수 설정 적용
source /etc/profile
profile 파일 변경사항을 적용합니다.
JAVA_HOME 확인
echo $JAVA_HOME
등록된 Java 환경변수 값 (/opt/java/jdk-17.0.15) 을 확인할 수 있습니다.
스프링부트 jar 빌드 방법
IntelliJ 스프링부트 jar 빌드
https://0songha0.github.io/op/2023-03-07-1#intellij-gradle-war-%EB%B9%8C%EB%93%9C-%EB%B0%A9%EB%B2%95
IntelliJ에서 Gradle로 스프링부트 프로젝트 jar 빌드 후, 생성된 .jar 파일을 파일질라로 서버에 올립니다.
프로젝트 배포 폴더 생성
sudo mkdir -p /opt/프로젝트명
프로젝트 jar 파일, deploy.sh 스크립트, 로그 등이 위치할 프로젝트 배포용 폴더를 생성합니다.
jar 파일 이동
cd /home/유저명/deploy
cp ./프로젝트명-GradleVersion명.jar /opt/프로젝트명
파일질라로 올린 jar 파일을 배포 폴더에 복사합니다.
스프링부트 내장 톰캣 서버 실행 방법
deploy.sh 스크립트 또는 systemd 서비스로 스프링부트 프로젝트 실행이 가능합니다.
두 방식 모두 젠킨스와 연동하여 자동 배포 파이프라인을 만들 수 있습니다.
deploy.sh 스크립트 파일 작성 방식
cd /opt/프로젝트명
vi deploy.sh
jar 파일 위치로 이동하고, deploysh 파일 내용을 아래와 같이 작성 후 저장합니다.
#!/bin/bash
set -e # 에러 발생 시 즉시 종료
# 기존 Java 프로세스 종료
PID=$(pgrep -f java || true)
if [ -n "$PID" ]; then
kill -9 $PID
# 종료 확인 (최대 10초)
for i in {1..10}; do
if ! ps -p $PID > /dev/null; then break; fi
sleep 1
done
# 종료 실패 시
if ps -p $PID > /dev/null; then
echo "기존 프로세스 종료 실패!"
exit 1
fi
fi
# 현재 위치에서 최신 JAR 파일 찾기
# ls -t : 수정 시간 기준 내림차순 정렬
APP_NAME=$(ls -t ./*.jar | head -n 1)
if [ -z "$APP_NAME" ]; then
echo "실행할 JAR 파일을 찾을 수 없습니다."
exit 1
fi
# 신규 애플리케이션 프로세스 실행
nohup java -Dspring.profiles.active=프로파일명 -jar "$APP_NAME" > /dev/null 2>&1 &
NEW_PID=$!
# /dev/null : 스크립트 로그를 파일로 남기지 않고 버림
# Spring Boot 톰캣 로그는 logback에 의해서 파일로 기록됨
# 실행 확인 (최대 5초)
sleep 3
if ps -p $NEW_PID > /dev/null; then
echo "애플리케이션 정상 실행 (PID: $NEW_PID)"
else
echo "애플리케이션 실행 실패 (로그 확인 필요)"
exit 1
fi
echo "배포 완료"
deploy.sh 스크립트로 실행하는 방식은 서버 재부팅 시 자동 시작이 불가합니다.
운영 환경에서 에러 발생 가능성이 높아 개발, 테스트용으로 적합합니다.
deploy.sh 스크립트 실행 권한 부여 명령어
chmod +x deploy.sh
스크립트 실행 권한을 부여하기 위해 최초 1회만 수행하면 됩니다.
모든 그룹에 대한 스크립트 파일 실행 권한이 부여됩니다.
-bash: ./deploy.sh: Permission denied
실행 권한을 부여하지 않고 실행하면, 위와 같은 메시지가 출력됩니다.
deploy.sh 스크립트 실행 명령어
./deploy.sh
작성한 deploy.sh 스크립트를 실행하여 내장 톰캣을 실행할 수 있습니다.
systemd 서비스 파일 작성 방식
https://0songha0.github.io/op/2022-08-06-1
위 글을 참고하여 서비스 파일을 작성 및 등록하고, systemctl 명령어로 실행하면 됩니다.
서버 재부팅 시 자동 재시작이 가능해 deploy.sh 스크립트 실행 방식보다 운영 환경에 적합합니다.
스프링부트 프로젝트 배포 방법
deploy.sh 스크립트 파일 실행 방식
cd /jar업로드폴더
cp ./프로젝트명-신규GradleVersion명.jar /opt/프로젝트명
./deploy.sh
신규 버전의 jar 파일 업로드 후 deploy.sh 파일을 재실행 합니다.
실행 중 jar 파일을 덮어씌우면 NoClassDefFoundError 에러가 발생할 수 있습니다.
이전 버전 jar 파일 삭제 방법
ls -t /opt/프로젝트명/*.jar | tail -n +4 | xargs rm -f
최신에 올린 jar 3개를 제외하고, 오래된 jar들을 모두 삭제할 수 있는 명령어입니다.
젠킨스 파이프라인에서 배포 완료 후 실행하는 것도 좋습니다.
systemd 서비스 파일 실행 방식
cd /jar업로드폴더
systemctl stop 서비스명.service
cp ./프로젝트명-GradleVersion명.jar /opt/프로젝트명
systemctl start 서비스명.service
기존 서비스 프로세스를 종료하고, jar 파일 교체 후 다시 실행하면 됩니다.
스프링부트 내장 톰캣 로그 관리
톰캣 로그 저장 위치 설정
<property name="LOG_FILE" value="./boot-logs/로그파일명" />
프로젝트 resources 폴더 내 logback-spring.xml 파일에서 스프링부트 로그 파일 위치를 지정할 수 있습니다.
logback-spring.xml 파일 예시
<configuration scan="true" scanPeriod="60 seconds">
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<!-- 스프링부트 jar 파일 위치에서 boot-logs 폴더 하위에 로그파일명.log 파일로 저장됩니다. -->
<property name="LOG_FILE" value="./boot-logs/로그파일명" />
<appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<charset>UTF-8</charset>
</encoder>
</appender>
<springProfile name="local">
<appender name="rollingFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<charset>UTF-8</charset>
<!-- <pattern>${FILE_LOG_PATTERN}</pattern> -->
<pattern>[%d{ISO8601}] [%5level] [%thread] [%class] [%method:%line] %msg%n</pattern>
</encoder>
<file>${LOG_FILE}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}</fileNamePattern>
<cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>
<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize>
<totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap>
<maxHistory>${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-7}</maxHistory>
</rollingPolicy>
</appender>
</springProfile>
<springProfile name="dev">
<appender name="rollingFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<charset>UTF-8</charset>
<!-- <pattern>${FILE_LOG_PATTERN}</pattern> -->
<pattern>[%d{ISO8601}] [%5level] [%thread] [%class] [%method:%line] %msg%n</pattern>
</encoder>
<file>${LOG_FILE}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}</fileNamePattern>
<cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>
<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize>
<totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap>
<maxHistory>${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-7}</maxHistory>
</rollingPolicy>
</appender>
</springProfile>
<springProfile name="stg">
<appender name="rollingFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<charset>UTF-8</charset>
<!-- <pattern>${FILE_LOG_PATTERN}</pattern> -->
<pattern>[%d{ISO8601}] [%5level] [%thread] [%class] [%method:%line] %msg%n</pattern>
</encoder>
<file>${LOG_FILE}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}</fileNamePattern>
<cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>
<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize>
<totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap>
<maxHistory>${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-7}</maxHistory>
</rollingPolicy>
</appender>
</springProfile>
<springProfile name="prod">
<appender name="rollingFileAppender" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<charset>UTF-8</charset>
<!-- <pattern>${FILE_LOG_PATTERN}</pattern> -->
<pattern>[%d{ISO8601}] [%5level] [%thread] [%class] [%method:%line] %msg%n</pattern>
</encoder>
<file>${LOG_FILE}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}</fileNamePattern>
<cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>
<maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize>
<totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap>
<maxHistory>${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-7}</maxHistory>
</rollingPolicy>
</appender>
</springProfile>
<root level="INFO">
<appender-ref ref="consoleAppender" />
<springProfile name="local">
<appender-ref ref="rollingFileAppender" />
<appender-ref ref="consoleAppender" />
</springProfile>
<springProfile name="dev">
<appender-ref ref="rollingFileAppender" />
<appender-ref ref="consoleAppender" />
</springProfile>
<springProfile name="stg">
<appender-ref ref="rollingFileAppender" />
<appender-ref ref="consoleAppender" />
</springProfile>
<springProfile name="webprod">
<appender-ref ref="rollingFileAppender" />
<appender-ref ref="consoleAppender" />
</springProfile>
<springProfile name="prod">
<appender-ref ref="rollingFileAppender" />
<appender-ref ref="consoleAppender" />
</springProfile>
</root>
<logger name="kr.co.프로젝트명" additivity="true" level="info" />
<logger name="org.springframework" additivity="true" level="error" />
<logger name="_org.springframework" additivity="true" level="error" />
<logger name="io" additivity="true" level="error" />
<logger name="org" additivity="true" level="error" />
<logger name="log4jdbc" additivity="true" level="error" />
<logger name="reactor" additivity="true" level="error" />
<logger name="springfox" additivity="true" level="error" />
<logger name="org.apache.kafka" additivity="true" level="info" />
<logger name="com.zaxxer.hikari" additivity="true" level="error" />
<logger name="com.zaxxer.hikari.HikariConfig" additivity="true" level="debug" />
<logger name="org.springframework.transaction" additivity="true" level="debug" />
<logger name="com.ulisesbocchio" additivity="true" level="error" />
<logger name="org.apache.ibatis.builder.xml" additivity="true" level="info" />
<!-- log4jdbc SQL로그 -->
<logger name="jdbc.connection" additivity="false"/>
<logger name="jdbc.audit" additivity="false"/>
<logger name="jdbc.resultset" additivity="false"/>
<logger name="jdbc.sqlonly" additivity="false"/>
<!-- SQL로그 -->
<springProfile name="local">
<logger name="jdbc.resultsettable" level="DEBUG">
<appender-ref ref="sqlRollingFileAppender" />
</logger>
<logger name="jdbc.sqltiming" level="DEBUG">
<appender-ref ref="sqlRollingFileAppender" />
</logger>
</springProfile>
</configuration>
rollingPolicy fileNamePattern 설정으로 오래된 로그 파일은 프로젝트명.YYYY-MM-DD.순번.gz 파일로 압축되어 저장됩니다.
스프링부트 정보 확인
스프링부트 PID 확인 방법
ps -ef | grep java
실행 중 java 프로세스 목록에서 jar 파일 실행 명령어 좌측에 406806 같은 PID가 확인됩니다.
스프링부트 포트 확인 방법
ss -ltnp | grep java
application.yml 파일 server port와 동일하게 실행되었는지 확인할 수 있습니다.