공공부하자개발 · 영어 학습 노트
리눅스
리눅스 운영RHEL 에서 배포·운영하기0/13 완료
  • 01셸 기본과 파일 시스템
  • 02텍스트 처리 (grep·sed·awk)
  • 03프로세스·서비스 관리 (systemd)
  • 04자바 애플리케이션 배포
  • 05Tomcat·nginx 배포·재기동
  • 06로그·디스크 운영 (logrotate·cron)
  • 07네트워크 진단 (ss·curl·firewalld)
  • 08셸 스크립트 (운영 자동화)
  • 09장애 대응 (jstack·jmap·OOM killer·FD)
  • 10보안 기초 (ssh 강화·SELinux·감사 로그)
  • 11컨테이너 기초 (podman)
  • 12백업·복구 (설정·릴리스·DB 덤프)
  • 13Docker 실무
사이트 소개개인정보처리방침연락처
© 2026 공부하자
홈 › 리눅스 운영 › 04 / 13

자바 애플리케이션 배포

섹션 7진행 0 / 13
1왜 배우는가2핵심 원리3코드 예제4응용 변형 예제5자주 하는 실수 (Tip)6연습 문제7정리‹ 이전다음 ›

2. 핵심 원리

2.1 표준 배포 폴더

앱 하나에 폴더 하나를 /opt/앱이름 아래 두고 역할별로 나눕니다. 01 레슨의 구조를 그대로 씁니다.

폴더 내용
releases/날짜/app.jar 버전별 jar
current 지금 버전을 가리키는 링크
conf/env JAVA_OPTS 등 서버별 값
conf/application.yml 외부 설정
logs/ 앱 로그·stdout
run/ pid 파일

current 가 링크이므로 새 버전 배포는 링크 교체이고 롤백은 이전 폴더로 링크를 되돌리는 것입니다. jar 를 덮어쓰지 않으니 이전 버전이 항상 남습니다.

2.2 java -jar 와 JAVA_OPTS

java [JVM 옵션] -jar app.jar [앱 인자] 순서입니다. JVM 옵션은 -jar 앞, Spring 인자는 뒤에 옵니다.

bash
java -Xms512m -Xmx1g -Dspring.profiles.active=prod -jar app.jar --server.port=8081

옵션을 스크립트에 직접 박지 않고 JAVA_OPTS 변수에 모읍니다. 서버마다 메모리가 다르니 값은 conf/env 파일에서 읽습니다.

2.3 힙 메모리 옵션

옵션 뜻 권장
-Xms 시작 힙 -Xmx 와 같게
-Xmx 최대 힙 서버 RAM 의 50~70%
-XX:MaxMetaspaceSize 클래스 영역 상한 256m
-XX:+HeapDumpOnOutOfMemoryError OOM 시 덤프 항상 켬
-XX:HeapDumpPath 덤프 위치 logs/

-Xmx 를 안 주면 JVM 은 물리 메모리의 1/4 을 씁니다. 8GB 서버에서 2GB 만 쓰다 OOM 이 나는 이유입니다. -Xms 와 -Xmx 를 같게 두면 힙 확장 비용이 없어집니다.

2.4 그 밖의 필수 옵션

bash
-Djava.security.egd=file:/dev/./urandom    # 난수 대기로 시작 지연 방지
-Dfile.encoding=UTF-8                      # 로그·파일 한글 깨짐 방지
-Duser.timezone=Asia/Seoul                 # 서버 시간대와 JVM 시간대 일치
-XX:+UseG1GC                               # JDK 21 기본, 명시하면 의도가 보임

egd 옵션이 없으면 /dev/random 이 엔트로피를 기다려 첫 요청이 수십 초 멈추는 경우가 있습니다. 시간대는 DB 서버와 맞추지 않으면 날짜 계산이 하루씩 어긋납니다.

2.5 외부 설정 파일

jar 안의 application.yml 은 개발 기본값이고, 서버 값은 밖에서 덮습니다. Spring Boot 는 실행 위치의 config/ 폴더를 자동으로 읽습니다.

방법 예
실행 폴더의 config/ /opt/myapp/config/application.yml
위치 지정 --spring.config.additional-location=
프로필 -Dspring.profiles.active=prod
환경 변수 SERVER_PORT=8081

비밀번호는 jar 에 넣지 않고 서버의 설정 파일이나 환경 변수에 둡니다. 파일 권한을 600 으로 하고 소유자를 앱 계정으로 맞춥니다.

2.6 실행 스크립트 뼈대

app.sh 는 start, stop, status, restart, deploy, rollback 여섯 명령을 받습니다. 모든 명령이 같은 변수와 pid 파일을 공유합니다.

bash
APP_HOME=${APP_HOME:-/opt/myapp}
JAVA_OPTS=${JAVA_OPTS:--Xms256m -Xmx512m}
PID_FILE=$APP_HOME/run/myapp.pid
[ -f "$APP_HOME/conf/env" ] && . "$APP_HOME/conf/env"   # 서버별 덮어쓰기

