Spring Boot Document에서 그들은 ‘각 SpringApplication은 ApplicationContext가 종료시 정상적으로 닫히도록 JVM에 종료 후크를 등록합니다.’라고 말했습니다.
ctrl+c
쉘 명령을 클릭 하면 애플리케이션이 정상적으로 종료 될 수 있습니다. 프로덕션 머신에서 애플리케이션을 실행하는 경우 명령을 사용해야합니다
java -jar ProApplicaton.jar
. 하지만 쉘 터미널을 닫을 수 없습니다. 그렇지 않으면 프로세스가 닫힙니다.
같은 명령을 실행 하면 정상적으로 종료하는 데 nohup java -jar ProApplicaton.jar &
사용할 수 없습니다 ctrl+c
.
프로덕션 환경에서 Spring Boot 애플리케이션을 시작하고 중지하는 올바른 방법은 무엇입니까?
답변
액추에이터 모듈을 사용하는 경우 엔드 포인트가 활성화 된 경우 JMX
또는을 통해 애플리케이션을 종료 할 수 있습니다 HTTP
.
추가 application.properties
:
endpoints.shutdown.enabled = true
다음 URL을 사용할 수 있습니다.
/actuator/shutdown
-애플리케이션을 정상적으로 종료 할 수 있습니다 (기본적으로 활성화되지 않음).
엔드 포인트가 노출되는 방식에 따라 민감한 매개 변수가 보안 힌트로 사용될 수 있습니다.
예를 들어 민감한 엔드 포인트는 액세스 할 때 사용자 이름 / 암호가 필요합니다 HTTP
(또는 웹 보안이 활성화되지 않은 경우 단순히 비활성화 됨).
로부터 봄 부트 문서
답변
다음은 코드를 변경하거나 종료 엔드 포인트를 노출 할 필요가없는 또 다른 옵션입니다. 다음 스크립트를 만들고이를 사용하여 앱을 시작하고 중지합니다.
start.sh
#!/bin/bash
java -jar myapp.jar & echo $! > ./pid.file &
앱을 시작하고 프로세스 ID를 파일에 저장합니다.
stop.sh
#!/bin/bash
kill $(cat ./pid.file)
저장된 프로세스 ID를 사용하여 앱을 중지합니다.
start_silent.sh
#!/bin/bash
nohup ./start.sh > foo.out 2> foo.err < /dev/null &
원격 머신 또는 CI 파이프 라인에서 ssh를 사용하여 앱을 시작해야하는 경우 대신이 스크립트를 사용하여 앱을 시작하십시오. start.sh를 직접 사용하면 셸이 중단 될 수 있습니다.
예를 들어. 앱을 다시 / 배포하려면 다음을 사용하여 다시 시작할 수 있습니다.
sshpass -p password ssh -oStrictHostKeyChecking=no userName@www.domain.com 'cd /home/user/pathToApp; ./stop.sh; ./start_silent.sh'
답변
@ Jean-Philippe Bond의 답변에 관해서는,
다음은 maven 사용자가 spring-boot-starter-actuator를 사용하여 스프링 부트 웹 앱을 종료하도록 HTTP 엔드 포인트를 구성하여 복사하여 붙여 넣을 수있는 maven 빠른 예제입니다.
1. 메이븐 pom.xml :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2.application.properties :
#No auth protected
endpoints.shutdown.sensitive=false
#Enable shutdown endpoint
endpoints.shutdown.enabled=true
3. post 메서드를 보내 앱을 종료합니다.
curl -X POST localhost:port/shutdown
보안 참고 :
종료 방법 인증 보호가 필요한 경우 다음이 필요할 수도 있습니다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
세부 사항 구성 :
답변
springboot 애플리케이션이 PID를 파일에 쓰도록 만들고 pid 파일을 사용하여 bash 스크립트를 사용하여 중지 또는 다시 시작하거나 상태를 가져올 수 있습니다. PID를 파일에 쓰려면 아래와 같이 ApplicationPidFileWriter를 사용하여 SpringApplication에 리스너를 등록합니다.
SpringApplication application = new SpringApplication(Application.class);
application.addListeners(new ApplicationPidFileWriter("./bin/app.pid"));
application.run();
그런 다음 bash 스크립트를 작성하여 스프링 부트 애플리케이션을 실행하십시오. 참조 .
이제 스크립트를 사용하여 시작, 중지 또는 다시 시작할 수 있습니다.
답변
모든 답변에는 정상적인 종료 (예 : 엔터프라이즈 응용 프로그램에서) 동안 조정 된 방식으로 작업의 일부를 완료해야한다는 사실이 누락 된 것 같습니다.
@PreDestroy
개별 빈에서 종료 코드를 실행할 수 있습니다. 더 정교한 것은 다음과 같습니다.
@Component
public class ApplicationShutdown implements ApplicationListener<ContextClosedEvent> {
@Autowired ... //various components and services
@Override
public void onApplicationEvent(ContextClosedEvent event) {
service1.changeHeartBeatMessage(); // allows loadbalancers & clusters to prepare for the impending shutdown
service2.deregisterQueueListeners();
service3.finishProcessingTasksAtHand();
service2.reportFailedTasks();
service4.gracefullyShutdownNativeSystemProcessesThatMayHaveBeenLaunched();
service1.eventLogGracefulShutdownComplete();
}
}
답변
나는 끝점을 노출하지 않고 ( 백그라운드에서 nohup을 사용하고 nohup을 통해 만든 파일을 제외하고 ) 쉘 스크립트로 중지하고 ( KILL PID를 사용하여 정상적으로 3 분 후에도 앱이 계속 실행중인 경우 강제 종료) 중지 합니다. 실행 가능한 jar를 만들고 PID 파일 작성기를 사용하여 PID 파일을 작성하고 Jar 및 Pid를 응용 프로그램 이름과 동일한 이름의 폴더에 저장하고 셸 스크립트도 시작 및 중지와 같은 이름을 가지고 있습니다. 이 중지 스크립트를 호출하고 jenkins 파이프 라인을 통해 스크립트를 시작합니다. 지금까지 문제가 없습니다. 8 개 응용 프로그램에 완벽하게 작동합니다 (매우 일반적인 스크립트이며 모든 응용 프로그램에 적용하기 쉽습니다).
메인 클래스
@SpringBootApplication
public class MyApplication {
public static final void main(String[] args) {
SpringApplicationBuilder app = new SpringApplicationBuilder(MyApplication.class);
app.build().addListeners(new ApplicationPidFileWriter());
app.run();
}
}
YML 파일
spring.pid.fail-on-write-error: true
spring.pid.file: /server-path-with-folder-as-app-name-for-ID/appName/appName.pid
다음은 시작 스크립트 (start-appname.sh)입니다.
#Active Profile(YAML)
ACTIVE_PROFILE="preprod"
# JVM Parameters and Spring boot initialization parameters
JVM_PARAM="-Xms512m -Xmx1024m -Dspring.profiles.active=${ACTIVE_PROFILE} -Dcom.webmethods.jms.clientIDSharing=true"
# Base Folder Path like "/folder/packages"
CURRENT_DIR=$(readlink -f "$0")
BASE_PACKAGE="${CURRENT_DIR%/bin/*}"
# Shell Script file name after removing path like "start-yaml-validator.sh"
SHELL_SCRIPT_FILE_NAME=$(basename -- "$0")
# Shell Script file name after removing extension like "start-yaml-validator"
SHELL_SCRIPT_FILE_NAME_WITHOUT_EXT="${SHELL_SCRIPT_FILE_NAME%.sh}"
# App name after removing start/stop strings like "yaml-validator"
APP_NAME=${SHELL_SCRIPT_FILE_NAME_WITHOUT_EXT#start-}
PIDS=`ps aux |grep [j]ava.*-Dspring.profiles.active=$ACTIVE_PROFILE.*$APP_NAME.*jar | awk {'print $2'}`
if [ -z "$PIDS" ]; then
echo "No instances of $APP_NAME with profile:$ACTIVE_PROFILE is running..." 1>&2
else
for PROCESS_ID in $PIDS; do
echo "Please stop the process($PROCESS_ID) using the shell script: stop-$APP_NAME.sh"
done
exit 1
fi
# Preparing the java home path for execution
JAVA_EXEC='/usr/bin/java'
# Java Executable - Jar Path Obtained from latest file in directory
JAVA_APP=$(ls -t $BASE_PACKAGE/apps/$APP_NAME/$APP_NAME*.jar | head -n1)
# To execute the application.
FINAL_EXEC="$JAVA_EXEC $JVM_PARAM -jar $JAVA_APP"
# Making executable command using tilde symbol and running completely detached from terminal
`nohup $FINAL_EXEC </dev/null >/dev/null 2>&1 &`
echo "$APP_NAME start script is completed."
다음은 중지 스크립트 (stop-appname.sh)입니다.
#Active Profile(YAML)
ACTIVE_PROFILE="preprod"
#Base Folder Path like "/folder/packages"
CURRENT_DIR=$(readlink -f "$0")
BASE_PACKAGE="${CURRENT_DIR%/bin/*}"
# Shell Script file name after removing path like "start-yaml-validator.sh"
SHELL_SCRIPT_FILE_NAME=$(basename -- "$0")
# Shell Script file name after removing extension like "start-yaml-validator"
SHELL_SCRIPT_FILE_NAME_WITHOUT_EXT="${SHELL_SCRIPT_FILE_NAME%.*}"
# App name after removing start/stop strings like "yaml-validator"
APP_NAME=${SHELL_SCRIPT_FILE_NAME_WITHOUT_EXT:5}
# Script to stop the application
PID_PATH="$BASE_PACKAGE/config/$APP_NAME/$APP_NAME.pid"
if [ ! -f "$PID_PATH" ]; then
echo "Process Id FilePath($PID_PATH) Not found"
else
PROCESS_ID=`cat $PID_PATH`
if [ ! -e /proc/$PROCESS_ID -a /proc/$PROCESS_ID/exe ]; then
echo "$APP_NAME was not running with PROCESS_ID:$PROCESS_ID.";
else
kill $PROCESS_ID;
echo "Gracefully stopping $APP_NAME with PROCESS_ID:$PROCESS_ID..."
sleep 5s
fi
fi
PIDS=`/bin/ps aux |/bin/grep [j]ava.*-Dspring.profiles.active=$ACTIVE_PROFILE.*$APP_NAME.*jar | /bin/awk {'print $2'}`
if [ -z "$PIDS" ]; then
echo "All instances of $APP_NAME with profile:$ACTIVE_PROFILE has has been successfully stopped now..." 1>&2
else
for PROCESS_ID in $PIDS; do
counter=1
until [ $counter -gt 150 ]
do
if ps -p $PROCESS_ID > /dev/null; then
echo "Waiting for the process($PROCESS_ID) to finish on it's own for $(( 300 - $(( $counter*5)) ))seconds..."
sleep 2s
((counter++))
else
echo "$APP_NAME with PROCESS_ID:$PROCESS_ID is stopped now.."
exit 0;
fi
done
echo "Forcefully Killing $APP_NAME with PROCESS_ID:$PROCESS_ID."
kill -9 $PROCESS_ID
done
fi
답변
Spring Boot는 애플리케이션 컨텍스트를 생성하는 동안 여러 애플리케이션 리스너를 제공했으며 그중 하나는 ApplicationFailedEvent입니다. 응용 프로그램 컨텍스트가 초기화되었는지 여부를 알기 위해 사용할 수 있습니다.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.context.ApplicationListener;
public class ApplicationErrorListener implements
ApplicationListener<ApplicationFailedEvent> {
private static final Logger LOGGER =
LoggerFactory.getLogger(ApplicationErrorListener.class);
@Override
public void onApplicationEvent(ApplicationFailedEvent event) {
if (event.getException() != null) {
LOGGER.info("!!!!!!Looks like something not working as
expected so stoping application.!!!!!!");
event.getApplicationContext().close();
System.exit(-1);
}
}
}
위의 리스너 클래스에 SpringApplication에 추가하십시오.
new SpringApplicationBuilder(Application.class)
.listeners(new ApplicationErrorListener())
.run(args);
