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:
- https://github.com/jihor/hiatus-spring-boot
- https://github.com/gesellix/graceful-shutdown-spring-boot
- 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>
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 Maven, puede utilizar el complemento ensamblador de la aplicación Maven .
El demonio mojo
(que incrusta
JSW
) generará un script de shell con argumento de inicio / detención.
La
stop
cerrará / matará con gracia su aplicación Spring.
Se puede usar el mismo script para usar su aplicación maven como un servicio de Linux.
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).
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);
}
}