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


120

Spring Boot Document에서 그들은 '각 SpringApplication은 ApplicationContext가 종료시 정상적으로 닫히도록 JVM에 종료 후크를 등록합니다.'라고 말했습니다.

ctrl+c쉘 명령을 클릭 하면 애플리케이션이 정상적으로 종료 될 수 있습니다. 프로덕션 머신에서 애플리케이션을 실행하는 경우 명령을 사용해야합니다 java -jar ProApplicaton.jar. 하지만 쉘 터미널을 닫을 수 없습니다. 그렇지 않으면 프로세스가 닫힙니다.

같은 명령을 실행 하면 정상적으로 종료하는 데 nohup java -jar ProApplicaton.jar &사용할 수 없습니다 ctrl+c.

프로덕션 환경에서 Spring Boot 애플리케이션을 시작하고 중지하는 올바른 방법은 무엇입니까?


구성에 따라 응용 프로그램의 PID가 파일에 기록됩니다. 해당 PID에 킬 신호를 보낼 수 있습니다. 이번 호의 주석도 참조하십시오 .
M. Deinum 2014 년

어떤 신호를 사용해야하나요? kill -9는 좋은 생각이 아니라고 생각합니다.
Chris

5
그게 내가 당신에게 그 실을 지적한 이유입니다 ...하지만 같은 kill -SIGTERM <PID>것이 트릭을해야합니다.
M. Deinum 2014 년

아니, PID와 죽일 -9
대붕

1
kill $ (lsof -ti tcp : <port>)-액추에이터를 사용하지 않고 빠른 킬이 필요한 경우
Alex Nolasco

답변:


61

액추에이터 모듈을 사용하는 경우 엔드 포인트가 활성화 된 경우 JMX또는을 통해 애플리케이션을 종료 할 수 있습니다 HTTP.

추가 application.properties:

endpoints.shutdown.enabled = true

다음 URL을 사용할 수 있습니다.

/actuator/shutdown -애플리케이션을 정상적으로 종료 할 수 있습니다 (기본적으로 활성화되지 않음).

엔드 포인트가 노출되는 방식에 따라 민감한 매개 변수가 보안 힌트로 사용될 수 있습니다.

예를 들어 민감한 엔드 포인트는 액세스 할 때 사용자 이름 / 암호가 필요합니다 HTTP(또는 웹 보안이 활성화되지 않은 경우 단순히 비활성화 됨).

로부터 봄 부트 문서


1
액추에이터 모듈을 포함하고 싶지 않습니다. 그러나 콘솔 로그에서 Spring Boot가 첫 번째 줄에 PID를 인쇄한다는 것을 알았습니다. 액추에이터 모듈 (ApplicationPidListener)을 추가하지 않고 Spring Boot가 다른 파일에 PID를 인쇄하도록하는 방법이 있습니까?
Chris

2
이것은이 끝점에 대한 http 게시물이어야합니다. 당신은 그것을 사용할 수 있습니다 endpoints.shutdown.enabled = 사실
sparkyspider

54

다음은 코드를 변경하거나 종료 엔드 포인트를 노출 할 필요가없는 또 다른 옵션입니다. 다음 스크립트를 만들고이를 사용하여 앱을 시작하고 중지합니다.

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'

이것이 답이되어야합니다. 방금 신호 15 종료가 봄에 우아하게 똥을 내리는 것을 확인했습니다.
Chad

1
다른 외부 쉘 스크립트 내부에서 nohup으로 호출되는 start.sh 내부에서 java-jar 실행을 호출하는 대신 start.sh 내부에서 nohup을 사용하여 java-jar 실행을 호출하지 않는 이유는 무엇입니까 ??
Anand Varkey Philips

2
@AnandVarkeyPhilips 유일한 이유는 테스트 목적으로 명령 줄에서 start.sh를 호출하는 경우가 있지만 항상 필요한 경우 nohup명령을 병합 할 수 있다는 것입니다
Jens

@Jens, 정보 주셔서 감사합니다. foo.out 2> foo.err </ dev / null &
Anand Varkey Philips

1
@jens, 감사합니다. 성공적으로 완료했으며 여기에 시작 및 중지 스크립트를 게시했습니다. ( stackoverflow.com/questions/26547532/… )
Anand Varkey Philips

52

@ 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>

세부 사항 구성 :


두 번째 단계 후 배포를 시도 할 때 다음 오류 메시지가 나타납니다. 설명 : org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter에있는 setAuthenticationConfiguration 메소드의 매개 변수 0에는 'org.springframework.security.config 유형의 Bean이 필요합니다. .annotation.authentication.configuration.AuthenticationConfiguration '을 찾을 수 없습니다. 조치 : 구성에서 'org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration'유형의 Bean을 정의하십시오.
Roberto

