[systemd-devel] Springboot and systemd, clean shutdown
Silvio Knizek
killermoehre at gmx.net
Wed Oct 2 21:35:59 UTC 2024
Am Mittwoch, dem 02.10.2024 um 12:58 -0400 schrieb Brian Reichert:
> ```
> My goal:
>
> Under SLES12 SP5 running systemd-228, I want to cleanly terminate
> a Java-based SpringBoot application.
>
> My problem:
>
> systemd (or at least the version available to me) seemingly is
> terminating my JVM differently than my ExecStop directive should
> yield.
>
> Just for quick background, a SpringBoot application is a self-hosted
> web server and web app, and you're excepted to interact with it via
> 'endpoints', basically several specific URLs.
>
> According to the SpringBoot documentation, the desired way is to
> POST to the shutdown URL.
>
> [https://docs.spring.io/spring-boot/api/rest/actuator/shutdown.html](https://docs.spring.io/spring-boot/api/rest/actuator/shutdown.html)
>
> Ignoring systemd: when the JVM is run is the foreground, using the
> shutdown endpoint yields a clean exit:
>
> 10-153-68-12:/home/webapp # env SERVER_PORT=8888 LOG_DIR=/home/webapp/log
> JDK_JAVA_OPTIONS=-Dlogging.config=log4j2.properties -Dloader.path=lib/
> SPRING_MAIN_LAZY_INITIALIZATION=true /home/webapp/jdk/bin/java
> -jar webapp.jar; echo $?
> NOTE: Picked up JDK_JAVA_OPTIONS: -Dlogging.config=log4j2.properties
> 2024-10-02 12:20:24,685 main ERROR appender File has no parameter
> that matches element Policies
> 0
>
> 10-153-68-12:/home/webapp # /usr/bin/curl -X POST localhost:8888/shutdown
> {"message":"Shutting down, bye..."}
>
> (Note the '0' exit status above.)
>
> When I've started my service via my service file, if I manually
> invoke the shutdown endpoint, I can see that the JVM exited with
> 0, but my ExecPost (using curl), was invoked, and that died as there
> was no longer a listener:
>
> 10-153-68-12:/home/webapp # systemctl status webapp.service --full
> webapp.service - example webapp
> Loaded: loaded (/etc/systemd/system/webapp.service; disabled; vendor preset: disabled)
> Active: failed (Result: exit-code) since Wed 2024-10-02 12:46:58 EDT; 7s ago
> Process: 27573 ExecStop=/usr/bin/curl -X POST localhost:${SERVER_PORT}/shutdown (code=exited, status=7)
> Process: 27434 ExecStart=/home/webapp/jdk/bin/java -jar ${JAR_FILE} ${JAR_ARGS} (code=exited, status=0/SUCCESS)
> Main PID: 27434 (code=exited, status=0/SUCCESS)
>
> Oct 02 12:46:40 10-153-68-12 systemd[1]: Started example webapp.
> Oct 02 12:46:40 10-153-68-12 java[27434]: NOTE: Picked up JDK_JAVA_OPTIONS: -Dlogging.config=log4j2.properties -Dloader.path=lib/
> Oct 02 12:46:42 10-153-68-12 java[27434]: 2024-10-02 12:46:42,417 main ERROR appender File has no parameter that matches element Policies
> Oct 02 12:46:58 10-153-68-12 curl[27573]: % Total % Received % Xferd Average Speed Time Time Time Current
> Oct 02 12:46:58 10-153-68-12 curl[27573]: Dload Upload Total Spent Left Speed
> Oct 02 12:46:58 10-153-68-12 curl[27573]: [158B blob data]
> Oct 02 12:46:58 10-153-68-12 curl[27573]: curl: (7) Failed to connect to localhost port 8888 after 2 ms: Couldn't connect to server
> Oct 02 12:46:58 10-153-68-12 systemd[1]: webapp.service: Control process exited, code=exited status=7
> Oct 02 12:46:58 10-153-68-12 systemd[1]: webapp.service: Unit entered failed state.
> Oct 02 12:46:58 10-153-68-12 systemd[1]: webapp.service: Failed with result 'exit-code'.
>
> I admit that's not a typical use case, so I'm not very worried, but
> it would be nice to harden against that.
>
> But it does show, that using curl to hit the shutdown endpoint does cause the JVM to exit cleanly.
>
> But, my real concern is when I use systemd to stop this service, I
> can see that curl was invoked, got content, and itself exited
> cleanly, but somehow my JVM is exiting with an exit status of 143:
>
> 10-153-68-12:/home/webapp # systemctl status webapp.service --full
> webapp.service - example webapp
> Loaded: loaded (/etc/systemd/system/webapp.service; disabled; vendor preset: disabled)
> Active: failed (Result: exit-code) since Wed 2024-10-02 12:30:22 EDT; 9s
> ago
> Process: 24220 ExecStop=/usr/bin/curl -X POST
> localhost:${SERVER_PORT}/shutdown (code=exited, status=0/SUCCESS)
> Process: 23462 ExecStart=/home/webapp/jdk/bin/java -jar ${JAR_FILE} ${JAR_ARGS} (code=exited, status=143)
> Main PID: 23462 (code=exited, status=143)
>
> Oct 02 12:27:20 10-153-68-12 java[23462]: NOTE: Picked up JDK_JAVA_OPTIONS: -Dlogging.config=log4j2.properties -Dloader.path=lib/
> Oct 02 12:27:23 10-153-68-12 java[23462]: 2024-10-02 12:27:23,015 main ERROR appender File has no parameter that matches element Policies
> Oct 02 12:30:21 10-153-68-12 systemd[1]: Stopping example webapp...
> Oct 02 12:30:21 10-153-68-12 curl[24220]: % Total % Received % Xferd Average Speed Time Time Time Current
> Oct 02 12:30:21 10-153-68-12 curl[24220]: Dload Upload Total Spent Left Speed
> Oct 02 12:30:22 10-153-68-12 curl[24220]: [237B blob data]
> Oct 02 12:30:22 10-153-68-12 systemd[1]: webapp.service: Main process exited, code=exited, status=143/n/a
> Oct 02 12:30:22 10-153-68-12 systemd[1]: Stopped example webapp.
> Oct 02 12:30:22 10-153-68-12 systemd[1]: webapp.service: Unit entered failed state.
> Oct 02 12:30:22 10-153-68-12 systemd[1]: webapp.service: Failed with result 'exit-code'.
>
> Further, when I compare logs, I can see that when I use the standalone
> execution, I can see the Springboot app cleanly shuts down it's
> listener; I don't see these entries when I use systemd to manage
> this webapp:
>
> [INFO ] 2024-10-02 12:27:20.656 [Thread-2] Http11NioProtocol - Pausing ProtocolHandler ["http-nio-8888"]
> [INFO ] 2024-10-02 12:27:20.656 [Thread-2] StandardService - Stopping service [Tomcat]
> [INFO ] 2024-10-02 12:27:20.658 [Thread-2] [/] - Destroying Spring FrameworkServlet 'dispatcherServlet'
> [INFO ] 2024-10-02 12:27:20.661 [Thread-2] Http11NioProtocol - Stopping ProtocolHandler ["http-nio-8888"]
> [INFO ] 2024-10-02 12:27:20.665 [Thread-2] Http11NioProtocol - Destroying ProtocolHandler ["http-nio-8888"]
>
> Does anyone have any insight on why I'm seeing this?
>
> Here's my service file:
>
> 10-153-68-12:/home/webapp # cat /etc/systemd/system/webapp.service
> [Unit]
> Description=example webapp
> Before=getty at tty1.service plymouth-quit.service
> DefaultDependencies=no
>
> Requires=local-fs.target
> After=local-fs.target
>
> [Service]
> User=webapp
> Group=webapp
>
> WorkingDirectory=/home/webapp
> EnvironmentFile=/home/webapp/webapp.env
>
> PassEnvironment=JAVA_HOME JDK_JAVA_OPTIONS JAR_FILE JAR_ARGS SERVER_PORT
>
> ExecStart=/home/webapp/jdk/bin/java -jar ${JAR_FILE} ${JAR_ARGS}
> ExecStop=/usr/bin/curl -X POST localhost:${SERVER_PORT}/shutdown
>
> TimeoutStopSec=30
> KillMode=mixed
> NotifyAccess=all
> Restart=no
>
> [Install]
> WantedBy=multi-user.target
>
>
> ```
Hi,
according to https://www.springcloud.io/post/2022-02/spring-boot-graceful-shutdown/#gsc.tab=0 you can just send `SIGTERM` to the MainPID as kill signal and be happy. No need to fiddle with some `curl` command. This is the same as spring boot is doing it in kubernetes (https://docs.spring.io/spring-boot/how-to/deployment/cloud.html#howto.deployment.cloud.kubernetes.container-lifecycle).
So here is my suggestion for a `webapp.service`.
```ini
[Unit]
Description=Example Webapp
[Service]
User=webapp
Group=Webapp
WorkingDirectory=/home/webapp
EnvironmentFile=/home/webapp/webapp.env
ExecStart=/home/webapp/jdk/bin/java -jar ${JAR_FILE} ${JAR_ARGS}
[Install]
WantedBy=multi-user.target
```
No need for a special `KillMode=`, as all subsequent processes will be SIGKILL'ed after the SIGTERM to MAINPID is done.
BR
Silvio
More information about the systemd-devel
mailing list