Scripted appserver war deployment example
From ControlTier
Requires Version 3.4.5 (help?)
This example shows how the ControlTier framework can provide a useful set of features that can make the scripting you create for application deployment simpler, more flexible, and ultimately more accessible. It uses the script-driven approach and strategies from the Scripters cookbook.
This document shows the basics of using ctl-exec, coreutils, and the use of a WebDAV repository to deploy a Tomcat application server, and a simple webapp called "simple". This deployment will be referred to as "simpleTomcat" and the deployment process for that application will be done via shell script named "simpleTomcat.sh".
This document walks through several iterations of the simpleTomcat.sh script implementation showing how the script is progressively made more flexible and reusable by taking advantage of ControlTier features and other best practices. Along the way, you will also see how these examples adhere to essential ControlTier Concepts.
Contents |
Dependencies
This example has these dependencies.
- ControlTier — 3.4.5
- Meet the prerequisites and install ControlTier according to the Installing ControlTier instructions.
- Unix
- This example is not compatible with Windows. It is compatible with Linux, Mac OS X and most unix variants.
- "demo" project
- Be sure the "demo" project has been created across all nodes you will run this example
- Tomcat — 5.5.28
- Download Zip: http://tomcat.apache.org/download-55.cgi or here v5.5.28
- Copy the downloaded Zip to
$CTIER_ROOT/examples/scripted-appserver-war-deployment/pkgs/apache-tomcat-5.5.28.zip - Make sure you went through the example Setting up a Package for import, as it creates a package this examples depends on.
Note: A WAR file is included in the $CTIER_ROOT/examples/scripted-appserver-war-deployment/pkgs directory so there is no external dependency for that.
Scripting with ctl-exec
The ctl-exec command provides a convenient method to execute a local script file across a set of hosts. In this mode, ctl-exec first copies the script to the remote host and then executes it. The ctl-exec command supports several other useful features for executing commands across a set of hosts including: filtering logic, parallelism, and error handling.
Rather than require you to name each host explicitly, the ctl-exec command lets you dispatch to a set of nodes through a filter-based lookup option. Each node registered to a ControlTier project includes a set of standard metadata that you can use in your filter specification. The examples discussed here will use tags to execute a script to just the nodes that share that tag name. Tags are useful to create a symbolic name for a set of nodes that share a characteristic, like a particular application they might all have resident.
You can run any script from the ControlTier server host across a set of tagged nodes using ctl-exec with this usage format:
ctl-exec -p project -I tags=tagname -s script
Letting ctl-exec do the dispatching for you, reduces complexity from the script writing. With ctl-exec as a front end, you avoid writing iteration and remote command dispatch logic from your scripts. This lets you write your scripts so that they always assume a local execution mode and thus easier to trouble shoot.
In the examples that follow, nodes where the "simple" application and Tomcat deployed are referred to as "simpleTomcat". This means anytime ctl-exec is used to call commands against nodes with "simpleTomcat" just those nodes are selected.
The diagram below illustrates ctl-exec executing the "simpleTomcat.sh" script to the nodes tagged "simpleTomcat":
The specific command usage to support the scenario shown in the diagram above is shown here:
ctl-exec -p demo -I tags=simpleTomcat -s simpleTomcat.sh
Scripting with WebDAV
Deployment procedures typically involve the distribution of various kinds of files to the nodes where the deployments reside. These files may be packaged artifacts that come from an internal build process. They may be 3rd party software packages. Configuration files may also need to be distributed. Often times these files are staged to a central administrative host and either pushed out via SCP. Alternatively, a web server might be used to make files accessible by HTTP clients such as wget or curl to pull them where needed.
Since file distribution is so essential to managing deployment, the ControlTier server includes a WebDAV server. WebDAV is a set of extensions to HTTP that make web content read/write. WebDAV provides a convenient way to loading the repository with files your deployment procedures need and letting you access them via any HTTP client.
In the examples that follow, you will see how the WebDAV facility is leveraged via commands from the coreutils library. In these examples, Zip and War files that are to deployed are first staged in the WebDAV and later pulled down.
The four deployment steps
In each iteration of the deployment scripting, you will see how the deployment process is always carried out in four main steps:
- Shutdown: Stop the server (tomcat)
- Check if it is running and if so, run the tomcat shutdown.sh script
- Installation: Install packages
- Download and extract the Tomcat Zip and Simple WAR files
- Configuration: Configure the server (tomcat)
- Customize the tomcat server.xml to run on different ports. Change the file permissions for the tomcat stop/start scripts to be executable
- Startup: Start the server (tomcat)
- Run the tomcat startup.sh script. Verify it is running by checking the listening port.
Any time the deployment process is performed, these same four steps will be executed.
Iterative approach
The example is broken down into several development iterations. During each iteration, the "simpleTomcat.sh" deployment script is re-implemented to take advantage of ControlTier features to make it more flexible, reusable and maintainable.
The table below summarizes the goal to meet for each iteration.
| Iteration | Implementation changes |
|---|---|
| 0 | Original script (no change) |
| 1 | Eliminate SSH calls and handcoded for loop with ctl-exec. Replace use of scp to push files with pull-based model supported by WebDAV |
| 2 | Introduce utility modules and decompose single script into a set of simpler but separate scripts accessible from WebDAV |
| 3 | Expose individual steps as a set of sub commands controlled through command line option |
Iteration Zero
We begin with a scripting approach that assumes no ControlTier use and is a typical example for executing a process like the one for the simple Tomcat deployment. The basic requirement is to execute #The four deployment steps across all the nodes where we want the "simple" Tomcat webapp deployed. It's inconvenient and time consuming to login to each machine to run these steps, so there is an additional requirement that it be done from a central administrative host over SSH. Finally, the Zip and WAR files will be staged to a central web server to make them easily distributable to anywhere on the network.
The scripts
Everything is implemented in a single script, "simpleTomcat.sh"
v0/simpleTomcat.sh
Here's the implementation of the simpleTomcat.sh that uses no ControlTier functionality. It's been organized by comments to show the for main steps.
The script has been designed so each step is done across the nodes before the next step is performed. The basic code structure is shown below.
You can see it's implemented using the for shell builtin well known in Unix shell. The set of nodes to execute the steps are specified by the user and referenced inside the script as $NODES. Inside the for loop are one or more calls to SSH to execute the needed command:
for node in $NODES; do ssh $node command1 ... ssh $node another command ... and so on done
At the top of the script are a few shell variables to specify the URL to get packages and specify package names, versions and install directory.
Here's how the "simpleTomcat.sh" implements the #The four deployment steps :
- Step 1: Start tomcat
- Remotely execute the
netstatandgrepcommands to check if the Tomcat server is running and hence listening on its port. If it is, then stop it by ssh'ing the tomcatCATALINA_HOME/bin/shutdown.shscript.
- Remotely execute the
- Step 2: Install packages
- Remotely execute
curlto get files from the WebDAV repository. Then sshunzipto extract each archive into the specified directory.
- Remotely execute
- Step 3: Configure tomcat
- Remotely execute
sedto substitute port settings in the server.xml. Then use ssh tochmodthe files in CATALINA_HOME/bin/* to set their execute bit.
- Remotely execute
- Step 4: Start tomcat
- SSh the
CATALINA_HOME/bin/startup.shscript to start Tomcat. Verify that the Tomcat server comes up by using SSh to callnetstatandgrepto check the process is bound to its port.
- SSh the
Additionally, each step is demarcated with a set of messages emitted by the echo command. These will show begin/end messages to make it easier to follow progress when watching the output.
#!/bin/sh # File: v0/simpleTomcat.sh die() { echo "ERROR: $@" ; exit 1 ; } [ -d "$CTL_BASE" ] || { die "ERROR: CTL_BASE not found" ; } USAGE="simpleTomcat.sh <node> <node> <node>" [ "$#" -lt 1 ] && { die "Usage: $USAGE" ; } # Nodes where simple webapp Tomcat deployement reside NODES="$@" # The zip and war files reside inside a WebDAV REPO_URL=http://strongbad:8080/jackrabbit/repository/workbench TOMCAT_PKGBASE=apache-tomcat-5.5.28 SIMPLE_PKGBASE=simple INSTALL_DIR=ctier/pkgs export CATALINA_HOME=$INSTALL_DIR/$TOMCAT_PKGBASE export CATALINA_BASE=$CATALINA_HOME; echo "Deploy simple tomcat. Steps: stop, install, configure, start" # # Step 1: Stop tomcat # for node in $NODES; do echo "begin stop (1/4) ..." ssh $node netstat -an|grep 28080|grep -q LISTEN && { echo "Tomcat listening (port=28080). Running stop script ..." ssh $node $CATALINA_HOME/bin/shutdown.sh || die "Script failed: stop.sh" } echo "end stop (1/4)" done # # Step 2: Install Packages # for node in $NODES; do echo "begin install (2/4) ..." scp $CTIER_ROOT/examples/scripted-appserver-war-deployment/pkgs/$TOMCAT_PKGBASE.zip \ $node:$INSTALL_DIR/$TOMCAT_PKGBASE.zip || die "Download failed: $TOMCAT_PKGBASE.zip" ssh $node test -d $CATALINA_HOME && { echo "removing old CATALINA_HOME" ssh $node rm -r $CATALINA_HOME || die "failed cleaning existing tomcat deployment" } ssh $node "cd $INSTALL_DIR; unzip -q $TOMCAT_PKGBASE.zip" || die "Extract failed: $TOMCAT_PKGBASE.zip" ssh $node mkdir -p $CATALINA_HOME/webapps/$SIMPLE_PKGBASE || { die "failed making $SIMPLE_PKGBASE directory" } scp $CTIER_ROOT/examples/scripted-appserver-war-deployment/pkgs/$SIMPLE_PKGBASE.war \ $node:$CATALINA_HOME/webapps/$SIMPLE_PKGBASE/$SIMPLE_PKGBASE.war || { die "Download failed: $SIMPLE_PKGBASE.war" } # extract the WAR ssh $node "cd $CATALINA_HOME/webapps/$SIMPLE_PKGBASE; unzip -q $SIMPLE_PKGBASE.war" || { die "Extract failed: $SIMPLE_PKGBASE.war" } ssh $node rm $CATALINA_HOME/webapps/$SIMPLE_PKGBASE/$SIMPLE_PKGBASE.war echo "end install (2/4)" done # # Step 3: Configure tomcat # for node in $NODES; do echo "begin configure (3/4) ..." echo "Chmod'ing files in $CATALINA_BASE/bin/*.sh" ssh $node chmod +x $CATALINA_BASE/bin/*.sh || die "chmod step failed" echo "Customizing $CATALINA_BASE/conf/server.xml ..." ssh $node "sed 's/8080/28080/g' $CATALINA_BASE/conf/server.xml > /tmp/server.xml" ssh $node "mv /tmp/server.xml $CATALINA_BASE/conf/server.xml" || { die "Text replace failed: $CATALINA_BASE/conf/server.xml" } ssh $node "sed 's/8005/28005/g' $CATALINA_BASE/conf/server.xml > /tmp/server.xml" ssh $node "mv /tmp/server.xml $CATALINA_BASE/conf/server.xml" || { die "Text replace failed: $CATALINA_BASE/conf/server.xml" } ssh $node "sed 's/8009/28009/g' $CATALINA_BASE/conf/server.xml > /tmp/server.xml" ssh $node "mv /tmp/server.xml $CATALINA_BASE/conf/server.xml" || { die "Text replace failed: $CATALINA_BASE/conf/server.xml" } echo "$CATALINA_BASE/conf/server.xml customized" echo "end configure (3/4)" done # # Step 4: Start tomcat # for node in $NODES; do echo "begin start (4/4) ..." echo "Invoking $CATALINA_HOME/bin/startup.sh" ssh $node $CATALINA_HOME/bin/startup.sh || die "Script failed: start.sh" # verify it is running sleep 10; # Wait 10 seconds to give Tomcat time to start and be read ssh $node netstat -an|grep 28080|grep -q LISTEN || die "Status failed: Tomcat not listening: (port=28080)" echo "end start (4/4)" echo "Deploy simple tomcat completed. Visit: http://$(hostname):28080/$SIMPLE_PKGBASE" done # end v0/simpleTomcat.sh
Running
v0/simpleTomcat.sh
From the admin host run the simpleTomcat.sh script specifying the nodes you want to deploy simple tomcat:
execute:
cd $CTIER_ROOT/examples/scripted-appserver-war-deployment/scripts
Change directory to the "scripts" subdirectory of the scripted-appserver-war-deployment example
$ ./v0/simpleTomcat.sh centos2
You should see output resembling the messages shown here:
output:
Deploy simple tomcat. Steps: stop, install, configure, start begin stop (1/4) ... Tomcat listening (port=28080). Running stop script ... end stop (1/4) begin install (2/4) ... removing old CATALINA_HOME end install (2/4) begin configure (3/4) ... Chmod'ing files in ctier/pkgs/apache-tomcat-5.5.28/bin/*.sh Customizing ctier/pkgs/apache-tomcat-5.5.28/conf/server.xml ... ctier/pkgs/apache-tomcat-5.5.28/conf/server.xml customized end configure (3/4) begin start (4/4) ... Invoking ctier/pkgs/apache-tomcat-5.5.28/bin/startup.sh end start (4/4) Deploy simple tomcat completed. Visit: http://strongbad.local:28080/simple
Once the script completes you should be able to access the deployed Tomcat and "simple" application. Visit the link like so (eg, http://centos2:28080/simple/):
You should see the current date and time displayed in the browser page.
Observations
The v0/simpleTomcat.sh script was sufficient for the task of managing the steps in the deployment process. There are several observations worth noting:
- Remote command execution (a form of dispatching) and individual deployment tasks are intermingled
- Due to #1, harder to debug because can't run the procedure locally on just one node
- Evaluating conditions is tricking when using SSH. Relies on non-zero SSH result code processing
- The "LISTEN" status used by the netstat/grep pipeline doesn't really say if the server is responsive or not
- Must be conscious of quoting when executing multipart command strings over SSH (eg, when having to do things in relative directories)
- Uses scp to push files from admin host. Push-mode for file distribution can sometimes be a scaling bottleneck.
Iteration One
Let's refactor the simpleTomcat.sh script to address some of the shortcomings mentioned above. The main approach here will be to:
- Separate the SSH logic from the functional steps by introducing ctl-exec
- Make all the steps work more atomically
- Enable easier debugging
- Use the WebDAV server to facilitate a pull-model for file distribution.
- Lets HTTP clients pull the artifacts where needed
The scripts
In this iteration you will see the implementation has changed to eliminate all calls to the ssh command for remote execution. Instead the script will assume to be executed via ctl-exec.
v1/simpleTomcat.sh
As in #v0/simpleTomcat.sh the script implements the four steps mentioned in the overview like so except this time all the steps are executed locally.
File listing: v1/simpleTomcat.sh
#!/bin/sh # File: v1/simpleTomcat.sh die() { echo "ERROR: $@" ; exit 1 ; } [ -d "$CTL_BASE" ] || { die "ERROR: CTL_BASE not found" ; } DAV_URL=http://strongbad:8080/jackrabbit/repository/workbench TOMCAT_PKGBASE=apache-tomcat-5.5.28 SIMPLE_PKGBASE=simple INSTALL_DIR=$CTIER_ROOT/pkgs export CATALINA_HOME=$INSTALL_DIR/$TOMCAT_PKGBASE export CATALINA_BASE=$CATALINA_HOME; echo "Deploy simple tomcat. Steps: stop, install, configure, start" # # Step 1: Ensure tomcat down if running # echo "begin stop (1/4) ..." netstat -an|grep 28080|grep -q LISTEN && { echo "Tomcat listening (port=28080). Running stop script ..." $CATALINA_HOME/bin/shutdown.sh || die "Script failed: stop.sh" } echo "end stop (1/4)" # # Step 2: Install Packages # echo "begin install (2/4) ..." curl -s --user default:default $DAV_URL/pkgs/demo/zip/zips/$TOMCAT_PKGBASE.zip \ -o $INSTALL_DIR/$TOMCAT_PKGBASE.zip || die "Download failed: $TOMCAT_PKGBASE.zip" [ -d $CATALINA_HOME ] && { rm -r $CATALINA_HOME || die "failed cleaning existing tomcat deployment" } cd $INSTALL_DIR unzip -q $INSTALL_DIR/$TOMCAT_PKGBASE.zip || die "Extract failed: $TOMCAT_PKGBASE.zip" curl -s --user default:default $DAV_URL/pkgs/demo/war/wars/$SIMPLE_PKGBASE.war \ -o $INSTALL_DIR/$SIMPLE_PKGBASE.war || die "Download failed: $SIMPLE_PKGBASE.war" # extract the WAR mkdir -p $CATALINA_HOME/webapps/$SIMPLE_PKGBASE || { die "failed making $SIMPLE_PKGBASE directory" } cd $CATALINA_HOME/webapps/$SIMPLE_PKGBASE unzip -q $INSTALL_DIR/$SIMPLE_PKGBASE.war || die "Extract failed: $SIMPLE_PKGBASE.war" echo "end install (2/4)" # # Step 3: Configure tomcat # echo "begin configure (3/4) ..." echo "Chmod'ing files in $CATALINA_BASE/bin/*.sh" chmod +x $CATALINA_BASE/bin/*.sh || die "chmod step failed" echo "Customizing $CATALINA_BASE/conf/server.xml ..." sed 's/8080/28080/g' $CATALINA_BASE/conf/server.xml > /tmp/server.xml mv /tmp/server.xml $CATALINA_BASE/conf/server.xml || { die "Text replace failed: $CATALINA_BASE/conf/server.xml" } sed 's/8005/28005/g' $CATALINA_BASE/conf/server.xml > /tmp/server.xml mv /tmp/server.xml $CATALINA_BASE/conf/server.xml || { die "Text replace failed: $CATALINA_BASE/conf/server.xml" } sed 's/8009/28009/g' $CATALINA_BASE/conf/server.xml > /tmp/server.xml mv /tmp/server.xml $CATALINA_BASE/conf/server.xml || { die "Text replace failed: $CATALINA_BASE/conf/server.xml" } echo "$CATALINA_BASE/conf/server.xml customized" echo "end configure (3/4)" # # Step 4: Start tomcat # echo "begin start (4/4) ..." echo "Invoking $CATALINA_HOME/bin/startup.sh" $CATALINA_HOME/bin/startup.sh || die "Script failed: start.sh" # verify it is running sleep 10; # Wait 10 seconds to give Tomcat time to start and be read netstat -an|grep 28080|grep -q LISTEN || die "Status failed: Tomcat not listening: (port=28080)" echo "end start (4/4)" echo "Deploy simple tomcat completed. Visit: http://$(hostname):28080/$SIMPLE_PKGBASE" # end v1/simpleTomcat.sh
v1/setup.sh
A setup.sh script will carry out a few steps ahead of doing the deployment.
Primarily, it tags your specified Nodes with the name "simpleTomcat" and then upload the apache-tomcat-5.5.28.zip and simple.war files to the WebDAV.
It makes use of the davutil module (one of the coreutils you'll learn about more later) to perform the WebDAV PUT operations.
The add-tag command is used to set each node with the name "simpleTomcat".
#!/bin/sh
# Name: v1/setup.sh
# Usage: setup.sh <node> <node> <node>
die() { echo "ERROR: $@" ; exit 1 ; }
[ -d $CTIER_ROOT/examples/scripted-appserver-war-deployment ] || {
die "Example directory not found: $CTIER_ROOT/examples/scripted-appserver-war-deployment"
}
#
# Tag the nodes
#
[ $# -gt 0 ] && {
echo "Adding simpleTag to these nodes: [$@] ..."
for node in $@; do
ctl -z -p demo -m ProjectBuilder -c add-tag --\
-type Node -name $node -tag simpleTomcat
done
}
#
# Upload the packages
#
cd $CTIER_ROOT/examples/scripted-appserver-war-deployment/pkgs
# Ensure the Tomcat zip was downloaded.
[ -f apache-tomcat-5.5.28.zip ] || {
die "Not found: apache-tomcat-5.5.28.zip. Go to http://tomcat.apache.org/download-55.cgi and save it here: `pwd`"
}
# Upload the Zip
ctl -z -p demo -m davutil -c put --\
-file `pwd`/apache-tomcat-5.5.28.zip \
-url dav://pkgs/demo/zip/zips/simple.apache-tomcat-5.5.28.zip || {
die "Upload failed: apache-tomcat-5.5.28.zip"
}
# Upload the War
ctl -z -p demo -m davutil -c put --\
-file `pwd`/simple.war \
-url dav://pkgs/demo/war/wars/simple.war || die "Failed uploading simple.war"
Running
Begin by running the setup and then run the deployment script.
v1/setup.sh
The setup.sh script is invoked directly.
-
cd $CTIER_ROOT/examples/scripted-appserver-war-deployment/scripts/v1/
- Change directory to the "v1" subdirectory
-
sh ./setup.sh <node>
- Run the setup script specifying the node (You can specify a space separated list) you want to deploy Tomcat to. Example:
sh ./setup.sh centos2
- Run the setup script specifying the node (You can specify a space separated list) you want to deploy Tomcat to. Example:
The setup.sh script should emit output resembling the messages below:
Adding simpleTomcat to these nodes: [centos2] ... Deleting: /Users/alexh/ctier/ctl/var/sessions/find-objects/add-tag.session | |--(Node) centos2 session data saved: /Users/alexh/ctier/ctl/var/sessions/find-objects/add-tag.session Found [1] objects. Adding tags ... Tagging object with tag simpleTomcat: centos2[Node] ... Uploading to: http://strongbad:8080/jackrabbit/repository/workbench/pkgs/demo/zip/zips/simple.apache-tomcat-5.5.28.zip Uploading: apache-tomcat-5.5.28.zip Puted 1 file to http://strongbad:8080/jackrabbit/repository/workbench/pkgs/demo/zip/zips/simple.apache-tomcat-5.5.28.zip Uploading to: http://strongbad:8080/jackrabbit/repository/workbench/pkgs/demo/war/wars/simple.war Uploading: simple.war Puted 1 file to http://strongbad:8080/jackrabbit/repository/workbench/pkgs/demo/war/wars/simple.war Uploading to: http://strongbad:8080/jackrabbit/repository/workbench/examples/simpleTomcat/scripts/v1 Uploading: simpleTomcat.sh Puted 1 files to http://strongbad:8080/jackrabbit/repository/workbench/examples/simpleTomcat/scripts/v1 Uploading to: http://strongbad:8080/jackrabbit/repository/workbench/pkgs/demo/war/wars/simple.war setup.sh completed
The setup.sh tagged the nodes you specified with the name "simpleTomcat. You can see
each node's tag information via a ctl-exec listing. Here we use ctl-exec -v to list information about the nodes registered in the demo project.
$ ctl-exec -p demo -v centos2: hostname: centos2 os-arch: i386 os-family: unix os-name: Linux os-version: 2.6.9-34.EL tags: [simpleTomcat] strongbad: hostname: strongbad os-arch: i386 os-family: unix os-name: Mac OS X os-version: 10.5.6 tags: [] ubuntu: hostname: ubuntu os-arch: i386 os-family: unix os-name: Linux os-version: 2.6.27-7-generic tags: [simpleTomcat]
Below is a screen shot of Workbench's Node Manager showing information about the same nodes. Detail about "centos2" is revealed making the "simpleTomcat" tag visible.
v1/simpleTomcat.sh
Call the simpleTomcat.sh script via ctl-exec
The ctl-exec command will execute the simpleTomcat.sh script on each node that is tagged "simpleTomcat". The simpleTomcat.sh script will execute locally on each of the nodes.
execute
ctl-exec -p demo -I tags=simpleTomcat -s simpleTomcat.sh
You should see output resembling the messages shown here:
output
number of nodes to dispatch to: 1, (threadcount=1) Connecting to centos2:22 done. Connecting to centos2:22 cmd : chmod +x /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh Connecting to centos2:22 cmd : /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh Deploy simple tomcat. Steps: stop, install, configure, start begin stop (1/4) ... end stop (1/4) begin install (2/4) ... end install (2/4) begin configure (3/4) ... Chmod'ing files in /home/alexh/ctier/pkgs/apache-tomcat-5.5.28/bin/*.sh Customizing /home/alexh/ctier/pkgs/apache-tomcat-5.5.28/conf/server.xml ... /home/alexh/ctier/pkgs/apache-tomcat-5.5.28/conf/server.xml customized end configure (3/4) begin start (4/4) ... Invoking /home/alexh/ctier/pkgs/apache-tomcat-5.5.28/bin/startup.sh end start (4/4) Deploy simple tomcat completed. Visit: http://centos2:28080/simple
Once the script completes you should be able to access the deployed Tomcat and "simple" application. Visit the link like so (eg, http://localhost:28080/simple/).
Observations
The v1/simpleTomcat.sh script was sufficient for the task of managing the steps in the deployment process. There are a couple shortcomings though:
- Hard-coded URL and authentication info to get the archives from the WebDAV repository. It's also a possibility that the curl utility may not be available across all hosts.
- The method used to check if tomcat was listening relied on a single 10 second sleep interval. It would be better to set a maximum wait time and then check periodically to see if Tomcat is running.
- Also, relies on the unix pipeline of
netstatandgrepwhich may not behave the same way across different Unix OS variants.
- Also, relies on the unix pipeline of
- Some of the steps of this script could be broken out into their own files. Simpler and focussed script are often easier to read, understand and maintain.
Iteration Two
Let's refactor the simpleTomcat.sh script to address the shortcomings mentioned above. The basic refactoring strategy will be to:
- Separate the deployment process logic from the individual deployment steps into two layers:
- Simple step-specific scripts that perform the stop, start and configure steps
- The simpleTomcat.sh script's role will become that of the orchestrator of the steps.
- Introduce the use of coreutils commands to address how to:
- access files from the repository
- check the Tomcat listening port
The scripts
In this iteration you will see the implementation of the deployment process broken into four scripts:
- start.sh: Manages the startup step
- stop.sh: Manages the stop step
- configure.sh: Manages the tomcat configuration step
- simpleTomcat.sh: Manages the process itself and calls the above scripts at the needed step
A number of coreutils commands are also used:
- netutil
listeningcommand checks network port status. It has options to set a maximum wait interval and will periodically retry the check - davutil
copycommand will be used to copy resources from the WebDAV to the local file system. - shellutil
execcommand can execute scripts that are stored on the WebDAV.
v2/simpleTomcat.sh
The refactored simpleTomcat.sh script now becomes a mixture of calls to commands in coreutils and calls to the new external scripts. Here's how each step is now be implemented:
- Step 1: Stop tomcat
- Step 2: Install packages
- Use the davutil
copycommand to copy the Zip DAV resource to the local filesystem. If a previous deployment of tomcat exists, it will remove the prior version, and then use theunzipcommand to extract the Zip. - Use the davutil
copycommand to copy the War DAV resource to the local filesystem. Make the CATALINA_HOME/webapps/simple directory, then use theunzipcommand to extract the WAR there.
- Use the davutil
- Step 3: Configure tomcat
- Invoke the
configure.shscript via shellutilexec
- Invoke the
- Step 4: Start tomcat
Each step is demarcated with a set of messages emitted by the echo command.
File listing: v2/simpleTomcat.sh
#!/bin/sh # File: v2/simpleTomcat.sh die() { echo "ERROR: $@" ; exit 1 ; } [ -d "$CTL_BASE" ] || { die "ERROR: CTL_BASE not found" ; } SCRIPTS_URL=dav://examples/simpleTomcat/scripts/v2 TOMCAT_PKGBASE=apache-tomcat-5.5.28 SIMPLE_PKGBASE=simple INSTALL_DIR=$CTIER_ROOT/pkgs CATALINA_HOME=$INSTALL_DIR/$TOMCAT_PKGBASE echo "Deploy simple tomcat. Steps: stop, install, configure, start" # # Step 1: Stop tomcat # echo "begin stop (1/4) ..." listening=`ctl -z -p demo -m netutil -c listening -- -port 28080` [ "$listening" = "true" ] && { echo "Tomcat listening (port=28080). Running stop script ..." ctl -z -p demo -m shellutil -c exec --\ -executable /bin/sh -scripturl $SCRIPTS_URL/stop.sh \ -argline $CATALINA_HOME || die "Script failed: stop.sh" } echo "end stop (1/4)" # # Step 2: Install packages # echo "begin install (2/4) ..." ctl -z -p demo -m davutil -c copy --\ -src dav://pkgs/demo/zip/zips/$TOMCAT_PKGBASE.zip \ -dest $INSTALL_DIR/$TOMCAT_PKGBASE.zip || die "Download failed: $TOMCAT_PKGBASE.zip" [ -d $CATALINA_HOME ] && { rm -r $CATALINA_HOME || die "failed cleaning existing tomcat deployment" } cd $INSTALL_DIR unzip -q $INSTALL_DIR/$TOMCAT_PKGBASE.zip || die "Extract failed: $TOMCAT_PKGBASE.zip" ctl -z -p demo -m davutil -c copy --\ -src dav://pkgs/demo/war/wars/$SIMPLE_PKGBASE.war \ -dest $INSTALL_DIR/$SIMPLE_PKGBASE.war || die "Download failed: $SIMPLE_PKGBASE.war" # extract the WAR mkdir -p $CATALINA_HOME/webapps/$SIMPLE_PKGBASE || { die "failed making $SIMPLE_PKGBASE directory" } cd $CATALINA_HOME/webapps/$SIMPLE_PKGBASE unzip -q $INSTALL_DIR/$SIMPLE_PKGBASE.war || die "Extract failed: $SIMPLE_PKGBASE.war" echo "end install (2/4)" # # Step 3: Configure tomcat # echo "begin configure (3/4) ..." ctl -z -p demo -m shellutil -c exec --\ -executable /bin/sh -scripturl $SCRIPTS_URL/configure.sh \ -argline $CATALINA_HOME || die "Script failed: configure.sh" echo "end configure (3/4)" # # Step 4: Start tomcat # echo "begin start (4/4) ..." ctl -z -p demo -m shellutil -c exec --\ -executable /bin/sh -scripturl $SCRIPTS_URL/start.sh \ -argline $CATALINA_HOME || die "Script failed: start.sh" # verify it is running. Wait up to 10s. Checks every 500ms listening=`ctl -z -p demo -m netutil -c listening -- -port 28080 -maxwait 10` [ "$listening" = "false" ] && die "Status failed: Tomcat not listening: (port=28080)" echo "end start (4/4)" echo "Deployment completed. Visit: http://$(hostname):28080/$SIMPLE_PKGBASE" # end v2/simpleTomcat.sh
stop.sh
The "stop.sh" script is conditionally called in Step#1 in simpleTomcat.sh. The "stop.sh" file is a shell script and wraps the Tomcat shutdown.sh script included in the Tomcat distribution.
File listing: stop.sh
#!/bin/bash # File: stop.sh die() { echo "ERROR: $@" ; exit 1 ; } [ $# = 1 ] || { die "Usage: stop.sh <catalina-home>" ; } [ -d "$1" ] && { export CATALINA_HOME=$1 export CATALINA_BASE=$CATALINA_HOME; $CATALINA_HOME/bin/shutdown.sh; exit $? } || { echo "WARN: CATALINA_HOME not found: $1" ; }
configure.sh
Besides managing the runtime state of the deployed Tomcat instance, there is one more requirement for this example. We need to customize the Tomcat installation to not use its default set of ports.
There are actually several steps carried out in the configure.sh script:
- Set the execute bit on the Tomcat scripts in $CATALINA_HOME/bin.
- Create a backup of the $CATALINA_BASE/conf/server.xml
- Re-write the $CATALINA_BASE/conf/server.xml to replace the standard ports with ones that won't conflict with our environment.
The script makes use of the textutil's replace command to substitute default port settings with ones for the "simple" deployment.
File listing: configure.sh
#!/bin/sh # File: configure.sh die() { echo "ERROR: $@" ; exit 1 ; } [ $# = 1 ] || { die "Usage: configure.sh <catalina-home>" ; } [ -d "$1" ] || { die "CATALINA_HOME not found: $1" ; } CATALINA_BASE=$1 echo "chmod'ing files in $CATALINA_BASE/bin/*.sh" chmod +x $CATALINA_BASE/bin/*.sh || die "chmod step failed" echo "Customizing $CATALINA_BASE/conf/server.xml ..." ctl -p demo -m textutil -c replace --\ -basedir $CATALINA_BASE/conf -filebase server -token 8080 -value 28080 || { die "Text replace failed: $CATALINA_BASE/conf/server.xml" } ctl -p demo -m textutil -c replace --\ -basedir $CATALINA_BASE/conf -filebase server -token 8005 -value 28105 || { die "Text replace failed: $CATALINA_BASE/conf/server.xml" } ctl -p demo -m textutil -c replace --\ -basedir $CATALINA_BASE/conf -filebase server -token 8009 -value 28009 || { die "Text replace failed: $CATALINA_BASE/conf/server.xml" } echo "$CATALINA_BASE/conf/server.xml customized"
start.sh
The "start.sh" script is called in Step#5 in simpleTomcat.sh. The "start.sh" file is a shell script and wraps the Tomcat CATALINA_HOME/bin/startup.sh script included in the Tomcat distribution.
File listing: start.sh
#!/bin/bash # File: start.sh die() { echo "ERROR: $@" ; exit 1 ; } [ $# = 1 ] || { die "Usage: start.sh <catalina-home>" ; } [ -d "$1" ] || { die "CATALINA_HOME not found: $1" ; } export CATALINA_HOME=$1 export CATALINA_BASE=$CATALINA_HOME; echo "Invoking $CATALINA_HOME/bin/startup.sh" $CATALINA_HOME/bin/startup.sh; exit $?
v2/setup.sh
The setup.sh script is the same as in #setup.sh v1 except it will be modified to upload the new scripts to the webdav. Below is the snippet that is added to the setup.sh for this iteration.
#
# Upload the scripts
#
cd $CTIER_ROOT/examples/scripted-appserver-war-deployment/scripts
ctl -z -p demo -m davutil -c put --\
-file `pwd`/v2 \
-url dav://examples/simpleTomcat/scripts/v2 || {
die "Failed uploading scripts to DAV"
}
You can see that the put command can copy a directory of files to the WebDAV.
Running
v2/setup.sh
Like in Iteration One, the setup.sh script is invoked directly.
-
cd $CTIER_ROOT/examples/scripted-appserver-war-deployment/scripts/v2
- Change directory to the "v2" subdirectory
-
sh ./setup.sh <node>
- Run the setup script specifying the node (You can specify a space separated list) you want to deploy Tomcat to. Example:
sh ./setup.sh centos2
- Run the setup script specifying the node (You can specify a space separated list) you want to deploy Tomcat to. Example:
v2/simpleTomcat.sh
Call the simpleTomcat.sh script via ctl-exec
Use ctl-exec to execute the simpleTomcat.sh script on each node that is tagged "simpleTomcat".
execute
ctl-exec -p demo -I tags=simpleTomcat -s simpleTomcat.sh
You should see output resembling the messages shown here:
output
Observations
The refactored implementation of the deployment process addressed several issues mentioned earlier. It is less monolithic, broken down into a two layers and leverages commands from coreutils.
The refactored design still suffers from one kind of inflexibility.
- All or nothing execution. You can't invoke individual steps in the deployment cycle.
Iteration Three
Let's refactor the implementation again to address the shortcomings mentioned in the observations in Iteration Two. The basic refactoring strategy will be to:
- Structure the code in the script to reflect the four main steps of the deployment cycle: stop, install, configure, start
- This will be done by using shell functions for each of the 4 steps
- Expose the individual steps as sub commands to the main driver script, "simpleTomcat.sh".
- Option parsing will be added that accept named subcommands
- Define some jobs in Jobcenter
- one job per function
The scripts
v3/simpleTomcat.sh
#!/bin/sh # File: simpleTomcat.sh PROG=`basename $0` USAGE=" Usage: $PROG -c command -c,-command Deploy command to run. Start|PackagesInstall|Configure|Stop|Deploy " die() { echo "ERROR: $@" ; exit 1 ; } [ -d "$CTL_BASE" ] || { die "ERROR: CTL_BASE not found" ; } SCRIPTS_URL=dav://examples/scripted-appserver-war-deployment TOMCAT_PKGBASE=apache-tomcat-5.5.28 SIMPLE_PKGBASE=simple INSTALL_DIR=$CTIER_ROOT/pkgs CATALINA_HOME=$INSTALL_DIR/$TOMCAT_PKGBASE # # Stop: # Stop() { listening=`ctl -z -p demo -m netutil -c listening -- -port 28080` ; [ "$listening" = "true" ] && { echo "Tomcat listening (port=28080). Running stop script ..." ; ctl -z -p demo -m shellutil -c exec --\ -executable /bin/sh -scripturl $SCRIPTS_URL/stop.sh \ -argline $CATALINA_HOME || die "Script failed: stop.sh" ; } return 0; } # # Packages-Install # PackagesInstall() { # Download the tomcat Zip ctl -z -p demo -m davutil -c copy --\ -src dav://pkgs/demo/zip/zips/$TOMCAT_PKGBASE.zip \ -dest $INSTALL_DIR/$TOMCAT_PKGBASE.zip || die "Download failed: $TOMCAT_PKGBASE.zip" ; [ -d $CATALINA_HOME ] && { rm -r $CATALINA_HOME || die "failed cleaning existing tomcat deployment" ; } cd $INSTALL_DIR ; unzip -q $INSTALL_DIR/$TOMCAT_PKGBASE.zip || die "Extract failed: $TOMCAT_PKGBASE.zip" ; # Download the simple.war ctl -z -p demo -m davutil -c copy --\ -src dav://pkgs/demo/war/wars/$SIMPLE_PKGBASE.war \ -dest $INSTALL_DIR/$SIMPLE_PKGBASE.war || die "Download failed: $SIMPLE_PKGBASE.war" ; # extract the WAR mkdir -p $CATALINA_HOME/webapps/$SIMPLE_PKGBASE || { die "failed making $SIMPLE_PKGBASE directory" ; } cd $CATALINA_HOME/webapps/$SIMPLE_PKGBASE ; unzip -q $INSTALL_DIR/$SIMPLE_PKGBASE.war || die "Extract failed: $SIMPLE_PKGBASE.war" ; } # # Configure # Configure() { ctl -z -p demo -m shellutil -c exec --\ -executable /bin/sh -scripturl $SCRIPTS_URL/configure.sh \ -argline $CATALINA_HOME || die "Script failed: configure.sh" ; } # # Start # Start() { ctl -z -p demo -m shellutil -c exec --\ -executable /bin/sh -scripturl $SCRIPTS_URL/start.sh \ -argline $CATALINA_HOME || die "Script failed: start.sh" ; # verify it is running. Wait up to 10s. Checks every 500ms listening=`ctl -z -p demo -m netutil -c listening -- -port 28080 -maxwait 10` ; [ "$listening" = "false" ] && die "Status failed: Tomcat not listening: (port=28080)" ; echo "Tomcat started. Visit: http://$(hostname):28080/$SIMPLE_PKGBASE" ; } # # Status # Status() { listening=`ctl -z -p demo -m netutil -c listening -- -port 28080` ; [ "$listening" = "true" ] && { echo "Tomcat listening: (port=28080)" } || { die "Tomcat not listening: (port=28080)" } } # # Deploy # Deploy() { Stop && PackagesInstall && Configure && Start; echo "Deployment completed."; } while [ "$#" -gt 0 ]; do OPT="$1" case "$OPT" in # options without arguments -h) echo "$USAGE" exit 0 ;; -c) COMMAND="$2" shift ;; # end of options, just arguments left *) echo "$USAGE" exit 2 ;; esac shift done case "$COMMAND" in Stop) Stop ;; Packages-Install) PackagesInstall ;; Configure) Configure ;; Start) Start ;; Status) Status ;; Deploy) Deploy ;; *) break; esac # end simpleTomcat.sh
Usage
The usage of the new simpleTomcat.sh is shown below:
Usage: simpleTomcat.sh -c sub-command -c,-command Command to run. Start|Packages-Install|Configure|Stop|Deploy
Because the simpleTomcat.sh now takes arguments, the ctl-exec command syntax changes slightly. For scripts that take arguments, you pass them after the "--" option:
ctl-exec -p demo -I tags=simpleTomcat -s simpleTomcat.sh -- [script-arguments]
For example, to run the "Status" sub-command in simpleTomcat.sh you add "-- -c Status":
ctl-exec -p demo -I tags=simpleTomcat -s simpleTomcat.sh -- -c Status
When ctl-exec executes simpleTomcat.sh on the local node, it will be invoked as if you typed:
simpleTomcat.sh -c Status
v3/setup.sh
The "setup.sh" script is the same as in Iteration Two. No changes were necessary.
Running
v3/setup.sh
Like in Iteration One and Two, the setup.sh script is invoked directly.
-
cd $CTIER_ROOT/examples/scripted-appserver-war-deployment/scripts/v3/
- Change directory to the "v3" subdirectory
-
sh ./setup.sh <node>
- Run the setup script specifying the node (You can specify a space separated list) you want to deploy Tomcat to. Example:
sh ./setup.sh centos2
- Run the setup script specifying the node (You can specify a space separated list) you want to deploy Tomcat to. Example:
v3/simpleTomcat.sh
Deploy the tomcat script via ctl-exec
Use ctl-exec to execute the "Deploy" sub-command from the simpleTomcat.sh script across all nodes tagged "simpleTomcat".
execute: Deploy
ctl-exec -p demo -I tags=simpleTomcat -s simpleTomcat.sh -- -c Deploy
output
number of nodes to dispatch to: 1, (threadcount=1) Connecting to centos2:22 done. Connecting to centos2:22 cmd : chmod +x /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh Connecting to centos2:22 cmd : /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh -c Deploy Deploy simple tomcat. Steps: stop, install, configure, start begin stop (1/4) ... Tomcat listening (port=28080). Running stop script ... Getting: http://strongbad:8080/jackrabbit/repository/workbench/examples/simpleTomcat/scripts/v3/stop.sh To: /tmp/exec1458106545160442288-stop.sh Copying 1 file to /tmp Tomcat stopped end stop (1/4) begin install (2/4) ... Getting: http://strongbad:8080/jackrabbit/repository/workbench/pkgs/demo/zip/zips/apache-tomcat-5.5.28.zip To: /home/alexh/ctier/pkgs/apache-tomcat-5.5.28.zip local file date : Sat Jun 20 15:08:03 PDT 2009 Not modified - so not downloaded Getting: http://strongbad:8080/jackrabbit/repository/workbench/pkgs/demo/war/wars/simple.war To: /home/alexh/ctier/pkgs/simple.war local file date : Sat Jun 20 15:08:04 PDT 2009 Not modified - so not downloaded end install (2/4) begin configure (3/4) ... Getting: http://strongbad:8080/jackrabbit/repository/workbench/examples/simpleTomcat/scripts/v3/configure.sh To: /tmp/exec4481840577151954212-configure.sh Copying 1 file to /tmp Chmod'ing files in /home/alexh/ctier/pkgs/apache-tomcat-5.5.28/bin/*.sh Customizing /home/alexh/ctier/pkgs/apache-tomcat-5.5.28/conf/server.xml ... /home/alexh/ctier/pkgs/apache-tomcat-5.5.28/conf/server.xml customized end configure (3/4) begin start (4/4)... Getting: http://strongbad:8080/jackrabbit/repository/workbench/examples/simpleTomcat/scripts/v3/start.sh To: /tmp/exec3072954032595161398-start.sh Copying 1 file to /tmp Invoking /home/alexh/ctier/pkgs/apache-tomcat-5.5.28/bin/startup.sh Tomcat started. Visit: http://centos2:28080/simple end start (4/4) Deployment completed. Visit: http://centos2:28080/simple
- Run the "Status" subcommand:
execute: Status
ctl-exec -p demo -I tags=simpleTomcat -s simpleTomcat.sh -- -c Status
output
number of nodes to dispatch to: 1, (threadcount=1) Connecting to centos2:22 done. Connecting to centos2:22 cmd : chmod +x /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh Connecting to centos2:22 cmd : /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh -c Status Tomcat listening: (port=28080)
- Run the "Status" subcommand
execute: Stop
ctl-exec -p demo -I tags=simpleTomcat -s simpleTomcat.sh -- -c Stop
output
number of nodes to dispatch to: 1, (threadcount=1) Connecting to centos2:22 done. Connecting to centos2:22 cmd : chmod +x /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh Connecting to centos2:22 cmd : /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh -c Stop Tomcat listening (port=28080). Running stop script ... Getting: http://strongbad:8080/jackrabbit/repository/workbench/examples/simpleTomcat/scripts/v3/stop.sh To: /tmp/exec2039195157561216008-stop.sh Copying 1 file to /tmp Tomcat stopped
- Run the "Start" subcommand
execute: Start
ctl-exec -p demo -I tags=simpleTomcat -s simpleTomcat.sh -- -c Start
output
number of nodes to dispatch to: 1, (threadcount=1) Connecting to centos2:22 done. Connecting to centos2:22 cmd : chmod +x /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh Connecting to centos2:22 cmd : /home/alexh/ctier/ctl/var/tmp/simpleTomcat.sh -c Start Getting: http://strongbad:8080/jackrabbit/repository/workbench/examples/simpleTomcat/scripts/v3/start.sh To: /tmp/exec1437938695406384114-start.sh Copying 1 file to /tmp Invoking /home/alexh/ctier/pkgs/apache-tomcat-5.5.28/bin/startup.sh Tomcat started. Visit: http://centos2:28080/simple
| ||||||||||||||




