Linux service vs J2EE container
Using standard J2EE containers for application deployment is not always suitable option. Time to time you need to run an java application (jar file) as a server less, more light weight linux process. Using standard java -cp …. MainClass is feasible but sooner or latter you will reveal that there is something important missing. Especially if you are supposed to run multiple components in this way. I becomes relly messy and hard to manage pretty soon. On linux system there is a solution which is a lot better – run the component as a linux service.
Linux service controlling process
Lets make is simple and easy to understand. Linux service is essentially a “process” which is driven by init script and has defined API – set of standard commands for management of the underlying linux process. Those linux service commands looks as following(processor represents actual name as defined in init script, see latter):
service processor start service processor status service processor stop service processor restartThat’s a lot simpler, easy to manage and monitor, right? You don’t need to know where particular jar file is located etc. Examples of init scripts can be usually located /etc/init.d/samples or just simply read scripts in /etc/init.d which contains various init scripts for different kinds of linux services already present on the system.
Linux Service wrappers
For java applications there is a bunch of projects which acts as a service wrappers. That enables you to quickly and easily turn jar file to regular linux service as a program daemon. There are wrappers even for windows OS. For some reasons I was directed to use just linux server standard tools so the reminder of this post will be about making the linux service program daemon in a common way via shell scripts.
Startup and shutdown scripts
First of all there is a necessity to create startup and shutdown script with a need to properly manage pid (process id) file accordingly. A good practice is to have a dedicated user to run a particular linux services and have them installed under /usr/local/xxx .
startup script follows:#!/bin/sh # # Script parameters: [Instalation_Foleder] # # JAVA_HOME Must point at your Java Development Kit installation. # Required to run the with the "debug" argument. # # JRE_HOME Must point at your Java Runtime installation. # Defaults to JAVA_HOME if empty. If JRE_HOME and JAVA_HOME # are both set, JRE_HOME is used. # # JAVA_OPTS (Optional) Java runtime options used when any command # is executed. # Check the way the script has been called and set current directory as PROCESSOR_HOME if [ "X$1" = "X" ] then cd .. >/dev/null pwd >/dev/null PROCESSOR_HOME=$PWD SERVICE_INVOKE="no" else PROCESSOR_HOME=$1 SERVICE_INVOKE="yes" fi echo PROCESSOR_HOME set to $PROCESSOR_HOME # Load confing source $PROCESSOR_HOME/bin/config.sh # Check if the invocation is according to configuration [asService | asProcess] if [ ! "$SERVICE_INVOKE" == "$RUN_AS_SERVICE" ] then echo "ERROR - Invocation is not according to configuration - run as a Lunux Service= $RUN_AS_SERVICE" exit 6 fi # check installation if [ ! -d "$PROCESSOR_HOME/bin" \ -o ! -f "$PROCESSOR_HOME/bin/config.sh" \ -o ! -d "$PROCESSOR_HOME/conf" \ -o ! -d "$PROCESSOR_HOME/deploy" \ -o ! -d "$PROCESSOR_HOME/lib" \ -o ! -f "$PROCESSOR_HOME/conf/log4j.properties" \ -o ! -f "$PROCESSOR_HOME/deploy/test1-1.0-SNAPSHOT.jar" ]; then echo echo ERROR - Installation is not correct! echo Expected installation package looks: echo "$PROCESSOR_HOME/bin" echo "$PROCESSOR_HOME/bin/config.sh" echo "$PROCESSOR_HOME/conf" echo "$PROCESSOR_HOME/conf/log4j.properties" echo "$PROCESSOR_HOME/deploy" echo "$PROCESSOR_HOME/deploy/test1-1.0-SNAPSHOT.jar" echo "$PROCESSOR_HOME/lib" exit 1 fi # clean up CLASSPATH= JAVA_OPTS= JAVA_PATH= JAVA_EXEC= # set JAVA REQUIRED_JVM_VERSION=1.7 if [ -z "$JAVA_HOME" ]; then if [ -z "$JRE_HOME" ]; then echo ERROR - either JAVA_HOME or JRE_HOME is not set!!! exit 1 else echo Java JRE used $JRE_HOME JAVA_PATH=$JRE_HOME fi else echo Java used $JAVA_HOME JAVA_PATH=$JAVA_HOME fi # set JAVA_EXEC JAVA_EXEC=$JAVA_PATH/bin/java #check Java bin if [ ! -x "$JAVA_EXEC" ]; then echo Java binaries not found $JAVA_EXEC exit 1 fi # checkJavaVersion JVM_VERSION=$("$JAVA_EXEC" -version 2>&1 | awk -F '"' '/version/ {print $2}') #echo version "$JVM_VERSION" if [[ "$JVM_VERSION" < "$REQUIRED_JVM_VERSION" ]]; then echo ERROR - $JAVA_EXEC doesnt point to propper java version $REQUIRED_JVM_VERSION exit 1 fi # setBDHISTP_MAIN BDHISTP_MAIN=cz.jaksky.PROCESSOR.PROCESSOR # setClasspath CLASSPATH=$PROCESSOR_HOME/deploy/*:$PROCESSOR_HOME/lib/* # echo Classpath set to: $CLASSPATH # setJAVA_OPTS JAVA_OPTS=-Dbdconf=$PROCESSOR_HOME/conf JAVA_OPTS="$JAVA_OPTS -Dlog4j.configuration=file:$PROCESSOR_HOME/conf/log4j.properties" #echo JAVA_OPTS set to: $JAVA_OPTS # This is nasty as in the code there is hardcoded location to actual config file for the process cd $PROCESSOR_HOME runProgram() { echo $JAVA_EXEC $JAVA_OPTS -classpath $CLASSPATH $BDHISTP_MAIN $JAVA_EXEC $JAVA_OPTS -classpath $CLASSPATH $BDHISTP_MAIN & PROCESS_PID=$! echo $PROCESS_PID > $PIDDIR/$PID_FILENAME echo "new application instance started as process $PROCESS_PID" } if [ ! -f "$PIDDIR/$PID_FILENAME" ] then echo "I will try to start new process ..." runProgram else PID=$(cat $PIDDIR/$PID_FILENAME) if ps -p $PID >/dev/null then echo "WARNING $APP_NAME already running as process $PID" else echo "process $PID is not running - will try to start a new instance of the application" echo " " runProgram fi fi exit 0shutdown script follows:
#!/bin/sh # Script usage: # this script can be invoked either directly in bin folder or from different location with passing information where to locate installation folder # # Check the way the script has been called and set current directory as PROCESSOR_HOME if [ "X$1" = "X" ] then cd .. >/dev/null pwd >/dev/null PROCESSOR_HOME=$PWD SERVICE_INVOKE="no" else PROCESSOR_HOME=$1 SERVICE_INVOKE="yes" fi echo PROCESSOR_HOME set to $PROCESSOR_HOME # Load confing source $PROCESSOR_HOME/bin/config.sh if [ -z "$PIDDIR" ] then echo "ERROR - Installation configuration file config.sh not found at $PROCESSOR_HOME/bin" exit 1 fi # Load confing source $PROCESSOR_HOME/bin/config.sh # Check if the invocation is according to configuration [asService | asProcess] if [ ! "$SERVICE_INVOKE" == "$RUN_AS_SERVICE" ] then echo "ERROR - Invocation is not according to configuration - run as a Lunux Service= $RUN_AS_SERVICE" exit 6 fi if [ -f "$PIDDIR/$PID_FILENAME" ] then PID=$(cat $PIDDIR/$PID_FILENAME) kill $PID RC=$? rm $PIDDIR/$PID_FILENAME echo "Application $APP_NAME - process $PID shut down successfull" exit $RC else echo "pid file not exist $PIDDIR/$PID_FILENAME, nothing to shut down" exit 0 fiConfig script
Those scripts relies on existence of installation configuration shell script – config.sh located in bin folder of installation as follows:
#!/bin/sh RUN_AS_SERVICE="yes" APP_NAME="Processor" APP_LONG_NAME="Processor instance" PIDDIR="/var/run/processor" PID_FILENAME="processor.pid"Startup script creates pid file located /var/run/processor – user under which the installation is running needs to have appropriate privileges.
Init.d script
Finally the init script which needs to be placed into /etc/init.d folder:
### BEGIN INIT INFO # Provides: processor # Required-Start: # Required-Stop: # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: processor daemon # Description: processor daemon # This provides example about how to # write a Init script. ### END INIT INFO # Config to edit if needed INSTALL_HOME=/usr/local/Processor JAVA_HOME=/usr/java/default SERVICE_USER="processor" # No modification allowed from here # Using the lsb functions to perform the operations. . /lib/lsb/init-functions # # If the daemon is not there, then exit. test -x $INSTALL_HOME/bin/startUp.sh || exit 5 test -x $INSTALL_HOME/bin/shutDown.sh || exit 5 test -x $INSTALL_HOME/bin/config.sh || exit 5 # Load confing source $INSTALL_HOME/bin/config.sh export JAVA_HOME PIDFILE=$PIDDIR/$PID_FILENAME # Process name ( For display ) NAME=$APP_NAME CURRENT_USER=`id -nu` start(){ echo "Starting $NAME under $SERVICE_USER user..." if [ "$CURRENT_USER" == "$SERVICE_USER" ] then $INSTALL_HOME/bin/startUp.sh $INSTALL_HOME >/dev/null RC=$? else su --preserve-environment --command="$INSTALL_HOME/bin/startUp.sh $INSTALL_HOME >/dev/null" $SERVICE_USER RC=$? fi } stop(){ echo "Stoping $NAME running under $SERVICE_USER user ..." if [ "$CURRENT_USER" == "$SERVICE_USER" ] then $INSTALL_HOME/bin/shutDown.sh $INSTALL_HOME >/dev/null RC=$? else su --preserve-environment --command="$INSTALL_HOME/bin/shutDown.sh $INSTALL_HOME >/dev/null" $SERVICE_USER RC=$? fi } case $1 in start) start exit $RC ;; stop) stop exit $RC ;; restart) stop start exit $RC ;; status) if [ ! -f "$PIDDIR/$PID_FILENAME" ] then echo "$NAME is NOT RUNNING" exit 1 else PID=$(cat $PIDDIR/$PID_FILENAME) if ps -p $PID >/dev/null then echo "$NAME is RUNNING $PID" exit 0 else echo "$NAME is NOT RUNNING" exit 1 fi fi ;; *) # For invalid arguments, print the usage message. echo "Usage: $0 {start|stop|restart|status}" exit 2 ;; esacIn the init script there is a need to change to appropriate java apps installation folder JAVA_HOME if not default and SERVICE_USER to user which is supposed to run this service. Service can be started under root account or SERVICE_USER without password specification or any other user with knowledge of credentials.
Conclusion
If you have a production like experience with java service wrappers mentioned at the beginning of the article don’t hesitate and share it! This way it serves the purpose at given situation.