unregistering stop exposed beans application app linux spring spring-boot

linux - exposed - stop spring boot application



¿Cómo cerrar una aplicación Spring Boot de forma correcta? (13)

A partir de Spring Boot 1.5, no existe un mecanismo de apagado elegante listo para usar. Algunos arrancadores de arranque por resorte proporcionan esta funcionalidad:

  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

Soy el autor de nr. 1. El motor de arranque se llama "Hiatus for Spring Boot". Funciona en el nivel del equilibrador de carga, es decir, simplemente marca el servicio como OUT_OF_SERVICE, sin interferir de ninguna manera con el contexto de la aplicación. Esto permite realizar un apagado correcto y significa que, si es necesario, el servicio se puede dejar fuera de servicio durante un tiempo y luego volver a la vida. La desventaja es que no detiene la JVM, tendrá que hacerlo con el comando kill . Como ejecuto todo en contenedores, esto no fue un gran problema para mí, porque tendré que parar y quitar el contenedor de todos modos.

Los números 2 y 3 se basan más o menos en esta publicación de Andy Wilkinson. Funcionan de una manera: una vez activados, finalmente cierran el contexto.

En el documento Spring Boot, dijeron que "Cada SpringApplication registrará un enlace de cierre con la JVM para garantizar que el ApplicationContext se cierre correctamente al salir".

Cuando hago clic en ctrl+c en el comando de shell, la aplicación se puede cerrar con gracia. Si ejecuto la aplicación en una máquina de producción, tengo que usar el comando java -jar ProApplicaton.jar . Pero no puedo cerrar el terminal de shell, de lo contrario cerrará el proceso.

Si ejecuto un comando como nohup java -jar ProApplicaton.jar & , no puedo usar ctrl+c para cerrarlo con gracia.

¿Cuál es la forma correcta de iniciar y detener una aplicación Spring Boot en el entorno de producción?


Aquí hay otra opción que no requiere que cambie el código o exponga un punto final de apagado. Cree los siguientes scripts y úselos para iniciar y detener su aplicación.

start.sh

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

Inicia su aplicación y guarda la identificación del proceso en un archivo

stop.sh

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

Detiene su aplicación usando la identificación de proceso guardada

start_silent.sh

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

Si necesita iniciar la aplicación usando ssh desde una máquina remota o una canalización de CI, use este script en su lugar para iniciar su aplicación. El uso de start.sh directamente puede dejar que el shell se cuelgue.

Después por ej. Al volver a implementar su aplicación, puede reiniciarla usando:

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


En cuanto a la respuesta de @ Jean-Philippe Bond,

Aquí hay un ejemplo rápido de Maven para que el usuario de Maven configure el punto final HTTP para cerrar una aplicación web Spring Boot usando Spring-boot-starter-actuator para que pueda copiar y pegar:

1.Maven pom.xml:

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

2.propiedad.propiedades:

#No auth protected endpoints.shutdown.sensitive=false #Enable shutdown endpoint endpoints.shutdown.enabled=true

Todos los puntos finales se enumeran aquí :

3.Envíe un método de publicación para cerrar la aplicación:

curl -X POST localhost:port/shutdown

Nota de seguridad:

si necesita el método de apagado protegido por autenticación, también puede necesitar

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

configurar detalles :


No expongo ningún punto final e inicio ( con nohup en segundo plano y sin archivos creados a través de nohup ) y me detengo con el script de shell (con KILL PID con gracia y forzo kill si la aplicación aún se está ejecutando después de 3 minutos ). Acabo de crear un jar ejecutable y utilizo el escritor de archivos PID para escribir el archivo PID y almacenar Jar y Pid en una carpeta con el mismo nombre que el nombre de la aplicación y los scripts de shell también tienen el mismo nombre con start y stop al final. A estos también los llamo stop script y start script a través de la tubería jenkins. No hay problemas hasta ahora. Funciona perfectamente para 8 aplicaciones (scripts muy genéricos y fáciles de aplicar para cualquier aplicación).

Clase principal

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

ARCHIVO YML

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

