Como desligar um aplicativo Spring Boot de maneira correta?

120

No Spring Boot Document, eles disseram que 'Cada SpringApplication registrará um gancho de desligamento com a JVM para garantir que o ApplicationContext seja fechado normalmente na saída.'

Quando clico ctrl+cno comando shell, o aplicativo pode ser encerrado normalmente. Se eu executar o aplicativo em uma máquina de produção, tenho que usar o comando java -jar ProApplicaton.jar. Mas não consigo fechar o terminal shell, caso contrário, o processo será fechado.

Se eu executar o comando como nohup java -jar ProApplicaton.jar &, não posso usar ctrl+cpara desligá-lo normalmente.

Qual é a maneira correta de iniciar e interromper um aplicativo Spring Boot no ambiente de produção?

Chris
fonte
Dependendo da sua configuração, o PID do aplicativo é gravado em um arquivo. Você pode enviar um sinal kill para esse PID. Veja também os comentários nesta edição .
M. Deinum
Qual sinal devo usar, não acho que kill -9 seja uma boa ideia, certo?
Chris,
5
É por isso que apontei para você esse tópico ... Mas algo como kill -SIGTERM <PID>deve resolver.
M. Deinum
matar com pid, não -9
Dapeng
1
kill $ (lsof -ti tcp: <port>) - caso você não queira usar o atuador e precise de um kill rápido
Alex Nolasco

Respostas:

61

Se estiver usando o módulo atuador, você pode desligar o aplicativo via JMXou HTTPse o terminal estiver habilitado.

adicionar a application.properties:

endpoints.shutdown.enabled = true

O seguinte URL estará disponível:

/actuator/shutdown - Permite que o aplicativo seja encerrado normalmente (não habilitado por padrão).

Dependendo de como um endpoint é exposto, o parâmetro confidencial pode ser usado como uma dica de segurança.

Por exemplo, endpoints sensíveis exigirão um nome de usuário / senha quando forem acessados HTTP (ou simplesmente desativados se a segurança da web não estiver ativada).

Da documentação de inicialização do Spring

Jean-Philippe Bond
fonte
1
Não quero incluir o módulo atuador. Mas descobri que, no log do meu console, Spring Boot imprime o PID na primeira linha. Existe alguma maneira de permitir que o Spring Boot imprima o PID em outro arquivo sem adicionar o módulo atuador (ApplicationPidListener)?
Chris
2
Observe que esta deve ser uma postagem http para este endpoint. Você pode habilitá-lo com endpoints.shutdown.enabled = true
sparkyspider
54

Aqui está outra opção que não exige que você altere o código ou exponha um endpoint de desligamento. Crie os scripts a seguir e use-os para iniciar e interromper seu aplicativo.

start.sh

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

Inicia seu aplicativo e salva a id do processo em um arquivo

stop.sh

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

Para seu aplicativo usando o ID de processo salvo

start_silent.sh

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

Se você precisar iniciar o aplicativo usando ssh de uma máquina remota ou um pipeline de CI, use este script para iniciar seu aplicativo. Usar start.sh diretamente pode deixar o shell travar.

Depois, por exemplo. re / implantando seu aplicativo, você pode reiniciá-lo usando:

sshpass -p password ssh -oStrictHostKeyChecking=no userName@www.domain.com 'cd /home/user/pathToApp; ./stop.sh; ./start_silent.sh'
Jens
fonte
Esta deve ser a resposta. Acabei de confirmar que um sinal de desligamento 15 indica que a primavera deve desligar normalmente.
Chade
1
Por que você não chama a execução java - jar com nohup dentro de start.sh, em vez de chamar a execução java - jar dentro de start.sh, que é chamada com nohup dentro de outro script de shell externo?
Anand Varkey Philips de
2
@AnandVarkeyPhilips A única razão é que às vezes eu chamo start.sh da linha de comando para fins de teste, mas se você sempre precisar nohup, pode apenas mesclar os comandos
Jens
@Jens, Obrigado pela informação. Você pode me dizer que faz isso: foo.out 2> foo.err </ dev / null &
Anand Varkey Philips
1
@jens, obrigado, fiz isso com sucesso e postei meu script de início e parada aqui abaixo. ( stackoverflow.com/questions/26547532/… )
Anand Varkey Philips
52

Quanto à resposta de @Jean-Philippe Bond,

aqui está um exemplo rápido do maven para o usuário do maven configurar o endpoint HTTP para desligar um aplicativo da web de inicialização do spring usando spring-boot-starter-atuator para que você possa copiar e colar:

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

Todos os endpoints estão listados aqui :

3. Envie um método de postagem para desligar o aplicativo:

curl -X POST localhost:port/shutdown

Nota de segurança:

