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


120

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

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

のようなコマンドを実行nohup java -jar ProApplicaton.jar &するctrl+cと、正常にシャットダウンするために使用できなくなります。

本番環境でSpring Boot Applicationを開始および停止する正しい方法は何ですか?


構成に応じて、アプリケーションのPIDがファイルに書き込まれます。そのPIDにkillシグナルを送信できます。この号のコメントもご覧ください。
M. Deinum 2014年

どちらの信号を使うべきか、kill -9は良い考えではないと思いますよね?
Chris

5
だから私はあなたにそのスレッドを指さしたのですが…のようなものkill -SIGTERM <PID>がトリックをするべきです。
M. Deinum 2014年

pidで殺す、-9なし
Dapeng

1
kill $(lsof -ti tcp:<port>)-アクチュエータを使用したくなく、クイックキルが必要な場合
Alex Nolasco

回答:


61

アクチュエータモジュールを使用している場合、JMXまたはHTTPエンドポイントが有効になっている場合、アプリケーションをシャットダウンできます。

に追加application.properties

endpoints.shutdown.enabled = true

次のURLが利用可能になります。

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

エンドポイントがどのように公開されるかに応じて、機密パラメータはセキュリティのヒントとして使用される場合があります。

たとえば、機密性の高いエンドポイントは、アクセス時にユーザー名/パスワードを要求しますHTTP(またはWebセキュリティが有効になっていない場合は単に無効にします)。

春ブーツのドキュメント


1
アクチュエータモジュールを含めたくありません。しかし、コンソールログで、Spring Bootが最初の行にPIDを出力していることがわかりました。Spring Bootにアクチュエーターモジュール(ApplicationPidListener)を追加せずにPIDを他のファイルに印刷させる方法はありますか?
Chris

2
これは、このエンドポイントへのhttp投稿でなければならないことに注意してください。endpoints.shutdown.enabled = trueで有効にできます
sparkyspider 2015年

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のシャットダウンにより、Springは正常にシットダウンするように指示されます。
チャド

1
別の外部シェルスクリプト内のnohupで呼び出されるstart.sh内のjava-jar実行を呼び出すのではなく、java-jar実行をstart.sh内のnohupで呼び出さないのはなぜですか?
Anand Varkey Philips、

2
@AnandVarkeyPhilips唯一の理由は、テスト目的でコマンドラインからstart.shを呼び出すことがありますが、常に必要なnohup場合は、コマンドをマージすることができます
Jens

@イェンス、情報をありがとう。これを行うと教えてください:foo.out 2> foo.err </ dev / null&
Anand Varkey Philips

1
@jens、ありがとうございました。以下に開始スクリプトと停止スクリプトを掲載しました。(stackoverflow.com/questions/26547532/...
アナンドVarkeyフィリップス

52

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

以下は、mavenユーザーがHTTPエンドポイントを設定して、Spring-boot-starter-actuatorを使用してSpring Boot 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

セキュリティノート:

保護されたシャットダウンメソッドauthが必要な場合は、

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

詳細を設定


2番目のステップの後、デプロイしようとすると、次のエラーメッセージが発生しました。 .annotation.authentication.configuration.AuthenticationConfiguration 'が見つかりませんでした。処置:構成でタイプ「org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration」のBeanを定義することを検討してください。
Roberto

2
注意:server.contextPath=/appNameapplication.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、2018年

エンドポイントを公開せず、シェルスクリプトを介して停止および開始するためにPIDファイルを使用する場合は、これを試してください
。stackoverflow.com/ questions / 26547532

27

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アプリケーションを実行します。リファレンス

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


ジェネリックとジェンキンス互換性のある完全な開始と停止シェルスクリプトがここで見つけることができた(stackoverflow.com/questions/26547532/...
アナンドVarkeyフィリップス


14

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

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

これはまさに私が必要としたものです。アプリケーションの実行後、ctrl-cを押します。@Michalに感謝
Claudio Moscoso

8

エンドポイントを公開せずに開始し(バックグラウンドでnohupを使用し、nohupで作成されたoutファイルなし)、シェルスクリプトで停止します(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はいくつかのアプリケーションリスナーを提供しましたが、アプリケーションコンテキストを作成しようとしましたが、その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);  

私が見つけた最良の答え!ありがとうございました!
Vagif

[@ user3137438]、pre destroyアノテーション内でのロギングとどう違うのですか?
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はシャットダウンフックを暗黙的にJVMに登録して、終了時にApplicationContextが正常に閉じられるようにします。これは、で注釈されたすべてのBeanメソッドも呼び出します@PreDestroy。つまり、Spring Coreアプリケーションで行う必要があるように、ブートアプリケーションで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および@PreDestroyIを使用initMethodし、destroyMethod内部に属性@Bean注釈。この例ではそう:@Bean(initMethod="init", destroyMethod="destroy")
acker9 2018

@PreDestroy少数の開発者が知らないかもしれないことについての1つのキャッチは、そのようなメソッドはシングルトンスコープのBeanに対してのみ呼び出されることです。開発者が他のスコープのBeanのライフサイクルのクリーンアップの部分を管理する必要が
asgs

2

これらは、Springアプリケーションをシャットダウンする多くの方法です。1つは、次のように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フォアグラウンドプロセスにシグナルを送信します。アンパサンドを使用すると、アプリケーションがバックグラウンドで実行されます。1つの解決策:行う

kill -INT pid

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

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


2

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

public class SomeClass {
    @Autowire
    private ApplicationContext context

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

これは私にとってはうまくいきます。どうもありがとうございます。
Khachornchitソンセン

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 Appアセンブラプラグインを使用できます

デーモンmojo(これはJSWを組み込んでいます)は、start / stop引数を持つシェルスクリプトを出力します。stop意志のシャットダウンが/優雅Springアプリケーションを殺します。

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


0

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.