programing

올바른 방법으로 Spring Boot 애플리케이션을 종료하는 방법은 무엇입니까?

nasanasas 2020. 8. 25. 08:10
반응형

올바른 방법으로 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:

  1. https://github.com/jihor/hiatus-spring-boot
  2. https://github.com/gesellix/graceful-shutdown-spring-boot
  3. 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. nohupdoes 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);
    }
}

참고URL : https://stackoverflow.com/questions/26547532/how-to-shutdown-a-spring-boot-application-in-a-correct-way

반응형