2
참고 : server.contextPath=/appName내 application.properties에 같은 것을 포함했다면 이제 종료 명령 은 다음과 같습니다 curl -X POST localhost:8080/appName/shutdown . 누군가에게 도움 이되기를 바랍니다. 이 실수 때문에 많이 힘들어했습니다.
Naveen Kumar

Spring Boot 1.5.8에서는 보안이 없으면 application.properties에있는 것이 좋습니다 endpoints.shutdown.enabled=true management.security.enabled=false.
Stefano Scarpanti

: 당신이 끝점을 노출하고 중지 쉘 스크립트를 통해 시작 PID 파일을 사용하려는 해달라고하면,이 시도 stackoverflow.com/questions/26547532/...
아난드 Varkey 필립스

27

springboot 애플리케이션이 PID를 파일에 쓰도록 만들고 pid 파일을 사용하여 bash 스크립트를 사용하여 중지 또는 다시 시작하거나 상태를 가져올 수 있습니다. PID를 파일에 쓰려면 아래와 같이 ApplicationPidFileWriter를 사용하여 SpringApplication에 리스너를 등록합니다.

SpringApplication application = new SpringApplication(Application.class);
application.addListeners(new ApplicationPidFileWriter("./bin/app.pid"));
application.run();

그런 다음 bash 스크립트를 작성하여 스프링 부트 애플리케이션을 실행하십시오. 참조 .

이제 스크립트를 사용하여 시작, 중지 또는 다시 시작할 수 있습니다.


Generic 및 Jenkins와 호환되는 전체 시작 및 중지 셸 스크립트는 여기에서 찾을 수 있습니다 ( stackoverflow.com/questions/26547532/… )
Anand Varkey Philips


14

모든 답변에는 정상적인 종료 (예 : 엔터프라이즈 응용 프로그램에서) 동안 조정 된 방식으로 작업의 일부를 완료해야한다는 사실이 누락 된 것 같습니다.

@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();
     }
}

이것이 내가 필요한 것입니다. 응용 프로그램을 실행 한 후 ctrl-c를 누르십시오. 감사합니다 @Michal
Claudio Moscoso

8

나는 끝점을 노출하지 않고 ( 백그라운드에서 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

7

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

내가 찾은 최고의 대답! 감사합니다!
Vagif

[@ user3137438], 파괴 전 주석에 로그인하는 것과 어떻게 다릅니 까?
Anand Varkey Philips

6

Spring Boot 2.3 이상부터는 내장 된 단계적 종료 메커니즘이 있습니다.

Pre-Spring Boot 2.3 에는 즉시 사용 가능한 정상 종료 메커니즘이 없습니다. 일부 스프링 부트 스타터는 다음 기능을 제공합니다.

  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

나는 nr의 저자입니다. 1. 스타터 이름은 "Hiatus for Spring Boot"입니다. 로드 밸런서 수준에서 작동합니다. 즉, 서비스를 OUT_OF_SERVICE로 표시하고 어떤 식 으로든 애플리케이션 컨텍스트를 방해하지 않습니다. 이를 통해 정상적인 종료를 수행 할 수 있으며 필요한 경우 서비스를 잠시 중단했다가 다시 사용할 수 있습니다. 단점은 JVM을 중지하지 않고 kill명령 으로 수행해야한다는 것입니다 . 컨테이너에서 모든 것을 실행하기 때문에 어차피 컨테이너를 중지하고 제거해야하기 때문에 이것은 큰 문제가 아니 었습니다.

2 번과 3 번은 Andy Wilkinson 의이 게시물 을 기반으로 합니다 . 단방향으로 작동합니다. 일단 트리거되면 결국 컨텍스트를 닫습니다.


5

SpringApplication은 종료시 ApplicationContext가 정상적으로 닫히도록 JVM에 셧다운 후크를 암시 적으로 등록합니다. 또한로 주석이 달린 모든 빈 메소드를 호출합니다 @PreDestroy. 즉 , 스프링 코어 애플리케이션에서해야하는 것처럼 부팅 애플리케이션에서 a 의 registerShutdownHook()메서드 를 명시 적으로 사용할 필요가 없습니다 ConfigurableApplicationContext.

@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");
        }
    }
}

대안으로에 @PostConstruct@PreDestroy내가 사용 initMethod하고 destroyMethod내에서 속성 @Bean주석. 따라서이 예의 경우 : @Bean(initMethod="init", destroyMethod="destroy").
acker9

@PreDestroy소수의 개발자가 알지 못하는 한 가지 단점은 이러한 메서드가 Singleton 범위를 가진 빈에 대해서만 호출된다는 것입니다. 개발자는 다른 범위에 대한 빈 수명주기의 정리 부분을 관리해야합니다
asgs

2

스프링 애플리케이션을 종료하는 여러 가지 방법이 있습니다. 하나는 다음에서 close ()를 호출하는 것입니다 ApplicationContext.

