Jak poprawnie zamknąć aplikację Spring Boot?

121

W dokumencie Spring Boot Document napisali, że „Każda aplikacja SpringApplication zarejestruje punkt zaczepienia zamykania systemu w JVM, aby zapewnić prawidłowe zamknięcie ApplicationContext przy zamykaniu”.

Kiedy klikam ctrl+cpolecenie powłoki, aplikację można bezpiecznie zamknąć. Jeśli uruchamiam aplikację na maszynie produkcyjnej, muszę użyć polecenia java -jar ProApplicaton.jar. Ale nie mogę zamknąć terminala powłoki, w przeciwnym razie zamknie proces.

Jeśli uruchomię polecenie w stylu nohup java -jar ProApplicaton.jar &, nie mogę go użyć ctrl+cdo zamknięcia go z wdziękiem.

Jaki jest prawidłowy sposób uruchamiania i zatrzymywania aplikacji Spring Boot w środowisku produkcyjnym?

Chris
źródło
W zależności od konfiguracji PID aplikacji jest zapisywany do pliku. Możesz wysłać sygnał zabicia do tego PID. Zobacz także komentarze w tym numerze .
M. Deinum
Którego sygnału powinienem użyć, nie sądzę, że kill -9 to dobry pomysł, prawda?
Chris,
5
Dlatego wskazałem ci ten wątek ... Ale coś takiego kill -SIGTERM <PID>powinno załatwić sprawę.
M. Deinum,
kill with pid, no -9
Dapeng
1
kill $ (lsof -ti tcp: <port>) - w przypadku, gdy nie chcesz używać siłownika i potrzebujesz szybkiego zabicia
Alex Nolasco

Odpowiedzi:

63

Jeśli używasz modułu wykonawczego, możesz zamknąć aplikację za pomocą JMXlub HTTPjeśli punkt końcowy jest włączony.

dodaj do application.properties:

endpoints.shutdown.enabled = true

Dostępny będzie następujący adres URL:

/actuator/shutdown - Zezwala na bezpieczne zamykanie aplikacji (domyślnie wyłączone).

W zależności od sposobu ujawnienia punktu końcowego parametr wrażliwy może służyć jako wskazówka dotycząca bezpieczeństwa.

Na przykład wrażliwe punkty końcowe będą wymagały nazwy użytkownika / hasła, gdy zostaną udostępnione HTTP(lub po prostu wyłączone, jeśli zabezpieczenia internetowe nie są włączone).

Z dokumentacji rozruchu Spring

Jean-Philippe Bond
źródło
1
Nie chcę dołączać modułu wykonawczego. Ale odkryłem, że w dzienniku konsoli Spring Boot wyświetla PID w pierwszym wierszu. Czy istnieje sposób, aby Spring Boot wydrukował PID w innym pliku bez dodawania modułu wykonawczego (ApplicationPidListener)?
Chris,
2
Zauważ, że musi to być wpis http do tego punktu końcowego. Możesz go włączyć za pomocą endpoints.shutdown.enabled = true
sparkyspider
54

Oto kolejna opcja, która nie wymaga zmiany kodu ani ujawniania punktu końcowego zamykania. Utwórz następujące skrypty i użyj ich do uruchamiania i zatrzymywania aplikacji.

start.sh

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

Uruchamia aplikację i zapisuje identyfikator procesu w pliku

stop.sh

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

Zatrzymuje aplikację przy użyciu zapisanego identyfikatora procesu

start_silent.sh

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

Jeśli chcesz uruchomić aplikację przy użyciu ssh z maszyny zdalnej lub potoku CI, użyj tego skryptu, aby uruchomić aplikację. Bezpośrednie użycie start.sh może spowodować zawieszenie się powłoki.

Po np. ponownie / wdrażając swoją aplikację, możesz ją zrestartować za pomocą:

