Comment arrêter correctement une application Spring Boot?

121

Dans le document Spring Boot, ils ont déclaré que «Chaque SpringApplication enregistrera un hook d'arrêt auprès de la JVM pour s'assurer que ApplicationContext est fermé correctement à la sortie.

Lorsque je clique ctrl+csur la commande shell, l'application peut être arrêtée en douceur. Si j'exécute 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 cela fermera le processus.

Si j'exécute une commande comme nohup java -jar ProApplicaton.jar &, je ne peux pas l'utiliser ctrl+cpour l'arrêter gracieusement.

Quelle est la bonne façon de démarrer et d'arrêter une application Spring Boot dans l'environnement de production?

Chris
la source
En fonction de votre configuration, le PID de l'application est écrit dans un fichier. Vous pouvez envoyer un signal d'arrêt à ce PID. Voir également les commentaires de ce numéro .
M. Deinum
Quel signal dois-je utiliser, je ne pense pas que kill -9 soit une bonne idée, non?
Chris
5
C'est pourquoi je vous ai indiqué ce fil ... Mais quelque chose comme kill -SIGTERM <PID>ça devrait faire l'affaire.
M. Deinum
tuer avec pid, no -9
Dapeng
1
kill $ (lsof -ti tcp: <port>) - au cas où vous ne voudriez pas utiliser l'actionneur et que vous auriez besoin d'un kill rapide
Alex Nolasco

Réponses:

63

Si vous utilisez le module actionneur, vous pouvez arrêter l'application via JMXouHTTP si le point final est activé.

ajouter à application.properties:

endpoints.shutdown.enabled = true

L'URL suivante sera disponible:

/actuator/shutdown - Permet à l'application de s'arrêter en douceur (non activé par défaut).

Selon la manière dont un point de terminaison est exposé, le paramètre sensible peut être utilisé comme un indice de sécurité.

Par exemple, les points de terminaison sensibles auront besoin d'un nom d'utilisateur / mot de passe lorsqu'ils seront accédés via HTTP (ou simplement désactivés si la sécurité Web n'est pas activée).

À partir de la documentation de Spring Boot

Jean-Philippe Bond
la source
1
Je ne souhaite pas inclure de module d'actionneur. Mais j'ai trouvé que, dans mon journal de console, Spring Boot imprimait le PID dans la première ligne. Existe-t-il un moyen de laisser Spring Boot imprimer le PID dans un autre fichier sans ajouter de module d'actionneur (ApplicationPidListener)?
Chris
2
Notez que cela doit être une publication http sur ce point de terminaison. Vous pouvez l'activer avec endpoints.shutdown.enabled = true
sparkyspider
54

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'ID de processus dans un fichier

stop.sh

#!/bin/bash
kill $(cat ./pid.file)

Arrête votre application en utilisant 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 ce script à la place pour démarrer votre application. L'utilisation directe de start.sh peut laisser le shell se bloquer.

Après par exemple. re / déployer votre application, vous pouvez la redémarrer en utilisant:

sshpass -p password ssh -oStrictHostKeyChecking=no userName@www.domain.com 'cd /home/user/pathToApp; ./stop.sh; ./start_silent.sh'
Jens
la source
Cela devrait être la réponse. Je viens de confirmer qu'un arrêt du signal 15 indique au printemps de s'arrêter gracieusement.
Tchad
1
Pourquoi n'appelez-vous pas java - jar execution avec nohup dans start.sh, plutôt que d 'appeler java - jar execution dans start.sh qui est appelé avec nohup dans un autre script shell externe ??
Anand Varkey Philips
2
@AnandVarkeyPhilips La seule raison est que j'appelle parfois start.sh à partir de la ligne de commande à des fins de test, mais si vous en avez toujours besoin, nohupvous pouvez simplement fusionner les commandes
Jens
@Jens, merci pour l'info. Pouvez-vous me dire que vous faites ceci: foo.out 2> foo.err </ dev / null &
Anand Varkey Philips
1
@jens, merci de l'avoir fait avec succès et j'ai posté mon script de démarrage et d'arrêt ci-dessous. ( stackoverflow.com/questions/26547532/… )
Anand Varkey Philips
52

Quant à la réponse de @ Jean-Philippe Bond,

Voici un exemple rapide maven pour l'utilisateur maven pour 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 de terminaison sont répertoriés ici :

3.Envoyez une méthode de publication pour arrêter l'application:

curl -X POST localhost:port/shutdown

Note de sécurité:

si vous avez besoin de la méthode d'arrêt auth protected, vous devrez peut-être également

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