se você precisa do método de desligamento protegido por autenticação, você também pode precisar

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

configurar detalhes :

Jaskey
fonte
Após a segunda etapa, ao tentar implantar, encontrei esta mensagem de erro: Descrição: O parâmetro 0 do método setAuthenticationConfiguration em org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter requer um bean do tipo 'org.springframework.security.config .annotation.authentication.configuration.AuthenticationConfiguration 'que não foi encontrado. Ação: Considere definir um bean do tipo 'org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration' em sua configuração.
Roberto
2
Observação: se eu incluí algo como server.contextPath=/appNameem meu application.properties, agora o comando para desligar seria: curl -X POST localhost:8080/appName/shutdown Espero que possa ajudar alguém. Tive que lutar muito por causa desse erro.
Naveen Kumar
Com Spring Boot 1.5.8 sugere-se, se não houver segurança, ter em application.properties endpoints.shutdown.enabled=true management.security.enabled=false.
Stefano Scarpanti
Se você não quiser expor o ponto final e usar o arquivo PID para parar e iniciar via script de shell, tente o seguinte: stackoverflow.com/questions/26547532/…
Anand Varkey Philips
27

Você pode fazer o aplicativo springboot gravar o PID em um arquivo e pode usar o arquivo pid para parar ou reiniciar ou obter o status usando um script bash. Para gravar o PID em um arquivo, registre um ouvinte para SpringApplication usando ApplicationPidFileWriter conforme mostrado abaixo:

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

Em seguida, escreva um script bash para executar o aplicativo Spring Boot. Referência .

Agora você pode usar o script para iniciar, parar ou reiniciar.

nav3916872
fonte
O script de shell de início e parada completo compatível com Genérico e Jenkins pode ser encontrado aqui ( stackoverflow.com/questions/26547532/… )
Anand Varkey Philips
14

Todas as respostas parecem não perceber o fato de que você pode precisar concluir alguma parte do trabalho de maneira coordenada durante o desligamento normal (por exemplo, em um aplicativo corporativo).

@PreDestroypermite que você execute o código de desligamento nos beans individuais. Algo mais sofisticado ficaria assim:

@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();
     }
}
Michal
fonte
Isso é exatamente o que eu precisava. Após executar o aplicativo, pressione ctrl-c Obrigado @Michal
Claudio Moscoso
8

Não exponho nenhum endpoint e inicio ( com nohup em segundo plano e sem arquivos de saída criados por meio de nohup ) e paro com script de shell (com KILL PID graciosamente e force o kill se o aplicativo ainda estiver em execução após 3 minutos ). Acabei de criar o jar executável e usar o gravador de arquivo PID para escrever o arquivo PID e armazenar Jar e Pid na pasta com o mesmo nome do nome do aplicativo e os scripts de shell também têm o mesmo nome com start e stop no final. Eu também chamo isso de script de parada e script de início via jenkins pipeline. Sem problemas até agora. Funcionando perfeitamente para 8 aplicativos (scripts muito genéricos e fáceis de aplicar para qualquer aplicativo).

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

YML FILE

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