sshpass -p password ssh -oStrictHostKeyChecking=no userName@www.domain.com 'cd /home/user/pathToApp; ./stop.sh; ./start_silent.sh'
Jens
źródło
To powinna być odpowiedź. Właśnie potwierdziłem, że wyłączenie sygnału 15 nakazuje wiosnę z wdziękiem srać.
Czad
1
Dlaczego nie wywołujesz wykonania java - jar z nohup wewnątrz start.sh, zamiast wywoływać wykonanie java - jar wewnątrz start.sh, które jest wywoływane z nohup wewnątrz innego zewnętrznego skryptu powłoki?
Anand Varkey Philips
2
@AnandVarkeyPhilips Jedynym powodem jest to, że czasami wywołuję start.sh z wiersza poleceń w celach testowych, ale jeśli zawsze potrzebujesz nohup, możesz po prostu scalić polecenia
Jens
@Jens, dzięki za informację. Czy możesz mi powiedzieć, że robisz to: foo.out 2> foo.err </ dev / null &
Anand Varkey Philips
1
@jens, dzięki, że udało mi się to zrobić i poniżej zamieściłem mój skrypt start i stop. ( stackoverflow.com/questions/26547532/… )
Anand Varkey Philips,
52

Jeśli chodzi o odpowiedź @ Jean-Philippe Bonda,

oto szybki przykład Mavena dla użytkownika maven, aby skonfigurować punkt końcowy HTTP, aby zamknąć aplikację internetową rozruchu sprężynowego za pomocą sprężyny rozruchowej-siłownika, aby można było skopiować i wkleić:

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

Wszystkie punkty końcowe są wymienione tutaj :

3.Wyślij post, aby zamknąć aplikację:

curl -X POST localhost:port/shutdown

Uwaga dotycząca bezpieczeństwa:

jeśli potrzebujesz zabezpieczonej metody zamykania, możesz również potrzebować

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

skonfiguruj szczegóły :

Jaskey
źródło
Po drugim kroku podczas próby wdrożenia wystąpił następujący komunikat o błędzie: Opis: parametr 0 metody setAuthenticationConfiguration w org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter wymagał komponentu bean typu „org.springframework.security.config .annotation.authentication.configuration.AuthenticationConfiguration ', którego nie można znaleźć. Akcja: rozważ zdefiniowanie komponentu bean typu „org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration”.
Roberto
2
Uwaga: jeśli zawarłem coś podobnego server.contextPath=/appNamedo pliku application.properties, teraz polecenie zamknięcia będzie wyglądać następująco : curl -X POST localhost:8080/appName/shutdown Mam nadzieję, że może to komuś pomóc. Musiałem dużo walczyć z powodu tego błędu.
Naveen Kumar
W przypadku Spring Boot 1.5.8 zaleca się, jeśli bez zabezpieczeń, mieć plik application.properties endpoints.shutdown.enabled=true management.security.enabled=false.
Stefano Scarpanti,
Jeśli nie chcesz ujawniać punktu końcowego i używać pliku PID do zatrzymywania i uruchamiania za pomocą skryptu powłoki, spróbuj tego: stackoverflow.com/questions/26547532/ ...
Anand Varkey Philips
27

Możesz sprawić, by aplikacja springboot zapisywała PID do pliku i możesz użyć pliku pid do zatrzymania lub ponownego uruchomienia lub uzyskania statusu za pomocą skryptu bash. Aby zapisać PID do pliku, zarejestruj odbiornik w SpringApplication za pomocą ApplicationPidFileWriter, jak pokazano poniżej:

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

Następnie napisz skrypt bash, aby uruchomić aplikację rozruchową wiosny. Odniesienie .

Teraz możesz użyć skryptu do uruchomienia, zatrzymania lub ponownego uruchomienia.

nav3916872
źródło
Kompletny skrypt powłoki start i stop kompatybilny z Generic i Jenkins można znaleźć tutaj ( stackoverflow.com/questions/26547532/… )
Anand Varkey Philips
14

Wydaje się, że we wszystkich odpowiedziach brakuje faktu, że pewna część pracy może być konieczna w skoordynowany sposób podczas bezpiecznego zamykania systemu (na przykład w aplikacji korporacyjnej).

@PreDestroyumożliwia wykonanie kodu zamknięcia w poszczególnych komponentach bean. Coś bardziej wyrafinowanego wyglądałoby tak:

@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();
     }
}
Michał
źródło
Właśnie tego potrzebowałem. Po uruchomieniu aplikacji naciśnij ctrl-c Thank @Michal
Claudio Moscoso
8