${VAR:-기본값} 문법이 핵심입니다. 환경 변수가 있으면 그것을 쓰고 없으면 기본값을 씁니다. conf/env 를 . 으로 읽어 들이면 그 파일의 값이 최종 우선입니다.

2.7 start 와 pid 파일

시작할 때 pid 를 파일에 적어 두면 나중에 stop 과 status 가 그 번호를 씁니다. ps | grep 으로 찾는 것보다 정확합니다.

bash
nohup "$JAVA" $JAVA_OPTS -jar current/app.jar >> logs/stdout.log 2>&1 &
echo $! > "$PID_FILE"

$JAVA_OPTS 를 따옴표 없이 쓰는 것이 의도입니다. 따옴표로 감싸면 옵션 전체가 인자 하나로 들어가 JVM 이 인식하지 못합니다. 시작 1초 뒤 kill -0 으로 살아 있는지 확인해 즉시 죽는 경우를 잡습니다.

2.8 stop 과 대기

03 레슨의 원칙대로 TERM 을 먼저 보내고 기다립니다. 30초 안에 안 죽으면 KILL 입니다.

bash
kill "$pid"
for _ in $(seq 1 30); do
  kill -0 "$pid" 2>/dev/null || { rm -f "$PID_FILE"; return 0; }
  sleep 1
done
kill -9 "$pid"

이 대기 시간이 Spring 의 완전 종료 시간보다 길어야 합니다. systemd 로 관리하면 이 로직이 TimeoutStopSec 으로 대체됩니다.

2.9 deploy 와 링크 교체

새 jar 를 releases/날짜/ 에 복사하고 current 링크를 바꿉니다. 오래된 릴리스는 5개만 남기고 지웁니다.

bash
rel=$APP_HOME/releases/$(date +%Y%m%d_%H%M%S)
mkdir -p "$rel" && cp "$jar" "$rel/app.jar"
ln -sfn "$rel" "$APP_HOME/current"
ls -1dt "$APP_HOME"/releases/*/ | tail -n +6 | xargs -r rm -rf

ln -sfn 은 기존 링크를 새 대상으로 원자적으로 바꿉니다. 실행 중인 JVM 은 이미 연 jar 를 계속 쓰므로 링크를 바꿔도 죽지 않고, restart 때 새 jar 를 읽습니다.

2.10 rollback

이전 릴리스 경로를 배포 시점에 기록해 두면 롤백은 그 경로로 링크를 되돌리는 것입니다. 스크립트는 run/current.rel 파일에 현재 경로를 적고, 새 릴리스 폴더에 .prev 로 이전 경로를 남깁니다.

bash
prev=$(cat "$APP_HOME/current/.prev")
do_stop; ln -sfn "$prev" "$APP_HOME/current"; do_start

readlink current 로도 이전 경로를 알 수 있지만 Git Bash 는 링크가 복사로 동작해 실패합니다. 파일에 기록하는 방식은 어느 환경에서나 같게 동작합니다.

2.11 status 와 종료 코드

status 는 사람이 읽는 메시지와 함께 종료 코드를 돌려줍니다. 실행 중이면 0, 정지면 3 이 systemd 관례입니다.

bash
if pid_alive; then echo "running PID=$(cat "$PID_FILE")"
else echo "stopped"; return 3; fi

배포 자동화 스크립트나 모니터링이 app.sh status; echo $? 로 판단합니다. 메시지 문자열을 파싱하지 않아도 됩니다.

2.12 systemd 와 연결

03 레슨의 유닛 파일에서 ExecStart 가 이 스크립트를 부르게 할 수도 있고, 스크립트 없이 java 를 직접 부를 수도 있습니다.

ini
[Service]
Type=forking
PIDFile=/opt/myapp/run/myapp.pid
ExecStart=/opt/myapp/app.sh start
ExecStop=/opt/myapp/app.sh stop

스크립트가 백그라운드로 띄우고 돌아오므로 Type=forking 과 PIDFile 을 씁니다. 단순한 앱은 03 레슨처럼 Type=simple 로 java 를 직접 부르는 편이 더 간단합니다.

핵심 원리
  • 2.1 표준 배포 폴더
  • 2.2 java -jar 와 JAVA_OPTS
  • 2.3 힙 메모리 옵션
  • 2.4 그 밖의 필수 옵션
  • 2.5 외부 설정 파일
  • 2.6 실행 스크립트 뼈대
  • 2.7 start 와 pid 파일
  • 2.8 stop 과 대기
  • 2.9 deploy 와 링크 교체
  • 2.10 rollback
  • 2.11 status 와 종료 코드
  • 2.12 systemd 와 연결
이전 섹션1 왜 배우는가2 / 7다음 섹션3 코드 예제