DevOps Zone is brought to you in partnership with:

Lives in the UK. Likes blogging, cycling and eating lemon drizzle cake. Roger is a DZone MVB and is not an employee of DZone and has posted 143 posts at DZone. You can read more from them at their website. View Full User Profile

Super Quick Tomcat App Deployment Using a PULL Script

01.14.2013
| 7606 views |
  • submit to reddit

If you managed to read my last blog you'll remember that I demonstrated a simple script for creating a new tomcat installation on a server by splitting the tomcat binaries from the conf files, storing the binaries on a FTP server and the conf files in version control, with a script recombining the two parts.

The next and most obvious improvement to this idea is to create a system for automatically deploying an application once it has compiled and passed its unit tests. There are many ways of doing this, from purchasing high end applications from hopefully reputable suppliers to writing your own Heath Robinson system based on glue, scissors and sticky tape; however, to me, no matter which way you look at it there are only ever going to be two viable methods of achieving automatic deployment. The first is the pull method and the seconds is its counterpart: push.

Assuming that you have a Hudson, Jenkins or some other build server automatically compiling and unit testing your code check ins, then a PULL process is some script or process that runs on your tomcat machine that's triggered either manually or automatically and PULLs the latest WAR file from your build server's output repository, deploying it on your tomcat server.


On the other hand, the PUSH process involves the build server running a script or carrying out some process that delivers, or PUSHES, your WAR file from the build server to your web server.


An example of a push system would be using the Maven Tomcat plugin running the "tomcat:redeploy" goal as part of your build process. For example "mvn install tomcat:redeploy". This means that when your Hudson or Jenkins build runs, and all your tests pass, then your WAR file is automatically deployed to your server; however, this isn't the only push mechanism available and it does have the drawback that is inherent in all systems that use your tomcat server as part of the deployment process.

The problem with using tomcat as part of the deployment process lies not with tomcat, but with your code. You have to be sure that your app doesn't contain any memory leaks, which is sometimes hard to do. If your app does leak memory, then after a redeploy or two you'll get an java.lang.OutOfMemoryError: PermGen exception thrown by tomcat, which means manual intervention is required to restart the server. Remember that a java.lang.OutOfMemoryError: PermGen exception is your fault and not tomcat's.

To me it seems far better to manage your application's redeployment without any help from tomcat. This allows you to perform the basic steps of
  1. shutting down your tomcat server
  2. cleaning any deployment directories
  3. defining a rollback point
  4. deploying the new version of your app
  5. restarting the server

The benefit of stopping and restarting the server is that you have a 'clean' server on which to commence your testing and so, baring this in mind the sample script below demonstrates all the above points using my address sample from the CaptainDebug Github repository.
#!/bin/bash

echo "Running Address Application install Script"

TOMCAT_VERSION=apache-tomcat-7.0.33-blog

# The FTP server holding the tomcat binaries
SERVER=<Your Server Name>
REPOSITORY=/.m2/repository/
ARTIFACT_ID=com/captaindebug/address/
APP=address
TYPE=WAR

SERVER_USER=<Your User Name>
SERVER_PASSWORD=<The Password Goes Here>
CUT_DIRS=6
NEW_VERSION_ARG=$1

wait_for_shutdown() {

 i=1
 while [ $i -le 20 ]
    do
    ps -ef | grep catalina.startup.Bootstrap > fred.txt
    if ls -l fred.txt | grep 81
    then
       i=21
    else 
       i=`expr $i + 1`
       sleep 1
    fi 
 done
}

check_version() {

    if [ -z $NEW_VERSION_ARG ] 
    then
        VERSION=1.0.0-BUILD-SNAPSHOT
    else
         VERSION=$NEW_VERSION_ARG  
    fi
    echo "Using version $VERSION"
}

make_app_location() {

    check_version
    APP_LOCATION=$REPOSITORY$ARTIFACT_ID$VERSION/$APP-$VERSION.$TYPE
}

do_shutdown() {

    cd ../$TOMCAT_VERSION
    bin/shutdown.sh

    wait_for_shutdown
}

clean_directories() {

    echo changing directory
    cd ../$TOMCAT_VERSION/webapps
    pwd
    rm -rf $APP
    rm $APP.$TYPE
}

get_new_version() {

    WAR_FILE=ftp://$SERVER_USER:$SERVER_PASSWORD@$SERVER$APP_LOCATION
    echo $WAR_FILE
    wget -r -nH --cut-dirs=$CUT_DIRS $WAR_FILE
}

create_rollback_point() {

    DATE=`date`
    BAK_FILE=$APP-$VERSION.$TYPE.$DATE.bak
    echo "Backup file is $BAK_FILE"
    mv "$APP-$VERSION.$TYPE" "$BAK_FILE"
    ln -s "$BAK_FILE"  $APP.$TYPE
}

restart_server() {

    cd ..
    pwd
    bin/startup.sh
}

make_app_location
do_shutdown
clean_directories
get_new_version
create_rollback_point
 
echo "The directory now looks like this:"
ls -l
 
restart_server 
In this scenario, I've got the address sample code building on my Hudson server with the result being stored in a Maven repository. The script resides in a scripts directory that's at the same level as the server: apache-tomcat-7.0.33-blog. The script has a very Heath Robinson method of shutting down the server and ensuring that it is shutdown before continuing. It does this by checking the file size of a temp file call fred.txt. On continuing, it cleans the existing directories, removing the address deployment directory and a symlink to the war file. It then uses wget to grab hold of the new version of the app, with the version being either specified on the command line or defaulting to 1.0.0-BUILD-SNAPSHOT.

The next thing to do is to create a rollback point so that we can rollback the latest deployment if we find that it contains a bunch of hideous mistakes. The first step here is to rename the transferred WAR file, appending the date and a ".bak" extension. Hence,

address-1.0.0-BUILD-SNAPSHOT.WAR

becomes:

address-1.0.0-BUILD-SNAPSHOT.WAR.Tue 1 Jan 2013 20:18:21 GMT.bak

Appending the date is useful when deploying snapshots and the ".bak" means that the file won't be loaded by tomcat. The second step in setting up rollback is achieved by using a symbolic link to the renamed WAR file, so that the deployed WAR will always be known as address.war irrespective of any version number embedded in the filename.

Using a symbolic link in this way allows us to rollback application versions by stopping the the server, deleting the symlink, creating a new symlink to the old version of the app and then restarting the server.

The final line of the script restarts the server.

The next thing to remember is to store your script in version control.

Yes, I know that this script isn't as elegant as it could be. If anyone has suggestions for improvements then please let me know...
The added benefit of using a script like this is that you can use the same script to deploy your application's WAR file in every environment: dev, test, UAT, production or whatever. This has the advantage of making your local dev environment just that tiny bit closer to looking like the production environment; the idea being that this will minimise deployment errors and mistakes making your life just that little bit easier. Remember, the days are long gone when you can say "well it works on my machine" to a complaint from the ops guys that your app isn't working.

The next link in the deployment chain is that something should now take over and run a set of automatic acceptance tests, but more on that later...


 

Published at DZone with permission of Roger Hughes, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)