[linux] 올바른 방법으로 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.

추가 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);