it-swarm.com.de

Wie kann ich eine Spring Boot-Anwendung ordnungsgemäß herunterfahren?

In dem Spring Boot-Dokument wurde folgendes gesagt: Jede SpringApplication registriert einen Shutdown-Hook bei der JVM, um sicherzustellen, dass der ApplicationContext beim Beenden ordnungsgemäß geschlossen wird.

Wenn ich im Shell-Befehl auf ctrl+c klicke, kann die Anwendung ordnungsgemäß heruntergefahren werden. Wenn ich die Anwendung auf einer Produktionsmaschine ausführen, muss ich den Befehl Java -jar ProApplicaton.jar verwenden. Ich kann das Shell-Terminal jedoch nicht schließen, andernfalls wird der Vorgang beendet.

Wenn ich einen Befehl wie Nohup Java -jar ProApplicaton.jar & ausführen, kann ich ctrl+c nicht verwenden, um ihn ordnungsgemäß herunterzufahren.

Wie kann ich eine Spring Boot-Anwendung in der Produktionsumgebung richtig starten und stoppen?

81
Chris

Wenn Sie das Stellgliedmodul verwenden, können Sie die Anwendung über JMX oder HTTP herunterfahren, wenn der Endpunkt aktiviert ist (fügen Sie endpoints.shutdown.enabled=true zu Ihrer application.properties-Datei hinzu).

/shutdown - Ermöglicht, dass die Anwendung ordnungsgemäß heruntergefahren wird (standardmäßig nicht aktiviert). 

Abhängig davon, wie ein Endpunkt verfügbar gemacht wird, kann der sensitive Parameter als Sicherheitshinweis verwendet werden. Zum Beispiel benötigen sensible Endpunkte einen Benutzernamen/ein Kennwort, wenn auf sie über HTTP zugegriffen wird (oder einfach deaktiviert werden, wenn die Websicherheit nicht aktiviert ist).

Aus der Spring Boot Dokumentation

48

Zur Antwort von @ Jean-Philippe Bond: 

hier ist ein schnelles Beispiel für den Maven-Benutzer, der den HTTP-Endpunkt so konfiguriert, dass eine Spring-Boot-Webanwendung mithilfe des Spring-Boot-Starter-Stellglieds heruntergefahren wird, damit Sie ihn kopieren und einfügen können:

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

Alle Endpunkte sind aufgelistet hier :

3.Senden Sie eine Post-Methode zum Herunterfahren der App:

curl -X POST localhost:port/shutdown

Sicherheitshinweis:

wenn Sie die Abschaltmethode auth protected benötigen, benötigen Sie möglicherweise auch 

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

Details konfigurieren :

42
Jaskey

Hier ist eine weitere Option, bei der Sie den Code nicht ändern oder einen Endpunkt für das Herunterfahren verfügbar machen müssen. Erstellen Sie die folgenden Skripts und verwenden Sie sie zum Starten und Stoppen Ihrer App. 

start.sh

#!/bin/bash
Java -jar myapp.jar & echo $! > ./pid.file &

Startet Ihre App und speichert die Prozess-ID in einer Datei

stop.sh

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

Stoppt Ihre App mit der gespeicherten Prozess-ID

start_silent.sh

#!/bin/bash
Nohup ./start.sh > foo.out 2> foo.err < /dev/null &

Wenn Sie die App mit ssh von einem Remote-Computer oder einer CI-Pipeline aus starten müssen, verwenden Sie stattdessen dieses Skript, um Ihre App zu starten. Durch die direkte Verwendung von start.sh kann die Shell hängen bleiben.

Nach z. Re/Deployment Ihrer App können Sie neu starten mit:

sshpass -p password ssh -oStrictHostKeyChecking=no [email protected] 'cd /home/user/pathToApp; ./stop.sh; ./silent_start.sh'
29
Jens

Sie können die Springboot-Anwendung so einrichten, dass die PID in eine Datei geschrieben wird, und Sie können die PID-Datei zum Anhalten oder Neustarten verwenden oder den Status mithilfe eines Bash-Skripts abrufen. Um die PID in eine Datei zu schreiben, registrieren Sie einen Listener bei SpringApplication mit ApplicationPidFileWriter wie folgt: 