Aquí está el script de inicio (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."

Aquí está el script de detención (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


Parece que a todas las respuestas les falta el hecho de que es posible que deba completar una parte del trabajo de manera coordinada durante el apagado correcto (por ejemplo, en una aplicación empresarial).

@PreDestroy permite ejecutar el código de apagado en los beans individuales. Algo más sofisticado se vería así:

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


Puede hacer que la aplicación springboot escriba el PID en el archivo y puede usar el archivo pid para detener o reiniciar u obtener el estado usando un script bash. Para escribir el PID en un archivo, registre un escucha en SpringApplication usando ApplicationPidFileWriter como se muestra a continuación:

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

Luego escriba un script bash para ejecutar la aplicación de arranque de primavera. Reference .

Ahora puede usar el script para iniciar, detener o reiniciar.



Si está utilizando el módulo actuador, puede cerrar la aplicación a través de JMX o HTTP si el punto final está habilitado (agregue endpoints.shutdown.enabled=true a su archivo application.properties ).

/shutdown : permite que la aplicación se cierre correctamente (no está habilitada de forma predeterminada).

Dependiendo de cómo se exponga un punto final, el parámetro sensible puede usarse como una pista de seguridad. Por ejemplo, los puntos finales sensibles requerirán un nombre de usuario / contraseña cuando se acceda a través de HTTP (o simplemente se deshabilitarán si la seguridad web no está habilitada).

De la documentación de arranque de Spring


Si se encuentra en un entorno Linux, todo lo que tiene que hacer es crear un enlace simbólico a su archivo .jar desde adentro /etc/init.d/

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

Luego puede iniciar la aplicación como cualquier otro servicio

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

Para cerrar la aplicación

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

De esta forma, la aplicación no terminará cuando salga del terminal. Y la aplicación se cerrará correctamente con el comando detener.


Son muchas formas de cerrar una aplicación de primavera. Una es llamar a close () en el ApplicationContext :

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

Su pregunta sugiere que desea cerrar su aplicación haciendo Ctrl+C , que se usa con frecuencia para terminar un comando. En este caso...

Usar endpoints.shutdown.enabled=true no es la mejor receta. Significa que expones un punto final para terminar tu aplicación. Entonces, dependiendo de su caso de uso y su entorno, tendrá que asegurarlo ...

Ctrl+C debería funcionar muy bien en su caso. Supongo que su problema es causado por el ampersand (&) Más explicación:

Un contexto de aplicación Spring puede haber registrado un gancho de apagado con el tiempo de ejecución JVM. Consulte la documentación de ApplicationContext .

No sé si Spring Boot configura este gancho automáticamente como dijiste. Supongo que lo es.

En Ctrl+C , su shell envía una señal INT a la aplicación en primer plano. Significa "por favor interrumpa su ejecución". La aplicación puede atrapar esta señal y hacer la limpieza antes de su finalización (el gancho registrado por Spring), o simplemente ignorarla (mal).

nohup es un comando que ejecuta el siguiente programa con una trampa para ignorar la señal HUP. HUP se usa para terminar el programa cuando cuelga (cierre su conexión ssh, por ejemplo). Además, redirige las salidas para evitar que su programa se bloquee en un TTY desaparecido. nohup NO ignora la señal INT. Por lo tanto, NO impide que Ctrl+C funcione.

Supongo que su problema es causado por el signo (&), no por nohup. Ctrl+C envía una señal a los procesos en primer plano. El ampersand hace que su aplicación se ejecute en segundo plano. Una solución: hacer

kill -INT pid

Usar kill -9 o kill -KILL es malo porque la aplicación (aquí la JVM) no puede atraparlo para que finalice correctamente.

Otra solución es recuperar su aplicación en primer plano. Entonces Ctrl+C funcionará. Eche un vistazo al control de Bash Job, más precisamente en fg .


Spring Boot proporcionó varias aplicaciones de escucha mientras intentaba crear el contexto de la aplicación, una de ellas es ApplicationFailedEvent. Podemos usar para conocer el clima del contexto de la aplicación inicializado o no.

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

Agregue a la clase de escucha anterior a SpringApplication.

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


SpringApplication registra implícitamente un enlace de cierre con la JVM para garantizar que ApplicationContext se cierre correctamente al salir. Eso también llamará a todos los métodos de bean anotados con @PreDestroy . Eso significa que no tenemos que usar explícitamente el método registerShutdownHook() de un ConfigurableApplicationContext en una aplicación de arranque, como tenemos que hacer en la aplicación 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"); } } }


Use el método de exit() estática exit() en la clase SpringApplication para cerrar su aplicación de arranque de primavera con gracia.

public class SomeClass { @Autowire private ApplicationContext context public void close() { SpringApplication.exit(context); } }