彼らは、Spring Boot Documentで、「各SpringApplicationがシャットダウンフックをJVMに登録して、ApplicationContextが終了時に正常に閉じられるようにする」と述べました。
Shellコマンドで_ctrl+c
をクリックすると、アプリケーションを正常にシャットダウンできます。実稼働マシンでアプリケーションを実行する場合、コマンドJava -jar ProApplicaton.jar
を使用する必要があります。ただし、シェルターミナルを閉じることはできません。そうしないと、プロセスが閉じられます。
Nohup Java -jar ProApplicaton.jar &
のようなコマンドを実行すると、ctrl+c
を使用して正常にシャットダウンできません。
実稼働環境でSpring Boot Applicationを起動および停止する正しい方法は何ですか?
アクチュエータモジュールを使用している場合、エンドポイントが有効になっている場合はJMX
またはHTTP
を使用してアプリケーションをシャットダウンできます(endpoints.shutdown.enabled=true
をapplication.properties
ファイルに追加します)。
/shutdown
-アプリケーションの正常なシャットダウンを許可します(デフォルトでは有効になっていません)。
エンドポイントの公開方法に応じて、機密パラメータをセキュリティヒントとして使用できます。たとえば、機密性の高いエンドポイントでは、HTTP
を介してアクセスするときにユーザー名/パスワードが必要になります(Webセキュリティが有効になっていない場合は単に無効になります)。
@ 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>
詳細の設定 :
コードの変更やシャットダウンエンドポイントの公開を必要としない別のオプションを次に示します。次のスクリプトを作成し、それらを使用してアプリを起動および停止します。
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'
Springbootアプリケーションを作成してPIDをファイルに書き込み、pidファイルを使用して停止または再起動するか、bashスクリプトを使用してステータスを取得できます。 PIDをファイルに書き込むには、以下に示すようにApplicationPidFileWriterを使用してSpringApplicationにリスナーを登録します。
SpringApplication application = new SpringApplication(Application.class);
application.addListeners(new ApplicationPidFileWriter("./bin/app.pid"));
application.run();
次に、bashスクリプトを記述して、スプリングブートアプリケーションを実行します。 参照 。
これで、スクリプトを使用して、開始、停止、または再起動できます。
エンドポイントを公開せず、(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);
SpringApplicationはシャットダウンフックをJVMに暗黙的に登録して、終了時にApplicationContextが適切に閉じられるようにします。また、@PreDestroy
アノテーションが付けられたすべてのBeanメソッドを呼び出します。つまり、Springコアアプリケーションで行う必要があるように、ブートアプリケーションでConfigurableApplicationContext
のregisterShutdownHook()
メソッドを明示的に使用する必要はありません。
@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");
}
}
}
すべての答えは、正常なシャットダウン中(エンタープライズアプリケーションなど)に調整された方法で作業の一部を完了する必要があるかもしれないという事実を欠いているようです。
@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();
}
}
Spring Boot 1.5の時点では、すぐに使える正常なシャットダウンメカニズムはありません。一部のスプリングブートスターターは、この機能を提供します。
私はnrの著者です。 1.スターターの名前は「Hiatus for Spring Boot」です。これはロードバランサーレベルで動作します。つまり、サービスをOUT_OF_SERVICEとしてマークするだけで、アプリケーションコンテキストを一切妨害しません。これにより、正常なシャットダウンが可能になり、必要に応じて、サービスをしばらくの間サービスから外し、その後サービスを再開することができます。欠点は、JVMが停止しないことです。kill
コマンドを使用して実行する必要があります。すべてをコンテナで実行しているので、とにかくコンテナを停止して削除する必要があるため、これは大したことではありませんでした。
2番と3番は、Andy Wilkinsonによる this post にほぼ基づいています。これらは一方向に機能します。一度トリガーされると、最終的にコンテキストを閉じます。
これらは、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
をご覧ください。
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
この方法では、ターミナルを終了してもアプリケーションは終了しません。そして、アプリケーションは停止コマンドで正常にシャットダウンします。
Mavenを使用している場合は、 Maven App assembler plugin を使用できます。
デーモンmojo (これは JSW を埋め込みます)は、start/stop引数付きのシェルスクリプトを出力します。 stop
は、Springアプリケーションを正常にシャットダウン/強制終了します。
同じスクリプトを使用して、MavenアプリケーションをLinuxサービスとして使用できます。
SpringApplicationクラスで静的exit()
メソッドを使用して、スプリングブートアプリケーションを正常に閉じます。
public class SomeClass {
@Autowire
private ApplicationContext context
public void close() {
SpringApplication.exit(context);
}
}