terça-feira, janeiro 29, 2019

JAVA JVM APPLICATIONS AS A SERVICE WITH SYSTEMD

linux service java application
JAVA JVM APPLICATIONS AS A SERVICE WITH SYSTEMD


Let’s say you have written a Java application that exposes an HTTP service. For example by using Spring Bootor Spark. During development you can probably start your application by running some main class. But when you finally deploy it to a server (and are not using containers like Docker) you need some way to run the application automatically.
On Linux servers you can either create an init.d script or use systemd. In this post we will take a look at the latter. I know there’s a lot of debate about which system is superior. I can’t really comment on that.

Using systemd With Spring Boot’s Embedded Script

For some time now it is possible to build a Spring Boot app with an embedded shell script to start it. It basically puts a shell script at the start of the JAR file and most shells have no problem calling this, even if there is all the binary JAR stuff after it. Java itself also has no problem starting it with java -jar even if there is a ASCII script at the start of the file.

Enabling the Embedded Script With Spring Boot

To enable this script you can use the Spring Boot plugin for either Maven or Gradle. It’s just a matter of adding some configuration.
apply plugin: 'spring-boot'
springBoot {
executable = true
}
.. or for Maven.
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
</configuration>
</plugin>
</plugins>
</build>
Now after a gradle build or mvn package you can start the application in a shell (like bash) by just running ./my-app.jar.
The embedded script can be used as an init.d service. This section of the Spring Boot documentation describes how to do that.

Creating a systemd Unit

The next section of the documentation also shows how to use the JAR with the embedded script with systemd. I want extend this documentation a little bit and also show how to use systemd without the embedded script later.
Let’s take a look at a longer example. This file is /etc/systemd/system/myapp.service.
[Unit]
Description=MyApplication web interface
After=syslog.target
[Service]
SyslogIdentifier=MyApplication
ExecStart=/home/myapp/application/my-app.jar
User=myapp
Type=simple
[Install]
WantedBy=multi-user.target
Let’s go over it.
Line 3: We define that our unit must run after syslog is available. Since all stdout and stderr output will be saved in syslog this is important.
Line 6: If you look at logs from your application in syslog (e.g. with journalctl) this will be the visible name of the application.
Line 7: Since no further arguments are added this will start the run target of the script. This means the script does not take care of pid and log files which makes sense when using systemd.
Line 8: It is good practice to let applications run with their own user, so we tell systemd to use our app user (of course it has to be created before).
Line 9: The type simple tells systemd that our executable from ExecStart is the main process (e.g. it doesn’t use fork()). This is also the default value.
Line 12multi-user.target defines that this service will only be started when the system boots up to this target (a non-graphical multi-user environment).
Setting WorkingDirectory in the Service section has no effect since the embedded script will change the directory anyway (see this line). The working directory will always be the one of the JAR file.
After the file is created, the service needs to be enabled with systemctl enable myapp.service and then can be started with systemctl start myapp.service. It will also be started when the machine reboots.

Looking at the Logs With journalctl

If you want to use the systemd logging facilities to log, your application needs a console appender. If you didn’t change the logging configuration in your Spring Boot app it will log to the console by default, otherwise configure your logging system (log4j, logback, …) to do so.
The output of journalctl is quite large. Luckily you can filter it easily! Just use journalctl -u myapp.serviceand only entries from your app will be shown. If you want to follow new entries as they come in, use the -fflag, like so journalctl -f -u myapp.service.

Using systemd Without the Embedded Script

If you don’t want to embed the script from Spring Boot or don’t use Spring Boot at all it is still very easy to use systemd. Since the embedded script doesn’t add much when only the run target is called I actually would recommend not using it when you use systemd.
For this section I will assume a Spark application. Our blog post shows an example on how to create a JAR file for a Spark application.
Now we modify the systemd unit a little.
[Unit]
Description=MyApplication web interface
After=syslog.target
[Service]
WorkingDirectory=/home/myapp/application
SyslogIdentifier=MyApplication
ExecStart=/bin/bash -c "java -jar /home/myapp/application/my-spark-app.jar"
User=myapp
Type=simple
[Install]
WantedBy=multi-user.target
It is now possible to override the working directory (see line 6). The part ExecStart now calls Java directly. In this case it is not really necessary to use a shell, but if you might want environment variable substitution somewhere this is the way to go.

Conclusion

Using systemd to register a JVM application as a service is really easy. Personally, without having too many stakes in the init.d vs. systemd debate I prefer it. The scripts in /etc/init.d always seemed a bit clunky to me. While they might allow a little bit easier debugging I like the declarative approach of systemd.

Nenhum comentário: