Thursday, December 22, 2011

Tomcat7 on CentOS5 - the right way

I found many guides regarding Tomcat7 on CentOS 5, but none of them seemed to fit my needs:
 - Run tomcat as non-root user for security reasons
 - Using official tar.gz file from tomcat.apache.org
 - Proper handling of PID files
 - No need to modify any of the original Apache-Tomcat scripts
 - Save stop of tomcat, even when application shutdown prevents it.

Therefore I've installed tomcat7 myself.

Here we go:

1. Download apache-tomcat-7.0.23.tar.gz 

wget http://www.bitlib.net/mirror/apache.org/tomcat/tomcat-7/v7.0.23/bin/apache-tomcat-7.0.23.tar.gz

2. Unpack apache-tomcat-7.0.23.tar.gz
in any directory you like. I use /opt/apache-tomcat_7.0.23 here

cd /opt
tar zxvf ../apache-tomcat-7.0.23.tar.gz

3. Create a tomcat user

useradd -d /usr/share/tomcat -s /sbin/nologin tomcat

4. Create a config directory for the tomcat instance

mkdir /etc/tomcat

5. Create tomcat70.conf file


vi /etc/tomcat/tomcat70.conf

tomcat70.conf content:


# tomcat7.0 service configuration file
# you could also override JAVA_HOME here
# Where your java installation lives
JAVA_HOME="/etc/alternatives/java_sdk"   # we use RH/CentOS alternatives aware Jpackage created Java RPM. 
##JAVA_HOME="/usr/java/default"           # if you use the standard Oracle JDK .bin installer
export JAVA_HOME
# You can pass some parameters to java
# here if you wish to
#JAVA_OPTS="-Xminf0.1 -Xmaxf0.3"
# Use JAVA_OPTS to set java.library.path for libtcnative.so
#JAVA_OPTS="-Djava.library.path=/usr/lib
# Where your tomcat installation lives
CATALINA_HOME="/opt/apache-tomcat_7.0.23"
export CATALINA_HOME
# What user should run tomcat
TOMCAT_USER="tomcat"
# You can change your tomcat locale here
#LANG=en_US
# Time to wait in seconds, before killing process
SHUTDOWN_WAIT=30
# Set the TOMCAT_PID location
CATALINA_PID=/var/run/tomcat70.pid
# If you wish to further customize your tomcat environment,
# put your own definitions here
# (i.e. LD_LIBRARY_PATH for some jdbc drivers)
# Just do not forget to export them :)
# Start HeapSize 512M, Max 1024MB, PermGenSize 512MB
JAVA_OPTS="-Xmx1024M -Xms512M -XX:MaxPermSize=512M"
# AWT Headless Mode and JMX for Lambda-PROBE application
JAVA_OPTS="$JAVA_OPTS -Djava.awt.headless=true"
JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote"
export JAVA_OPTS

6. Create the start script /etc/init.d/tomcat70
This script is based on an old JPackage init.d script for tomcat4 and was heavily modified over time by me  - and of course properly supports PID file handling. This is especially needed for tomcat stop in production environment, because sometimes Tomcat is not able to stop when big applications are deployed (e.g. because of Log4J class locking)

/etc/init.d/tomcat70 content:


#!/bin/sh
#
# tomcat70 Startup script for Tomcat 7.0, the Apache Servlet Engine
#
#
# chkconfig: - 80 16
# description: Tomcat 7.0 is the Apache Servlet Engine
# processname: tomcat70
# config:  /etc/tomcat/tomcat70.conf
# pidfile: /var/run/tomcat70.pid
#
# Gomez Henri <hgomez@users.sourceforge.net>
# Keith Irwin <keith_irwin@non.hp.com>
# Nicolas Mailhot <nicolas.mailhot@one2team.com>

# Adapted and extended for Tomcat6, Tomcat7 default installation on CentOS by Robert Oschwald <robertoschwald@google****.com>
# Note: You must create a config file /etc/tomcat/tomcat70.conf


# version 1.02 - Removed initlog support
# version 1.03 - Removed config:
# version 1.04 - tomcat will start before httpd and stop after httpd
# version 1.05 - jdk hardcoded to link /usr/java/jdk and tomcat runs as "nobody"
# version 1.06 - split up into script and config file
# version 1.07 - Rework from Nicolas ideas
# version 1.08 - Fix work dir permission at start time, switch to use tomcat4
# version 1.09 - Fix pidfile and config tags
# version 1.10 - Fallback to su direct use on systems without Redhat/Mandrake init.d functions
# version 1.11 - Fix webapps dir permissions
# version 1.12 - remove initial start/stop level for chkconfig (- 80 20)
# version 1.13 - remove chown of logs/work/temp/webapps dir, owned by tomcat4 at install time
# version 1.14 - correct the start/stop ugly hack by waiting all the threads stops
# version 1.15 - ensure we're looking for TOMCAT_USER running catalina
# version 1.16 - Add support for CATALINA_PID env var
# version 1.17 - Remove run files only tomcat started correctl
#                in start area, check that tomcat is not allready running
# version 1.18 - Fix kill typo (thanks Kaj J. Niemi)
# version 1.19 - Add jar relinking
# version 1.20 - Check there is no stalling tomcat4.pid
# version 1.20tc5 - Changed all instances of tomcat4 to tomcat5 except TOMCAT_USER
# version 1.21 - Add status command
# version 1.21a - Adapted for tomcat55 manual installation, roos
# version 1.21b - tomcat55.conf handling added, roos
# version 1.21b-a - moved tomcat55.conf
# version 1.21b-b - some more changes due to multi-tomcat boot
# version 1.21b-c - tomcat7 handling added, roos
#