Aqui está o script de início (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."

Aqui está o script de parada (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
fonte
7

Spring Boot forneceu vários ouvintes de aplicativo enquanto tentava criar o contexto do aplicativo, um deles é ApplicationFailedEvent. Podemos usar para saber se o contexto do aplicativo foi inicializado ou não.

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

Adicione a classe de ouvinte acima a SpringApplication.

    new SpringApplicationBuilder(Application.class)
            .listeners(new ApplicationErrorListener())
            .run(args);  
user3137438
fonte
A melhor resposta que encontrei! Obrigado!
Vagif
[@ user3137438], Como é diferente do registro dentro da anotação de pré-destruição?
Anand Varkey Philips
6

A partir do Spring Boot 2.3 e posterior, há um mecanismo de desligamento normal integrado .

Antes do Spring Boot 2.3 , não há nenhum mecanismo de desligamento normal pronto para uso. Alguns iniciadores de boot com mola fornecem esta funcionalidade:

  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

Eu sou o autor do nr. 1. O starter é denominado "Hiato para a Spring Boot". Ele funciona no nível do balanceador de carga, ou seja, simplesmente marca o serviço como OUT_OF_SERVICE, não interferindo no contexto do aplicativo de forma alguma. Isso permite fazer um desligamento normal e significa que, se necessário, o serviço pode ser retirado de serviço por algum tempo e, em seguida, restaurado. A desvantagem é que isso não para a JVM, você terá que fazer isso com o killcomando. Como eu executo tudo em contêineres, isso não é grande coisa para mim, porque terei que parar e remover o contêiner de qualquer maneira.

Os números 2 e 3 são mais ou menos baseados neste post de Andy Wilkinson. Eles funcionam unilateralmente - uma vez acionados, eles eventualmente fecham o contexto.

Jihor
fonte
5

SpringApplication registra implicitamente um gancho de encerramento com a JVM para garantir que ApplicationContext seja fechado normalmente na saída. Isso também chamará todos os métodos de bean anotados com @PreDestroy. Isso significa que não temos que usar explicitamente o registerShutdownHook()método de a ConfigurableApplicationContextem um aplicativo de inicialização, como temos que fazer no aplicativo 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
fonte
Como alternativa a @PostConstructe @PreDestroyusei os atributos initMethode destroyMethodna @Beananotação. Portanto, para este exemplo: @Bean(initMethod="init", destroyMethod="destroy").
acker9
Um detalhe que @PreDestroypoucos desenvolvedores podem não saber é que esses métodos são chamados apenas para beans com escopo Singleton. Os desenvolvedores precisam gerenciar a parte de limpeza do ciclo de vida do feijão para outros escopos
asgs de
2

Existem várias maneiras de encerrar um aplicativo Spring. Uma é chamar close () no ApplicationContext:

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

Sua pergunta sugere que você deseja fechar seu aplicativo fazendo Ctrl+C, que é freqüentemente usado para encerrar um comando. Nesse caso...

O uso endpoints.shutdown.enabled=truenão é a melhor receita. Isso significa que você expõe um ponto de extremidade para encerrar seu aplicativo. Portanto, dependendo do seu caso de uso e do seu ambiente, você terá que protegê-lo ...

Ctrl+Cdeve funcionar muito bem no seu caso. Presumo que o seu problema seja causado pelo E comercial (&) Mais explicação:

Um contexto de aplicativo Spring pode ter registrado um gancho de encerramento com o tempo de execução da JVM. Consulte a documentação do ApplicationContext .

Não sei se o Spring Boot configura este gancho automaticamente como você disse. Eu suponho que sim.

Ativado Ctrl+C, seu shell envia um INTsinal para o aplicativo em primeiro plano. Significa "interrompa sua execução". O aplicativo pode capturar este sinal e fazer a limpeza antes de seu término (o gancho registrado pelo Spring) ou simplesmente ignorá-lo (ruim).

nohupé o comando que executa o seguinte programa com uma armadilha para ignorar o sinal HUP. O HUP é usado para encerrar o programa quando você desliga (feche sua conexão ssh, por exemplo). Além disso, ele redireciona as saídas para evitar que o seu programa bloqueie em um TTY desaparecido. nohupNÃO ignora o sinal INT. Portanto, NÃO impedeCtrl+C trabalho.

Presumo que o seu problema seja causado pelo "e" comercial (&), não pelo nohup. Ctrl+Cenvia um sinal para os processos de primeiro plano. O E comercial faz com que seu aplicativo seja executado em segundo plano. Uma solução: faça

kill -INT pid

Use kill -9oukill -KILL está incorreto porque o aplicativo (aqui a JVM) não pode interceptá-lo para encerrar normalmente.

Outra solução é trazer de volta seu aplicativo em primeiro plano. Então Ctrl+Cvai funcionar. Dê uma olhada no controle Bash Job, mais precisamente ativado fg.

mcoolive
fonte
2

Use o exit()método estático na classe SpringApplication para fechar seu aplicativo de inicialização de primavera normalmente.

public class SomeClass {
    @Autowire
    private ApplicationContext context

    public void close() {
        SpringApplication.exit(context);
    }
}
rw026
fonte
Isso funciona para mim. Muito obrigado.
Khachornchit Songsaen
1

Spring Boot agora suporta desligamento normal (atualmente em versões de pré-lançamento, 2.3.0.BUILD-SNAPSHOT)

Quando ativado, o desligamento do aplicativo incluirá um período de carência de duração configurável. Durante este período de carência, as solicitações existentes poderão ser concluídas, mas nenhuma nova solicitação será permitida

Você pode habilitá-lo com:

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
fonte
0

Se você estiver usando o maven, poderá usar o plugin Maven App assembler .

O daemon mojo (que incorpora JSW ) produzirá um script de shell com o argumento start / stop. O stopdesligamento vontade / matar graciosamente sua aplicação Primavera.

O mesmo script pode ser usado para usar seu aplicativo maven como um serviço Linux.

Nicolas Labrot
fonte
0

Se você estiver em um ambiente Linux, tudo o que você precisa fazer é criar um link simbólico para o seu arquivo .jar de dentro de /etc/init.d/

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

Então você pode iniciar o aplicativo como qualquer outro serviço

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

Para fechar o aplicativo

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

Dessa forma, o aplicativo não será encerrado quando você sair do terminal. E o aplicativo será encerrado normalmente com o comando de parada.

Johna
fonte