SpringApplication application = new SpringApplication(Application.class);
application.addListeners(new ApplicationPidFileWriter("./bin/app.pid"));
application.run();

Schreiben Sie dann ein Bash-Skript, um die Spring-Boot-Anwendung auszuführen. Referenz

Jetzt können Sie das Skript zum Starten, Stoppen oder Neustarten verwenden.

25
nav3916872

Ich mache keine Endpunkte verfügbar und starte ( mit Nohup im Hintergrund und ohne durch Nohup erstellte aus -Dateien ) und halte mit Shell-Skript an (mit KILL PID würdevoll und erzwinge kill, wenn die App nach 3 Minuten noch läuft ). Ich erstelle ein ausführbares jar und verwende PID File Writer, um eine PID-Datei zu schreiben und Jar und Pid in einem Ordner mit demselben Namen wie dem Namen der Anwendung zu speichern. Shell-Skripte haben am Ende auch den gleichen Namen mit start und stop. Ich rufe diese Stoppskripts auf und starte das Skript auch über die Jenkins-Pipeline. Bisher keine Probleme. Perfekt für 8 Anwendungen (Sehr generische Skripte und einfach für jede App zu installieren).

Hauptklasse

@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-DATEI

spring.pid.fail-on-write-error: true
spring.pid.file: /server-path-with-folder-as-app-name-for-ID/appName/appName.pid

