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
- shutting down your tomcat server
- cleaning any deployment directories
- defining a rollback point
- deploying the new version of your app
- 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...
No comments:
Post a comment