configurer les détails :

Jaskey
la source
Après la deuxième étape, lors de la tentative de déploiement, ce message d'erreur a été rencontré: Description: Le paramètre 0 de la méthode setAuthenticationConfiguration dans org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter nécessitait un bean de type «org.springframework.security.config .annotation.authentication.configuration.AuthenticationConfiguration 'qui n'a pas pu être trouvée. Action: envisagez de définir un bean de type «org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration» dans votre configuration.
Roberto
2
Veuillez noter: si j'ai inclus quelque chose comme server.contextPath=/appNamedans mon application.properties Alors maintenant, la commande d'arrêt deviendrait: curl -X POST localhost:8080/appName/shutdown J'espère que cela peut aider quelqu'un. J'ai dû beaucoup lutter à cause de cette erreur.
Naveen Kumar
Avec Spring Boot 1.5.8, il est suggéré, si sans sécurité, d'avoir dans application.properties endpoints.shutdown.enabled=true management.security.enabled=false.
Stefano Scarpanti
Si vous ne voulez pas exposer le point final et utiliser le fichier PID pour arrêter et démarrer via un script shell, essayez ceci: stackoverflow.com/questions/26547532/…
Anand Varkey Philips
27

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 l'état à l'aide d'un script bash. Pour écrire le PID dans un fichier, enregistrez un écouteur sur 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 Spring Boot. Référence .

Vous pouvez maintenant utiliser le script pour démarrer, arrêter ou redémarrer.

nav3916872
la source
Le script shell de démarrage et d'arrêt complet compatible générique et Jenkins peut être trouvé ici ( stackoverflow.com/questions/26547532/… )
Anand Varkey Philips
14

Toutes les réponses semblent manquer le fait que vous devrez peut-être effectuer une partie du travail de manière coordonnée pendant un arrêt normal (par exemple, dans une application d'entreprise).

@PreDestroyvous permet d'exécuter le code d'arrêt dans les beans individuels. 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();
     }
}
Michal
la source
C'est exactement ce dont j'avais besoin. Après avoir exécuté l'application, appuyez sur ctrl-c Merci @Michal
Claudio Moscoso
8

Je n'expose aucun point de terminaison et je démarre ( avec nohup en arrière-plan et sans les fichiers créés via nohup ) et je m'arrête avec le script shell (avec KILL PID gracieusement et force tuer si l'application est toujours en cours d'exécution après 3 minutes ). Je viens de créer un fichier jar exécutable et d'utiliser l'écrivain de fichier PID pour écrire un fichier PID et stocker Jar et Pid dans un dossier avec le même nom que le 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émarre le script via le pipeline jenkins également. Aucun problème pour l'instant. Fonctionne parfaitement pour 8 applications (scripts très génériques et faciles à appliquer pour n'importe quelle application).

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
Anand Varkey Philips
la source
7

Spring Boot a fourni plusieurs écouteurs d'application tout en essayant de créer un contexte d'application, l'un d'eux est ApplicationFailedEvent. On peut utiliser pour connaître la météo du contexte d'application initialisé ou non.

    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'écouteur ci-dessus à SpringApplication.

    new SpringApplicationBuilder(Application.class)
            .listeners(new ApplicationErrorListener())
            .run(args);  
user3137438
la source
La meilleure réponse que j'ai trouvée! Je vous remercie!
Vagif
[@ user3137438], En quoi est-ce différent de la journalisation dans l'annotation pré-destruction?
Anand Varkey Philips
6

À partir de Spring Boot 2.3 et versions ultérieures, il existe un arrêt progressif intégré mécanisme d' .

Pre-Spring Boot 2.3 , il n'y a pas de mécanisme d'arrêt normal prêt à l'emploi. Certains démarreurs à ressort offrent cette fonctionnalité:

  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

Je suis l'auteur de nr. 1. Le démarreur s'appelle "Hiatus for Spring Boot". Il fonctionne au niveau de l'équilibreur de charge, c'est-à-dire qu'il marque simplement le service comme OUT_OF_SERVICE, sans interférer en aucune façon avec le contexte de l'application. Cela permet de procéder à un arrêt progressif 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 JVM, vous devrez le faire avec la killcommande. Comme j'exécute tout dans des conteneurs, ce n'était pas un gros problème pour moi, car je devrai de toute façon m'arrêter et retirer le conteneur.

Les n ° 2 et 3 sont plus ou moins basés sur ce post d'Andy Wilkinson. Ils fonctionnent à sens unique - une fois déclenchés, ils finissent par fermer le contexte.

jihor
la source
5

SpringApplication enregistre implicitement un hook d'arrêt avec la JVM pour garantir que ApplicationContext est fermé correctement à la sortie. Cela appellera également toutes les méthodes bean annotées avec @PreDestroy. Cela signifie que nous n'avons pas à utiliser explicitement la registerShutdownHook()méthode a ConfigurableApplicationContextdans une application de démarrage, comme nous devons le faire dans l'application Spring Core.

@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");
        }
    }
}
Shruti Gupta
la source
Au lieu de cela @PostConstruct, @PreDestroyj'ai utilisé les attributs initMethodet destroyMethoddans l' @Beanannotation. Donc , pour cet exemple: @Bean(initMethod="init", destroyMethod="destroy").
acker9
Un problème que @PreDestroypeu de développeurs ignorent est que ces méthodes ne sont appelées que pour les beans avec une portée Singleton. Les développeurs doivent gérer la partie nettoyage du cycle de vie du bean pour les autres portées
asgs
2

Il existe de nombreuses façons d'arrêter une application à ressort. La première consiste à appeler close () sur le ApplicationContext:

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

Votre question suggère que vous souhaitez fermer votre application en faisant Ctrl+C, qui est fréquemment utilisé pour terminer une commande. Dans ce cas...

L'utilisation endpoints.shutdown.enabled=truen'est pas la meilleure recette. Cela signifie que vous exposez un point de terminaison pour mettre fin à votre application. Ainsi, en fonction de votre cas d'utilisation et de votre environnement, vous devrez le sécuriser ...

Ctrl+Cdevrait très bien fonctionner dans votre cas. Je suppose que votre problème est causé par l'esperluette (&) Plus d'explication:

Un contexte d'application Spring peut avoir enregistré un hook d'arrêt avec le runtime JVM. Consultez la documentation ApplicationContext .

Je ne sais pas si Spring Boot configure ce hook automatiquement comme vous l'avez dit. Je suppose que oui.

Activé Ctrl+C, votre shell envoie un INTsignal à l'application au premier plan. Cela signifie "veuillez interrompre votre exécution". L'application peut intercepter ce signal et effectuer un nettoyage avant sa fin (le hook enregistré par Spring), ou simplement l'ignorer (mauvais).

nohupest une commande qui exécute le programme suivant avec un trap 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 TTY disparu. nohupn'ignore PAS le signal INT. Cela n'empêche donc PAS Ctrl+Cde travailler.

Je suppose que votre problème est causé par l'esperluette (&), pas par nohup. Ctrl+Cenvoie un signal aux processus de premier plan. L'esperluette entraîne l'exécution de votre application en arrière-plan. Une solution: faire

kill -INT pid

Utilisez kill -9ou kill -KILLest incorrect car l'application (ici la JVM) ne peut pas l'intercepter pour se terminer correctement.

Une autre solution consiste à ramener votre application au premier plan. Puis Ctrl+Cfonctionnera. Jetez un œil sur le contrôle Bash Job, plus précisément sur fg.

mcoolive
la source
2

Utilisez la exit()méthode statique de la classe SpringApplication pour fermer correctement votre application de démarrage de printemps.

public class SomeClass {
    @Autowire
    private ApplicationContext context

    public void close() {
        SpringApplication.exit(context);
    }
}
rw026
la source
Cela fonctionne pour moi. Merci beaucoup.
Khachornchit Songsaen
1

Spring Boot prend désormais en charge l'arrêt progressif (actuellement dans les versions préliminaires, 2.3.0.BUILD-SNAPSHOT)

Lorsqu'il est activé, l'arrêt de l'application comprendra une période de grâce d'une durée configurable. Pendant cette période de grâce, les demandes existantes seront autorisées à se terminer mais aucune nouvelle demande ne sera autorisée

Vous pouvez l'activer avec:

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

cmlonder
la source
0

Si vous utilisez maven, vous pouvez utiliser le plugin assembleur Maven App .

Le démon mojo (qui intègre JSW ) produira un script shell avec l'argument start / stop. Le stopva arrêter / tuer gracieusement votre application Spring.

Le même script peut être utilisé pour utiliser votre application maven en tant que service linux.

Nicolas Labrot
la source
0

Si vous êtes dans un environnement Linux, tout ce que vous avez à faire est de créer un lien symbolique vers votre fichier .jar depuis /etc/init.d/

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

Ensuite, vous pouvez démarrer 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 s'arrêtera gracieusement avec la commande d'arrêt.

Johna
la source