Hier ist das Startskript (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."

Hier ist das Stoppskript (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 stellte mehrere Anwendungslistener zur Verfügung, während versucht wurde, einen Anwendungskontext zu erstellen. Einer davon ist ApplicationFailedEvent. Wir können verwenden, um zu wissen, ob der Anwendungskontext initialisiert ist oder nicht.

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

Zu obiger Listener-Klasse zu SpringApplication hinzufügen.

    new SpringApplicationBuilder(Application.class)
            .listeners(new ApplicationErrorListener())
            .run(args);  
4
user3137438

SpringApplication registriert implizit einen Shutdown-Hook bei der JVM, um sicherzustellen, dass ApplicationContext beim Beenden ordnungsgemäß geschlossen wird. Das ruft auch alle Bean-Methoden auf, die mit @PreDestroy versehen sind. Das bedeutet, dass wir die registerShutdownHook()-Methode einer ConfigurableApplicationContext in einer Boot-Anwendung nicht explizit verwenden müssen, wie dies bei der Spring-Core-Anwendung der Fall ist.

@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

Bei allen Antworten scheint die Tatsache zu fehlen, dass Sie während eines ordnungsgemäß heruntergefahrenen Herunterfahrens (beispielsweise in einer Unternehmensanwendung) einen Teil der Arbeit koordiniert abschließen müssen.

Mit @PreDestroy können Sie den Shutdown-Code in den einzelnen Beans ausführen. Etwas anspruchsvoller würde so aussehen:

@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

Ab Spring Boot 1.5 gibt es keinen Standard-Herunterfahrmechanismus. _ Einige Springstart-Starter bieten diese Funktionalität:

  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

Ich bin der Autor von Nr. 1. Der Starter heißt "Hiatus für Spring Boot". Es arbeitet auf der Lastausgleichs-Ebene, d. H. Kennzeichnet den Dienst einfach als OUT_OF_SERVICE und stört den Anwendungskontext in keiner Weise. Dies ermöglicht ein ordnungsgemäßes Herunterfahren und bedeutet, dass der Dienst bei Bedarf für einige Zeit außer Betrieb gesetzt und wieder zum Leben erweckt werden kann. Der Nachteil ist, dass die JVM nicht angehalten wird. Sie müssen dies mit dem Befehl kill tun. Da ich alles in Containern führe, war das für mich keine große Sache, da ich den Container sowieso stoppen und entfernen muss.

Die Nummern 2 und 3 basieren mehr oder weniger auf diesem Beitrag von Andy Wilkinson. Sie arbeiten in eine Richtung - sobald sie ausgelöst werden, schließen sie den Kontext.

3
jihor

Wenn Sie sich in einer Linux-Umgebung befinden, müssen Sie lediglich einen Symlink zu Ihrer .jar-Datei in /etc/init.d/ erstellen. 

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

Dann können Sie die Anwendung wie jeder andere Dienst starten 

Sudo /etc/init.d/myboot-app start

Um die Anwendung zu schließen

Sudo /etc/init.d/myboot-app stop

Auf diese Weise wird die Anwendung nicht beendet, wenn Sie das Terminal verlassen. Die Anwendung wird mit dem Stoppbefehl ordnungsgemäß heruntergefahren.

0
Johna

Wenn Sie Maven verwenden, können Sie das Maven App Assembler-Plugin verwenden. 

Der Daemon mojo (der JSW einbettet) gibt ein Shell-Skript mit dem Argument start/stop aus. Die Variable stop wird Ihre Spring-Anwendung ordnungsgemäß herunterfahren/beenden.

Dasselbe Skript kann verwendet werden, um Ihre Maven-Anwendung als Linux-Dienst zu verwenden.

0
Nicolas Labrot

Es gibt viele Möglichkeiten, eine Federanwendung herunterzufahren. Eine ist, close () auf der ApplicationContext aufzurufen:

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

Ihre Frage schlägt vor, dass Sie Ihre Anwendung mit Ctrl+C beenden möchten, das häufig zum Beenden eines Befehls verwendet wird. In diesem Fall...

Verwenden Sie endpoints.shutdown.enabled=true ist nicht das beste Rezept. Es bedeutet, dass Sie einen Endpunkt für die Beendigung Ihrer Anwendung verfügbar machen. Abhängig von Ihrem Anwendungsfall und Ihrer Umgebung müssen Sie ihn also sichern ...

Ctrl+C sollte in Ihrem Fall sehr gut funktionieren. Ich gehe davon aus, dass Ihr Problem durch das kaufmännische Und (&) verursacht wurde. Weitere Informationen:

Ein Spring Application Context kann einen Shutdown-Hook bei der JVM-Laufzeit registrieren. Siehe ApplicationContext-Dokumentation .

Ich weiß nicht, ob Spring Boot diesen Haken automatisch konfiguriert, wie Sie es sagten. Ich gehe davon aus, dass es ist.

Bei Ctrl+C sendet Ihre Shell ein INT-Signal an die Vordergrundanwendung. Es bedeutet "Bitte unterbrechen Sie die Ausführung". Die Anwendung kann dieses Signal abfangen und vor der Beendigung bereinigen (den von Spring registrierten Hook) oder es einfach ignorieren (schlecht).

Nohup ist ein Befehl, der das folgende Programm mit einem Trap ausführt, um das HUP-Signal zu ignorieren. HUP wird verwendet, um das Programm zu beenden, wenn Sie auflegen (z. B. Ihre SSH-Verbindung schließen). Außerdem werden die Ausgaben umgeleitet, um zu verhindern, dass Ihr Programm auf einem verschwundenen TTY blockiert. Nohup ignoriert das INT-Signal NICHT. Es verhindert also NICHT, dass Ctrl+C funktioniert.

Ich gehe davon aus, dass Ihr Problem durch das kaufmännische Und (&) verursacht wurde, nicht durch Nohup. Ctrl+C sendet ein Signal an die Vordergrundprozesse. Das kaufmännische Und bewirkt, dass Ihre Anwendung im Hintergrund ausgeführt wird. Eine Lösung: do

kill -INT pid

Die Verwendung von kill -9 oder kill -KILL ist schlecht, da die Anwendung (hier die JVM) sie nicht abfangen kann, um ordnungsgemäß zu beenden.

Eine andere Lösung ist, Ihre Anwendung wieder in den Vordergrund zu bringen. Dann funktioniert Ctrl+C. Schauen Sie sich die Bash-Job-Kontrolle an, genauer fg.

0
mcoolive

Verwenden Sie die Methode static exit() in der SpringApplication-Klasse, um Ihre Spring-Boot-Anwendung ordnungsgemäß zu schließen.

public class SomeClass {
    @Autowire
    private ApplicationContext context

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