ApplicationContext ctx =
    SpringApplication.run(HelloWorldApplication.class, args);
// ...
ctx.close()

귀하의 질문 Ctrl+C은 명령을 종료하는 데 자주 사용되는을 수행하여 응용 프로그램을 닫고 싶다고 제안합니다 . 이 경우 ...

사용 endpoints.shutdown.enabled=true은 최고의 레시피가 아닙니다. 이는 애플리케이션을 종료하기 위해 엔드 포인트를 노출하는 것을 의미합니다. 따라서 사용 사례와 환경에 따라 보안을 유지해야합니다.

Ctrl+C귀하의 경우에는 매우 잘 작동합니다. 앰퍼샌드 (&)로 인해 문제가 발생한 것으로 가정합니다.

Spring Application Context는 JVM 런타임에 종료 후크를 등록했을 수 있습니다. ApplicationContext 문서를 참조하십시오 .

당신이 말했듯이 Spring Boot 가이 후크를 자동으로 구성하는지 모르겠습니다. 나는 그것이 있다고 가정한다.

에서 Ctrl+C쉘은 INT포 그라운드 애플리케이션에 신호를 보냅니다 . "실행을 중단하십시오"라는 뜻입니다. 애플리케이션은이 신호를 트랩하고 종료 (Spring에 의해 등록 된 후크) 전에 정리를 수행하거나 단순히 무시 (나쁜) 할 수 있습니다.

nohupHUP 신호를 무시하기 위해 트랩과 함께 다음 프로그램을 실행하는 명령입니다. HUP는 전화를 끊을 때 프로그램을 종료하는 데 사용됩니다 (예 : ssh 연결 닫기). 또한 프로그램이 사라진 TTY에서 차단되지 않도록 출력을 리디렉션합니다. nohupINT 신호를 무시하지 않습니다. 따라서 Ctrl+C작동을 방해하지 않습니다 .

귀하의 문제는 nohup이 아닌 앰퍼샌드 (&)로 인해 발생했다고 가정합니다. Ctrl+C포 그라운드 프로세스에 신호를 보냅니다. 앰퍼샌드는 애플리케이션이 백그라운드에서 실행되도록합니다. 하나의 솔루션 : 수행

kill -INT pid

응용 프로그램 (여기서는 JVM)이 정상적으로 종료되도록 트랩 할 수 없기 때문에 kill -9또는 사용 kill -KILL이 잘못되었습니다.

또 다른 해결책은 애플리케이션을 포 그라운드로 되 돌리는 것입니다. 그런 다음 Ctrl+C작동합니다. Bash Job 제어에 대해 자세히 살펴보십시오 fg.


2

exit()SpringApplication 클래스 의 정적 메서드를 사용 하여 스프링 부트 애플리케이션을 정상적으로 닫습니다.

public class SomeClass {
    @Autowire
    private ApplicationContext context

    public void close() {
        SpringApplication.exit(context);
    }
}

이것은 나를 위해 작동합니다. 정말 고맙습니다.
Khachornchit Songsaen

1

Spring Boot는 이제 단계적 종료를 지원합니다 (현재 시험판 버전 2.3.0.BUILD-SNAPSHOT).

활성화되면 응용 프로그램 종료에 구성 가능한 유예 기간이 포함됩니다. 이 유예 기간 동안 기존 요청은 완료 할 수 있지만 새로운 요청은 허용되지 않습니다.

다음을 사용하여 활성화 할 수 있습니다.

server.shutdown.grace-period=30s

https://docs.spring.io/spring-boot/docs/2.3.0.BUILD-SNAPSHOT/reference/html/spring-boot-features.html#boot-features-graceful-shutdown


0

maven을 사용하는 경우 Maven 앱 어셈블러 플러그인을 사용할 수 있습니다 .

데몬 mojo ( JSW 포함 )는 start / stop 인수가있는 쉘 스크립트를 출력합니다. stop의지 종료 / 우아하게 봄 응용 프로그램을 죽인다.

동일한 스크립트를 사용하여 Maven 애플리케이션을 Linux 서비스로 사용할 수 있습니다.


0

리눅스 환경에 있다면 /etc/init.d/ 내부에서 .jar 파일에 대한 심볼릭 링크를 생성하기 만하면됩니다.

sudo ln -s /path/to/your/myboot-app.jar /etc/init.d/myboot-app

그런 다음 다른 서비스와 마찬가지로 응용 프로그램을 시작할 수 있습니다.

sudo /etc/init.d/myboot-app start

응용 프로그램을 닫으려면

sudo /etc/init.d/myboot-app stop

이렇게하면 터미널을 종료 할 때 응용 프로그램이 종료되지 않습니다. 그리고 응용 프로그램은 중지 명령으로 정상적으로 종료됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.