web-dev-qa-db-ja.com

Spring Bootアプリケーションを正しい方法でシャットダウンする方法は?

彼らは、Spring Boot Documentで、「各SpringApplicationがシャットダウンフックをJVMに登録して、ApplicationContextが終了時に正常に閉じられるようにする」と述べました。

Shellコマンドで_ctrl+cをクリックすると、アプリケーションを正常にシャットダウンできます。実稼働マシンでアプリケーションを実行する場合、コマンドJava -jar ProApplicaton.jarを使用する必要があります。ただし、シェルターミナルを閉じることはできません。そうしないと、プロセスが閉じられます。

Nohup Java -jar ProApplicaton.jar &のようなコマンドを実行すると、ctrl+cを使用して正常にシャットダウンできません。

実稼働環境でSpring Boot Applicationを起動および停止する正しい方法は何ですか?

90
Chris

アクチュエータモジュールを使用している場合、エンドポイントが有効になっている場合はJMXまたはHTTPを使用してアプリケーションをシャットダウンできます(endpoints.shutdown.enabled=trueapplication.propertiesファイルに追加します)。

/shutdown-アプリケーションの正常なシャットダウンを許可します(デフォルトでは有効になっていません)。

エンドポイントの公開方法に応じて、機密パラメータをセキュリティヒントとして使用できます。たとえば、機密性の高いエンドポイントでは、HTTPを介してアクセスするときにユーザー名/パスワードが必要になります(Webセキュリティが有効になっていない場合は単に無効になります)。

Spring boot documentation から

53

@ Jean-Philippe Bondの答えについては、

以下は、mavenユーザーがHTTPエンドポイントを設定して、spring-boot-starter-actuatorを使用してスプリングブートWebアプリをシャットダウンし、コピーアンドペーストできるようにするためのmavenの簡単な例です。

1.Maven 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>

詳細の設定

44
Jaskey

コードの変更やシャットダウンエンドポイントの公開を必要としない別のオプションを次に示します。次のスクリプトを作成し、それらを使用してアプリを起動および停止します。

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 [email protected] 'cd /home/user/pathToApp; ./stop.sh; ./start_silent.sh'
36
Jens

Springbootアプリケーションを作成してPIDをファイルに書き込み、pidファイルを使用して停止または再起動するか、bashスクリプトを使用してステータスを取得できます。 PIDをファイルに書き込むには、以下に示すようにApplicationPidFileWriterを使用してSpringApplicationにリスナーを登録します。

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

次に、bashスクリプトを記述して、スプリングブートアプリケーションを実行します。 参照

これで、スクリプトを使用して、開始、停止、または再起動できます。

25
nav3916872

エンドポイントを公開せず、(Nohupをバックグラウンドで、Nohupで作成したoutファイルなしで)開始し、Shell script(withKID 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は、アプリケーションコンテキストを作成しようとするときにいくつかのアプリケーションリスナーを提供し、そのうちの1つは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);  
4
user3137438

SpringApplicationはシャットダウンフックをJVMに暗黙的に登録して、終了時にApplicationContextが適切に閉じられるようにします。また、@PreDestroyアノテーションが付けられたすべてのBeanメソッドを呼び出します。つまり、Springコアアプリケーションで行う必要があるように、ブートアプリケーションでConfigurableApplicationContextregisterShutdownHook()メソッドを明示的に使用する必要はありません。

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

すべての答えは、正常なシャットダウン中(エンタープライズアプリケーションなど)に調整された方法で作業の一部を完了する必要があるかもしれないという事実を欠いているようです。

@PreDestroyを使用すると、個々のBeanでシャットダウンコードを実行できます。より洗練されたものは次のようになります。

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

Spring Boot 1.5の時点では、すぐに使える正常なシャットダウンメカニズムはありません。一部のスプリングブートスターターは、この機能を提供します。

  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による this post にほぼ基づいています。これらは一方向に機能します。一度トリガーされると、最終的にコンテキストを閉じます。

3
jihor

これらは、Springアプリケーションをシャットダウンする多くの方法です。 1つは、ApplicationContextでclose()を呼び出すことです。

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

あなたの質問は、Ctrl+Cを実行してアプリケーションを閉じることを示唆しています。これは、コマンドの終了によく使用されます。この場合...

endpoints.shutdown.enabled=trueを使用するのは最良のレシピではありません。これは、エンドポイントを公開してアプリケーションを終了することを意味します。したがって、ユースケースと環境に応じて、それを保護する必要があります...

Ctrl+Cは、あなたの場合に非常にうまく機能するはずです。あなたの問題はアンパサンド(&)が原因であると思われます。

Spring Application ContextはシャットダウンフックをJVMランタイムに登録している場合があります。 ApplicationContext documentation を参照してください。

あなたが言ったようにSpring Bootがこのフックを自動的に設定するかどうかはわかりません。そうだと思います。

Ctrl+Cで、シェルはINT信号をフォアグラウンドアプリケーションに送信します。 「実行を中断してください」という意味です。アプリケーションはこのシグナルをトラップし、終了する前にクリーンアップを実行するか(Springによって登録されたフック)、単に無視する(悪い)ことができます。

Nohupは、HUPシグナルを無視するトラップで次のプログラムを実行するコマンドです。 HUPは、電話を切るときにプログラムを終了するために使用されます(たとえば、ssh接続を閉じます)。さらに、プログラムが消失したTTYでブロックされないように、出力をリダイレクトします。 NohupはINT信号を無視しません。したがって、Ctrl+Cが機能することを妨げません。

あなたの問題は、Nohupではなく、アンパサンド(&)が原因であると考えています。 Ctrl+Cは、フォアグラウンドプロセスにシグナルを送信します。アンパサンドにより、アプリケーションがバックグラウンドで実行されます。 1つの解決策:行う

kill -INT pid

kill -9またはkill -KILLの使用は、アプリケーション(ここではJVM)がトラップして正常に終了できないため、不適切です。

別の解決策は、アプリケーションをフォアグラウンドに戻すことです。その後、Ctrl+Cが機能します。 Bash Jobコントロール、より正確にはfgをご覧ください。

2
mcoolive

Linux環境にいる場合、/ 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

この方法では、ターミナルを終了してもアプリケーションは終了しません。そして、アプリケーションは停止コマンドで正常にシャットダウンします。

0
Johna

Mavenを使用している場合は、 Maven App assembler plugin を使用できます。

デーモンmojo (これは JSW を埋め込みます)は、start/stop引数付きのシェルスクリプトを出力します。 stopは、Springアプリケーションを正常にシャットダウン/強制終了します。

同じスクリプトを使用して、MavenアプリケーションをLinuxサービスとして使用できます。

0
Nicolas Labrot

SpringApplicationクラスで静的exit()メソッドを使用して、スプリングブートアプリケーションを正常に閉じます。

public class SomeClass {
    @Autowire
    private ApplicationContext context

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