# Source function library.
if [ -x /etc/rc.d/init.d/functions ]; then
. /etc/rc.d/init.d/functions
fi


# Get Tomcat config
TOMCAT_CFG="/etc/tomcat/tomcat70.conf"
[ -r "$TOMCAT_CFG" ] && . "${TOMCAT_CFG}"


# if CATALINA_HOME is not set, boil out
if [ -z "$CATALINA_HOME" ]; then
    echo "Fatal! CATALINA_HOME not set in tomcat70.conf"
    exit 1
fi


# Path to the tomcat launch script (direct don't use wrapper)
TOMCAT_SCRIPT=$CATALINA_HOME/bin/catalina.sh


# by roos, propper umask
umask 002


# Tomcat name :)
TOMCAT_PROG=tomcat70
        
# if TOMCAT_USER is not set, use tomcat like Apache HTTP server
if [ -z "$TOMCAT_USER" ]; then
    TOMCAT_USER="apache"
fi


# Since the daemon function will sandbox $tomcat
# no environment stuff should be defined here anymore.
# Please use the /etc/tomcat/tomcatXX.conf file instead ; it will
# be read by the $tomcat script


RETVAL=0


# added by roos to be able to delete logfiles
umask 002


# See how we were called.
start() {
    echo -n "Starting $TOMCAT_PROG: "
if [ -f /var/lock/subsys/tomcat70 ] ; then
  if [ -f $CATALINA_PID ]; then
          read kpid < $CATALINA_PID
          # if checkpid $kpid 2>&1; then
if [ -d "/proc/${kpid}" ]; then
              echo "process allready running"
              return -1
          else
              echo "lock file found but no process running for pid $kpid, continuing"
  fi
  fi
  fi

  touch $CATALINA_PID
  chmod 0777 $CATALINA_PID
  chown $TOMCAT_USER $CATALINA_PID


if [ -x /etc/rc.d/init.d/functions ]; then
        daemon --user $TOMCAT_USER $TOMCAT_SCRIPT start 
    else
        su - $TOMCAT_USER -c "$TOMCAT_SCRIPT start"
    fi


    RETVAL=$?
    echo
    [ $RETVAL = 0 ] && touch /var/lock/subsys/tomcat70
    return $RETVAL
}


function stop() {
    RETVAL="0"
    echo -n "Stopping ${TOMCAT_PROG}: "
    if [ -f "/var/lock/subsys/tomcat70" ]; then
if [ -x /etc/rc.d/init.d/functions ]; then
            daemon --user $TOMCAT_USER $TOMCAT_SCRIPT stop
        else
            su - $TOMCAT_USER -c "$TOMCAT_SCRIPT stop"
        fi
        RETVAL=$?
        if [ "$RETVAL" -eq "0" ]; then
            count="0"
            if [ -f $CATALINA_PID ]; then
                read kpid < $CATALINA_PID
                if [ -z $kpid ]; then
                  # already stopped
                  rm -f /var/lock/subsys/tomcat70 $CATALINA_PID
                  return $RETVAL
                fi
                until [ "$(ps --pid $kpid | grep -c $kpid)" -eq "0" ] || \
                      [ "$count" -gt "$SHUTDOWN_WAIT" ]; do
                        echo "waiting for processes $kpid to exit"
                    sleep 1
                    let count="${count}+1"
                done
                if [ "$count" -gt "$SHUTDOWN_WAIT" ]; then
                        echo "killing processes which didn't stop after $SHUTDOWN_WAIT seconds"
                    kill -9 $kpid
                fi
            fi
            rm -f /var/lock/subsys/tomcat70 $CATALINA_PID
        fi
    fi
    return $RETVAL
}




status() {
if [ -f /var/lock/subsys/tomcat70 ] ; then
  if [ -f $CATALINA_PID ]; then
          read kpid < $CATALINA_PID
          if checkpid $kpid 2>&1; then
              echo "tomcat70 is running ($kpid)"
return 0
          else
              echo "lock file found but no process running for pid $kpid"
  fi
  fi
  fi
echo "tomcat6 is stopped."
return 1
}




# See how we were called.
case "$1" in
  start)
        start
        ;;
  stop)
        stop
        ;;
  status)
status
exit $?
;;
  restart)
        stop
        sleep 2
        start
        ;;
  condrestart)
        if [ -f $CATALINA_PID ] ; then
                stop
                start
        fi
        ;;
  *)
        echo "Usage: $TOMCAT_PROG {start|stop|status|restart|condrestart}"
        exit 1
esac


exit $RETVAL

7. set permissions

chmod 755 /etc/init.d/tomcat70

chown -R tomcat /opt/apache-tomcat-7.0.23

chmod -R 775 /opt/apache-tomcat-7.0.23


8. create setenv.sh file 
My impression is that this is the most-overseen feature of Apache-Tomcat installation. Many people edit catalina.sh directly to set environment vars like JAVA_HOME.
But there is a standard way to add environment vars by adding a setenv.sh file to the tomcat bin directory without modifying anything in the original files by adding a setenv.sh file to the tomcat bin dir.

/opt/tomcat-7.0.23/bin/setenv.sh content:

# setenv.sh to read env var in catalina.sh
TOMCAT_CFG="/etc/tomcat/tomcat70.conf"
[ -r "$TOMCAT_CFG" ] && . "${TOMCAT_CFG}"




9. Start tomcat
/etc/init.d/tomcat70 start



Hope that helps someone. Any comments appreciated.