An example of the Apache Commons Daemon in action. I originally wanted to create an end-to-end working example using SystemD / SystemCtl, until I realised that this is not possible in a containerised environment.
This example steps through building, deploying and starting a Java project as a daemon, before sending it a kill signal, to allow the user to observe it shutting down gracefully.
- Docker
- Git
- Clone the repos:
git clone https://www.github.com/edwinek/example-daemon
- Change into repos dir and make the build script executable:
cd ExampleDaemon
chmod +x build.sh
- Run the script:
./build.sh
- This will create the Docker image, start a container with an interactive bash terminal.
- From the bash terminal on the Docker container, run the
start-daemon.sh
script.- The service will start in the background.
- Tail the service's log, to see that it's running:
tail -f /var/log/example-daemon/output.log
- From a second terminal window, connect to the same container:
docker exec -ti daemon_container bash
- Find the PIds of the
jsvc
process:ps -aux
- Stop the
jsvc
process using the kill command and the PId:- eg. for PId 12
kill 12
- eg. for PId 12
- In the original window, observe the service logging that it has received the shut down message / has shut down.
- You can exit the
tail
session by usingControl-C
- Then you can close the connection to each of the Docker bash sessions by using
Control-D
- The Docker container can then be stopped with:
docker stop daemon_container
- The container will delete itself upon being stopped
- The image snapshots can be deleted with:
docker rmi example_jsvc:latest
- The container runs the minimal image of Ubuntu 18.04, with the OpenJDK Java 8 JDK / runtime as well as the Apache Commons Daemon
jsvc
binary installed, as well. This binary maps OS control signals to a Java interface implemented by theuk.edwinek.ExampleSystemDService
class.
- The Gradle
jar
task was modified in three ways:- The output jar path is set to
/opt/example-daemon
on the container - The output build path is set to
/tmp
on the container- This is to prevent creating dirs on the host machine owned by the container
- All dependencies are bundled in the jar
- The output jar path is set to
- The Apache Commons Daemon dependency is included (to supply the interface mentioned above), as well as Spring and the SLF4J logging facade for Log4J2.
- The service logs to
/var/log/example-daemon/output.log
build.sh
- Creates the image based on the
Dockerfile
- Spins up a detached instance of the container running bash
- Builds the jar using the
gradlew
wrapper - Attaches a bash session to the container
- Creates the image based on the
start-daemon.sh
- Runs jsvc, specifying the Java home path, the classpath to the Apache Commons Jar, the classpath to the example daemon jar itself, and the class that implements the Apache-supplied Daemon interface.
- In a non-containerised environment the start script could be replaced by a service management mechanism such as
system
,upstart
orsysvinit
. - In a production environment may be worth creating a restricted user, specifically for running Java daemons, rather than using root.