web-dev-qa-db-fra.com

Comment arrêter correctement une application de démarrage Spring?

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?

81
Chris

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

48
Jean-Philippe Bond

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

Note de sécurité:

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>

configurer les détails :

42
Jaskey

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'
29
Jens

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.

25
nav3916872

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
5

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);  
4
user3137438

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

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

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é:

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

3
jihor

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.

0
Johna

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.

0
Nicolas Labrot

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. Nohupn'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.

0
mcoolive

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);
    }
}
0
rw026