Nie ujawniam żadnych punktów końcowych i uruchamiam ( z nohup w tle i bez plików utworzonych przez nohup ) i zatrzymuję się za pomocą skryptu powłoki (z wdziękiem KILL PID i wymuszeniem zabicia, jeśli aplikacja nadal działa po 3 minutach ). Po prostu tworzę wykonywalny jar i używam programu do zapisywania plików PID, aby zapisać plik PID i przechowywać Jar i Pid w folderze o tej samej nazwie, co nazwa aplikacji, a skrypty powłoki również mają tę samą nazwę z początkiem i końcem. Nazywam te skrypty zatrzymujące i uruchamiające skrypt również przez potok Jenkinsa. Na razie brak problemów. Doskonale działa dla 8 aplikacji (bardzo ogólne skrypty i łatwe do zastosowania w dowolnej aplikacji).

Klasa główna

@SpringBootApplication
public class MyApplication {

    public static final void main(String[] args) {
        SpringApplicationBuilder app = new SpringApplicationBuilder(MyApplication.class);
        app.build().addListeners(new ApplicationPidFileWriter());
        app.run();
    }
}

PLIK YML

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

Oto skrypt startowy (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."

Oto skrypt zatrzymujący (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
źródło
7

Spring Boot dostarczył kilka nasłuchiwania aplikacji podczas próby stworzenia kontekstu aplikacji, jednym z nich jest ApplicationFailedEvent. Możemy użyć, aby wiedzieć, czy kontekst aplikacji został zainicjowany, czy nie.

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

Dodaj do powyższej klasy detektora do SpringApplication.

    new SpringApplicationBuilder(Application.class)
            .listeners(new ApplicationErrorListener())
            .run(args);  
user3137438
źródło
Najlepsza odpowiedź, jaką znalazłem! Dziękuję Ci!
Vagif
[@ user3137438], Czym różni się od logowania w adnotacji przed zniszczeniem?
Anand Varkey Philips
6

Od wersji Spring Boot 2.3 i nowszych istnieje wbudowany wdzięczny mechanizm zamykania .

Pre-Spring Boot 2.3 , nie ma gotowego do użycia wdzięcznego mechanizmu zamykania. Niektóre rozruszniki sprężynowe zapewniają tę funkcjonalność:

  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

Jestem autorem nr. 1. Starter nosi nazwę „Hiatus for Spring Boot”. Działa na poziomie load balancera, tzn. Po prostu oznacza usługę jako OUT_OF_SERVICE, nie ingerując w żaden sposób w kontekst aplikacji. Pozwala to na płynne zamknięcie i oznacza, że ​​w razie potrzeby usługa może zostać wyłączona na pewien czas, a następnie przywrócona do życia. Wadą jest to, że nie zatrzymuje JVM, będziesz musiał to zrobić za pomocą killpolecenia. Ponieważ prowadzę wszystko w kontenerach, nie było to dla mnie nic wielkiego, bo i tak będę musiał się zatrzymać i wyjąć kontener.

Numery 2 i 3 są mniej więcej oparte na tym poście Andy'ego Wilkinsona. Działają jednokierunkowo - po uruchomieniu zamykają kontekst.

jihor
źródło
5

SpringApplication niejawnie rejestruje hak zamykania systemu w maszynie JVM, aby zapewnić prawidłowe zamknięcie ApplicationContext przy zamykaniu. Spowoduje to również wywołanie wszystkich metod bean z adnotacjami @PreDestroy. Oznacza to, że nie musimy jawnie używać registerShutdownHook()metody a ConfigurableApplicationContextw aplikacji rozruchowej, tak jak w przypadku aplikacji 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
źródło
Zamiast @PostConstructi @PreDestroyużyłem atrybutów initMethodi destroyMethodw @Beanadnotacji. Tak więc w tym przykładzie: @Bean(initMethod="init", destroyMethod="destroy").
Acker9
Jeden haczyk, o którym @PreDestroyniewielu deweloperów może nie wiedzieć, polega na tym, że takie metody są wywoływane tylko w przypadku ziaren o zasięgu Singleton. Deweloperzy muszą zarządzać oczyszczaną częścią cyklu życia fasoli dla innych zakresów
asgs
2

Istnieje wiele sposobów zamykania aplikacji sprężynowej. Jednym z nich jest wywołanie funkcji close () na ApplicationContext:

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

Twoje pytanie sugeruje, że chcesz zamknąć aplikację, robiąc to Ctrl+C, co jest często używane do zakończenia polecenia. W tym przypadku...

Stosowanie endpoints.shutdown.enabled=truenie jest najlepszym przepisem. Oznacza to, że udostępniasz punkt końcowy, aby zamknąć aplikację. Tak więc, w zależności od przypadku użycia i środowiska, będziesz musiał je zabezpieczyć ...

Ctrl+Cpowinien działać bardzo dobrze w twoim przypadku. Zakładam, że przyczyną problemu jest znak & (&) Więcej wyjaśnień:

Kontekst aplikacji Spring mógł zarejestrować punkt zaczepienia zamknięcia systemu w środowisku wykonawczym maszyny JVM. Zobacz dokumentację ApplicationContext .

Nie wiem, czy Spring Boot skonfiguruje ten hak automatycznie, jak powiedziałeś. Zakładam, że tak.

W dniu Ctrl+Ctwoja powłoka wysyła INTsygnał do aplikacji pierwszego planu. To znaczy „proszę, przerwij wykonanie”. Aplikacja może przechwycić ten sygnał i wyczyścić go przed jego zakończeniem (zaczep zarejestrowany przez Spring) lub po prostu go zignorować (źle).

nohupto polecenie, które wykonuje następujący program z pułapką, aby zignorować sygnał HUP. HUP jest używany do kończenia programu po rozłączeniu się (na przykład zamknięcie połączenia ssh). Ponadto przekierowuje dane wyjściowe, aby uniknąć blokowania się programu na zaginionym TTY. nohupNIE ignoruje sygnału INT. Więc to NIE przeszkadza Ctrl+Cw pracy.

Zakładam, że przyczyną problemu jest znak „&”, a nie „nohup”. Ctrl+Cwysyła sygnał do procesów pierwszoplanowych. Znak ampersand powoduje uruchomienie aplikacji w tle. Jedno rozwiązanie: zrób

kill -INT pid

Użyj kill -9lub kill -KILLjest złe, ponieważ aplikacja (w tym przypadku JVM) nie może jej przechwycić, aby zakończyć w sposób wdzięczny.

Innym rozwiązaniem jest przywrócenie aplikacji na pierwszy plan. Wtedy Ctrl+Czadziała. Przyjrzyj się sterowaniu Bash Job, a dokładniej fg.

mcoolive
źródło
2

Użyj exit()metody statycznej w klasie SpringApplication, aby bezpiecznie zamknąć aplikację rozruchową wiosny.

public class SomeClass {
    @Autowire
    private ApplicationContext context

    public void close() {
        SpringApplication.exit(context);
    }
}
rw026
źródło
To działa dla mnie. Dziękuję bardzo.
Khachornchit Songsaen
1

Spring Boot obsługuje teraz bezpieczne zamykanie (obecnie w wersjach wstępnych 2.3.0.BUILD-SNAPSHOT)

Po włączeniu zamknięcie aplikacji będzie obejmowało okres karencji o konfigurowalnym czasie trwania. W tym okresie karencji istniejące żądania będą mogły zostać zakończone, ale żadne nowe żądania nie będą dozwolone

Możesz to włączyć za pomocą:

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
źródło
0

Jeśli używasz mavena, możesz użyć wtyczki Maven App assembler .

Demon mojo (który osadza JSW ) wyświetli skrypt powłoki z argumentem start / stop. Z stopwdziękiem zamknie / zabije Twoją aplikację Spring.

Ten sam skrypt może służyć do używania aplikacji maven jako usługi linuxowej.

Nicolas Labrot
źródło
0

Jeśli pracujesz w środowisku linuxowym, wszystko co musisz zrobić, to utworzyć dowiązanie symboliczne do pliku .jar z wnętrza /etc/init.d/

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

Następnie możesz uruchomić aplikację jak każdą inną usługę

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

Aby zamknąć aplikację

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

W ten sposób aplikacja nie zostanie zakończona po wyjściu z terminala. Aplikacja zamknie się płynnie po poleceniu zatrzymania.

Johna
źródło