Dans le document d'amorçage printanier, ils ont déclaré que "Chaque application SpringApplication enregistrera un point d'arrêt avec la machine virtuelle Java pour s'assurer que le champ ApplicationContext est fermé normalement à la sortie".
Lorsque je clique sur ctrl+c
dans la commande Shell, l’application peut être arrêtée normalement. Si je lance l'application sur une machine de production, je dois utiliser la commande Java -jar ProApplicaton.jar
. Mais je ne peux pas fermer le terminal Shell, sinon le processus sera fermé.
Si j'exécute une commande telle que Nohup Java -jar ProApplicaton.jar &
, je ne peux pas utiliser ctrl+c
pour l'éteindre en douceur.
Quelle est la bonne façon de démarrer et d’arrêter une application Spring Boot dans l’environnement de production?
Si vous utilisez le module d'actionneur, vous pouvez arrêter l'application via JMX
ou HTTP
si le noeud final est activé (ajoutez endpoints.shutdown.enabled=true
à votre fichier application.properties
).
/shutdown
- Permet à l'application de s'éteindre normalement (non activée par défaut).
En fonction de la manière dont un terminal est exposé, le paramètre sensitive peut être utilisé comme indicateur de sécurité. Par exemple, les points de terminaison sensibles nécessiteront un nom d'utilisateur/mot de passe lorsqu'ils sont accessibles via HTTP
(ou simplement désactivés si la sécurité Web n'est pas activée).
À partir de la documentation de démarrage Spring
Quant à la réponse de Jean-Philippe Bond,
voici un exemple rapide maven permettant à l'utilisateur maven de configurer le point de terminaison HTTP pour arrêter une application Web Spring Boot à l'aide de spring-boot-starter-actuator afin que vous puissiez copier et coller:
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
Tous les points finaux sont répertoriés ici :
3.Envoyez une méthode de publication pour fermer l'application:
curl -X POST localhost:port/shutdown
si vous avez besoin de la méthode d'arrêt auth protected, vous aurez peut-être aussi besoin de
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
Voici une autre option qui ne vous oblige pas à modifier le code ou à exposer un point de terminaison d'arrêt. Créez les scripts suivants et utilisez-les pour démarrer et arrêter votre application.
start.sh
#!/bin/bash
Java -jar myapp.jar & echo $! > ./pid.file &
Démarre votre application et enregistre l'identifiant du processus dans un fichier.
stop.sh
#!/bin/bash
kill $(cat ./pid.file)
Arrête votre application à l'aide de l'ID de processus enregistré.
start_silent.sh
#!/bin/bash
Nohup ./start.sh > foo.out 2> foo.err < /dev/null &
Si vous devez démarrer l'application à l'aide de ssh à partir d'une machine distante ou d'un pipeline CI, utilisez plutôt ce script pour démarrer votre application. Utiliser start.sh directement peut laisser le shell s’accrocher.
Après par exemple. Pour ré/déployer votre application, vous pouvez la redémarrer en utilisant:
sshpass -p password ssh -oStrictHostKeyChecking=no [email protected] 'cd /home/user/pathToApp; ./stop.sh; ./silent_start.sh'
Vous pouvez faire en sorte que l'application springboot écrive le PID dans un fichier et vous pouvez utiliser le fichier pid pour arrêter ou redémarrer ou obtenir le statut à l'aide d'un script bash. Pour écrire le PID dans un fichier, enregistrez un écouteur dans SpringApplication à l'aide de ApplicationPidFileWriter, comme indiqué ci-dessous:
SpringApplication application = new SpringApplication(Application.class);
application.addListeners(new ApplicationPidFileWriter("./bin/app.pid"));
application.run();
Ensuite, écrivez un script bash pour exécuter l'application de démarrage printanier. Référence .
Vous pouvez maintenant utiliser le script pour démarrer, arrêter ou redémarrer.
Je n'expose aucun point de terminaison et commence ( avec Nohup en arrière-plan et sans les fichiers créés via Nohup ) et m'arrête avec le script shell (avec KILL PID et force kill à tuer si l'application est toujours en cours d'exécution après 3 min ). Je viens de créer un fichier jar exécutable et d’utiliser un graveur de fichier PID pour écrire le fichier PID et pour stocker les fichiers Jar et Pid dans un dossier portant le même nom que celui du nom de l’application et les scripts Shell ont également le même nom, avec start et stop à la fin. J'appelle ces scripts d'arrêt et démarrage de script via pipeline jenkins également. Aucun problème jusqu'à présent. Fonctionne parfaitement pour 8 applications (scripts très génériques et faciles à appliquer pour toutes les applications).
Classe principale
@SpringBootApplication
public class MyApplication {
public static final void main(String[] args) {
SpringApplicationBuilder app = new SpringApplicationBuilder(MyApplication.class);
app.build().addListeners(new ApplicationPidFileWriter());
app.run();
}
}
FICHIER YML
spring.pid.fail-on-write-error: true
spring.pid.file: /server-path-with-folder-as-app-name-for-ID/appName/appName.pid
Voici le script de démarrage (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."
Voici le script d'arrêt (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 a fourni plusieurs applications d'écoute tout en essayant de créer un contexte d'application, ApplicationFailedEvent. Nous pouvons utiliser pour connaître le temps d'application ou non du contexte d'application.
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);
}
}
}
Ajoutez à la classe d'écoute ci-dessus à SpringApplication.
new SpringApplicationBuilder(Application.class)
.listeners(new ApplicationErrorListener())
.run(args);
SpringApplication enregistre implicitement un point d'arrêt avec la machine virtuelle Java pour s'assurer que ApplicationContext se ferme normalement à la sortie. Cela appellera également toutes les méthodes de bean annotées avec @PreDestroy
. Cela signifie que nous n'avons pas à utiliser explicitement la méthode registerShutdownHook()
d'une ConfigurableApplicationContext
dans une application de démarrage, comme c'est le cas dans l'application core Spring.
@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");
}
}
}
Toutes les réponses semblent passer à côté du fait qu'il peut être nécessaire de terminer une partie du travail de manière coordonnée lors d'un arrêt en douceur (par exemple, dans une application d'entreprise).
@PreDestroy
vous permet d'exécuter le code d'arrêt dans les différents haricots. Quelque chose de plus sophistiqué ressemblerait à ceci:
@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();
}
}
A partir de Spring Boot 1.5, il n’existe aucun mécanisme d’arrêt progressif et prêt à l’emploi . Certains démarreurs Spring-Boot offrent cette fonctionnalité:
Je suis l'auteur de nr. 1. Le démarreur est nommé "Hiatus for Spring Boot". Cela fonctionne au niveau de l’équilibreur de charge, c’est-à-dire qu’il marque simplement le service comme OUT_OF_SERVICE, sans interférer de quelque manière que ce soit avec le contexte de l’application. Cela permet d'effectuer un arrêt en douceur et signifie que, si nécessaire, le service peut être mis hors service pendant un certain temps, puis ramené à la vie. L'inconvénient est que cela n'arrête pas la machine virtuelle, vous devrez le faire avec la commande kill
. Comme je transportais tout dans des conteneurs, ce n'était pas un gros problème pour moi, car je vais devoir arrêter et retirer le conteneur de toute façon.
Nos 2 et 3 sont plus ou moins basés sur ce post de Andy Wilkinson. Ils travaillent à sens unique - une fois déclenchés, ils finissent par fermer le contexte.
Si vous êtes dans un environnement linux, tout ce que vous avez à faire est de créer un lien symbolique vers votre fichier .jar à partir de /etc/init.d/
Sudo ln -s /path/to/your/myboot-app.jar /etc/init.d/myboot-app
Ensuite, vous pouvez lancer l'application comme n'importe quel autre service.
Sudo /etc/init.d/myboot-app start
Pour fermer l'application
Sudo /etc/init.d/myboot-app stop
De cette façon, l'application ne se terminera pas lorsque vous quitterez le terminal. Et l'application va s'arrêter gracieusement avec la commande stop.
Si vous utilisez maven, vous pouvez utiliser le plugin Maven App Assembler .
Le démon mojo (qui intègre JSW ) génère un script Shell avec l’argument start/stop. La stop
va arrêter/tuer gracieusement votre application Spring.
Le même script peut être utilisé pour utiliser votre application maven en tant que service linux.
Il existe de nombreuses façons d’arrêter une application au printemps. La première consiste à appeler close () sur la ApplicationContext
:
ApplicationContext ctx =
SpringApplication.run(HelloWorldApplication.class, args);
// ...
ctx.close()
Votre question suggère que vous souhaitiez fermer votre application en utilisant Ctrl+C
, qui est fréquemment utilisé pour mettre fin à une commande. Dans ce cas...
Utiliser endpoints.shutdown.enabled=true
n'est pas la meilleure recette. Cela signifie que vous exposez un point final pour mettre fin à votre application. Ainsi, en fonction de votre cas d'utilisation et de votre environnement, vous devrez le sécuriser ...
Ctrl+C
devrait très bien fonctionner dans votre cas. Je suppose que votre problème est causé par l'esperluette (&) Plus d'explications:
Un contexte d'application Spring peut avoir enregistré un crochet d'arrêt avec le runtime JVM. Voir Documentation ApplicationContext .
Je ne sais pas si Spring Boot configure ce hook automatiquement comme vous l'avez dit. Je suppose que c'est.
Sur Ctrl+C
, votre shell envoie un signal INT
à l'application de premier plan. Cela signifie "veuillez interrompre votre exécution". L'application peut intercepter ce signal et effectuer le nettoyage avant sa fin (le hook enregistré par Spring), ou simplement l'ignorer (mauvais).
Nohup
est une commande qui exécute le programme suivant avec une interruption pour ignorer le signal HUP. HUP est utilisé pour terminer le programme lorsque vous raccrochez (fermez votre connexion SSH par exemple). De plus, il redirige les sorties pour éviter que votre programme ne se bloque sur un téléscripteur disparu. Nohup
n'ignore pas le signal INT. Donc, cela n'empêche PAS Ctrl+C
de fonctionner.
Je suppose que votre problème est causé par l'esperluette (&), pas par Nohup. Ctrl+C
envoie un signal aux processus de premier plan. L'esperluette provoque l'exécution de votre application en arrière-plan. Une solution: faire
kill -INT pid
L'utilisation de kill -9
ou kill -KILL
est incorrecte car l'application (ici la JVM) ne peut pas l'intercepter pour se terminer normalement.
Une autre solution consiste à ramener votre application au premier plan. Alors Ctrl+C
fonctionnera. Jetez un coup d'œil au contrôle de Bash Job, plus précisément à fg
.
Utilisez la méthode statique exit()
de la classe SpringApplication pour fermer votre application de démarrage printanier de manière élégante.
public class SomeClass {
@Autowire
private ApplicationContext context
public void close() {
SpringApplication.exit(context);
}
}