올바른 방법으로 Spring Boot 애플리케이션을 종료하는 방법은 무엇입니까?
Spring Boot Document에서 그들은 '각 SpringApplication은 ApplicationContext가 종료시 정상적으로 닫히도록 JVM에 종료 후크를 등록합니다.'라고 말했습니다.
ctrl+c
쉘 명령을 클릭 하면 애플리케이션이 정상적으로 종료 될 수 있습니다. 프로덕션 머신에서 애플리케이션을 실행하는 경우 명령을 사용해야합니다 java -jar ProApplicaton.jar
. 하지만 쉘 터미널을 닫을 수 없습니다. 그렇지 않으면 프로세스가 닫힙니다.
같은 명령을 실행 하면 정상적으로 종료하는 데 nohup java -jar ProApplicaton.jar &
사용할 수 없습니다 ctrl+c
.
프로덕션 환경에서 Spring Boot 애플리케이션을 시작하고 중지하는 올바른 방법은 무엇입니까?
당신은 액추에이터 모듈을 사용하는 경우를 통해 응용 프로그램 종료를 할 수 있습니다 JMX
또는 HTTP
엔드 포인트가 활성화 된 경우 (추가 endpoints.shutdown.enabled=true
사용자에게 application.properties
파일).
/shutdown
-애플리케이션을 정상적으로 종료 할 수 있습니다 (기본적으로 활성화되지 않음).
엔드 포인트가 노출되는 방식에 따라 민감한 매개 변수가 보안 힌트로 사용될 수 있습니다. 예를 들어 민감한 엔드 포인트는 액세스 할 때 사용자 이름 / 암호가 필요합니다 HTTP
(또는 웹 보안이 활성화되지 않은 경우 단순히 비활성화 됨).
로부터 봄 부트 문서
@ 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>
세부 사항 구성 :
다음은 코드를 변경하거나 종료 엔드 포인트를 노출 할 필요가없는 또 다른 옵션입니다. 다음 스크립트를 생성하고이를 사용하여 앱을 시작 및 중지합니다.
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'
springboot 응용 프로그램이 PID를 파일에 쓰도록 만들 수 있으며 pid 파일을 사용하여 bash 스크립트를 사용하여 중지 또는 다시 시작하거나 상태를 가져올 수 있습니다. PID를 파일에 쓰려면 아래와 같이 ApplicationPidFileWriter를 사용하여 SpringApplication에 리스너를 등록합니다.
SpringApplication application = new SpringApplication(Application.class);
application.addListeners(new ApplicationPidFileWriter("./bin/app.pid"));
application.run();
그런 다음 bash 스크립트를 작성하여 스프링 부트 애플리케이션을 실행합니다. 참조 .
이제 스크립트를 사용하여 시작, 중지 또는 다시 시작할 수 있습니다.
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);
모든 대답에는 정상적인 종료 (예 : 엔터프라이즈 응용 프로그램에서) 동안 조정 된 방식으로 작업의 일부를 완료해야 할 수도 있다는 사실이 누락 된 것 같습니다.
@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
SpringApplication implicitly registers a shutdown hook with the JVM to ensure that ApplicationContext is closed gracefully on exit. That will also call all bean methods annotated with @PreDestroy
. That means we don't have to explicitly use the registerShutdownHook()
method of a ConfigurableApplicationContext
in a boot application, like we have to do in spring core application.
@SpringBootConfiguration
public class ExampleMain {
@Bean
MyBean myBean() {
return new MyBean();
}
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(ExampleMain.class, args);
MyBean myBean = context.getBean(MyBean.class);
myBean.doSomething();
//no need to call context.registerShutdownHook();
}
private static class MyBean {
@PostConstruct
public void init() {
System.out.println("init");
}
public void doSomething() {
System.out.println("in doSomething()");
}
@PreDestroy
public void destroy() {
System.out.println("destroy");
}
}
}
As of Spring Boot 1.5, there is no out-of-the box graceful shutdown mechanism. Some spring-boot starters provide this functionality:
- https://github.com/jihor/hiatus-spring-boot
- https://github.com/gesellix/graceful-shutdown-spring-boot
- https://github.com/corentin59/spring-boot-graceful-shutdown
I am the author of nr. 1. The starter is named "Hiatus for Spring Boot". It works on the load balancer level, i.e. simply marks the service as OUT_OF_SERVICE, not interfering with application context in any way. This allows to do a graceful shutdown and means that, if required, the service can be taken out of service for some time and then brought back to life. The downside is that it doesn't stop the JVM, you will have to do it with kill
command. As I run everything in containers, this was no big deal for me, because I will have to stop and remove the container anyway.
Nos. 2 and 3 are more or less based on this post by Andy Wilkinson. They work one-way - once triggered, they eventually close the context.
They are many ways to shutdown a spring application. One is to call close() on the ApplicationContext
:
ApplicationContext ctx =
SpringApplication.run(HelloWorldApplication.class, args);
// ...
ctx.close()
Your question suggest you want to close your application by doing Ctrl+C
, that is frequently used to terminate a command. In this case...
Use endpoints.shutdown.enabled=true
is not the best recipe. It means you expose an end-point to terminate your application. So, depending on your use case and your environment, you will have to secure it...
Ctrl+C
should work very well in your case. I assume your issue is caused by the ampersand (&) More explanation:
A Spring Application Context may have register a shutdown hook with the JVM runtime. See ApplicationContext documentation.
I don't know if Spring Boot configure this hook automatically as you said. I assume it is.
On Ctrl+C
, your shell sends an INT
signal to the foreground application. It means "please interrupt your execution". The application can trap this signal and do cleanup before its termination (the hook registered by Spring), or simply ignore it (bad).
nohup
is command that execute the following program with a trap to ignore the HUP signal. HUP is used to terminate program when you hang up (close your ssh connexion for example). Moreover it redirects outputs to avoid that your program blocks on a vanished TTY. nohup
does NOT ignore INT signal. So it does NOT prevent Ctrl+C
to work.
I assume your issue is caused by the ampersand (&), not by nohup. Ctrl+C
sends a signal to the foreground processes. The ampersand causes your application to be run in background. One solution: do
kill -INT pid
Use kill -9
or kill -KILL
is bad because the application (here the JVM) cannot trap it to terminate gracefully.
Another solution is to bring back your application in foreground. Then Ctrl+C
will work. Have a look on Bash Job control, more precisely on fg
.
If you are using maven you could use the Maven App assembler plugin.
The daemon mojo (which embed JSW) will output a shell script with start/stop argument. The stop
will shutdown/kill gracefully your Spring application.
The same script can be used to use your maven application as a linux service.
If you are in a linux environment all you have to do is to create a symlink to your .jar file from inside /etc/init.d/
sudo ln -s /path/to/your/myboot-app.jar /etc/init.d/myboot-app
Then you can start the application like any other service
sudo /etc/init.d/myboot-app start
To close the application
sudo /etc/init.d/myboot-app stop
This way, application will not terminate when you exit the terminal. And application will shutdown gracefully with stop command.
Use the static exit()
method in the SpringApplication class for closing your spring boot application gracefully.
public class SomeClass {
@Autowire
private ApplicationContext context
public void close() {
SpringApplication.exit(context);
}
}
'programing' 카테고리의 다른 글
Apache를 사용할 때 PHP에서 액세스하기위한 환경 변수 설정 (0) | 2020.08.25 |
---|---|
셀러리 작업을 어떻게 단위 테스트합니까? (0) | 2020.08.25 |
목록을 ComboBox에 바인딩하는 방법은 무엇입니까? (0) | 2020.08.25 |
Firebase 권한이 거부되었습니다. (0) | 2020.08.24 |
디렉토리의 각 파일에 대한 루프 코드 (0) | 2020.08.24 |