Last update: 12-Mar-2012
This is the third part of a step-by-step guide for Java Web application developers wanting to get their feet wet in the Amazon cloud. Here are the links to Part I and Part II, just in case.
If you have installed the "official" Tomcat from the Amazon Linux repo, you are already covered - skip this section.
The System V init scripts should follow the guidelines
described in /usr/share/doc/initscripts-version/sysvinitfiles.
At a minimum, the "bare bones" script presented in
Part II
should be enhanced to use a lock file, so as to facilitate graceful shutdowns
and correct transition between runlevels.
There are also some helper functions
in /etc/init.d/functions that the script could have utilized.
Here is an enhanced version of the script:
#!/bin/sh
# A slightly better Apache Tomcat start/stop script
#
# chkconfig: 345 80 20
# description: Apache Tomcat
# Source the init.d function library
. /etc/init.d/functions
RETVAL="0"
case $1 in
start)
echo "Starting Tomcat:"
/bin/su -s /bin/sh - tomcat -c /opt/tomcat/bin/startup.sh
RETVAL=$?
[ $RETVAL = 0 ] && touch /var/lock/subsys/tomcat
;;
stop)
echo "Stopping Tomcat:"
/bin/su -s /bin/sh - tomcat -c /opt/tomcat/bin/shutdown.sh
RETVAL=$?
[ $RETVAL = 0 ] && rm -f /var/lock/subsys/tomcat
;;
restart)
$0 stop
$0 start
;;
condrestart)
[ -e /var/lock/subsys/tomcat ] && $0 restart
;;
status)
status tomcat
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
RETVAL="1"
esac
exit $RETVAL
Some features of the Tomcat start/stop script, catalina.sh,
such as waiting for the process to end, rely on the use of a PID file.
In an instance that you will be running several instances of Tomcat on
a single EC2 instance :), PID files would enable you to quickly
map processes to (Tomcat) instances.
PID files normally reside in /var/run,
which is only writable by the superuser.
But you are running Tomcat as an unprivileged user, aren't you?
Therefore you need the init script to create the PID file and give
ownership to that unprivileged user before starting Tomcat…
start)
echo "Starting Tomcat:"
# Fix pid file permissions
touch /var/run/tomcat.pid 2>&1 || RETVAL="4"
if [ "$RETVAL" -eq "0" -a "$?" -eq "0" ]; then
chown tomcat:tomcat /var/run/tomcat.pid
fi
/bin/su -s /bin/sh - tomcat -c /opt/tomcat/bin/startup.sh
…and remove the PID file upon stopping Tomcat:
stop)
echo "Stopping Tomcat:"
/bin/su -s /bin/sh - tomcat -c /opt/tomcat/bin/shutdown.sh
RETVAL=$?
[ $RETVAL = 0 ] && rm -f /var/lock/subsys/tomcat /var/run/tomcat.pid
Now stop Tomcat and add the following line to /opt/tomcat/bin/setenv.sh:
CATALINA_PID=/var/run/tomcat.pid
The init script included in the official Amazon Linux Tomcat package is over 300 lines long. It is way more configurable than my script above. In particular, the “official” script makes running multiple instances of Tomcat easier.
If you want to peek into that script without installing the package, download and extract it manually:
yumdownloader tomcat6
rpm2cpio tomcat6-version.amzn1.noarch.rpm | cpio -idmv "./etc/init.d/*"
The script is in the file ./etc/init.d/tomcat6.
Your instance is up and running, but if you stop and start it again, you'll notice that its Public DNS changes. This happens because an instance's public IP address is allocated dynamically from a pool during launch and returned to the pool when you stop or terminate the instance. Should your instance or Availability Zone fail, dynamic DNS changes will take hours to propagate. Enter another elastic entity - Elastic IP address.
In Amazon EC2, an Elastic IP address is a static IP address associated with your account, rather than with a particular instance. To designate a particular instance to serve requests coming from the Internet, you map one of your Elastic IP addresses to that instance.
An Elastic IP address does not cost you extra money when it is mapped to an instance. Otherwise it incurs a small hourly fee, so do not leave your Elastic IPs unmapped!
To allocate an Elastic IP address and map it to your freshly created instance:
Select Elastic IPs in your AWS Management Console navigation bar and click the Allocate New Address button:
A confirmation dialog will appear. Click Yes, Allocate:
Select the newly allocated Elastic IP address and click the Associate Address toolbar button:
Go back to My Instances. You will notice that the instance's public DNS has changed to match the Elastic IP address:
From now on, use the Elastic IP address or the updated public DNS to access your instance. They will survive instance stops and failures.
If you prefer to use a symbolic hostname, add an entry to your
local hosts file or create DNS records as necessary.
Speaking about DNS, if your Web application will be sending email messages to third-parties, you will need to inform Amazon about that and request them to create reverse DNS records for your Elastic IP address so as to reduce the chance of those messages being flagged as spam. Here is the link to Request to Remove Email Sending Limitations
Tomcat often sits behind an HTTP server such as Apache, which serves static content and proxies requests for dynamic content to Tomcat. Another popular option is to use squid as a reverse proxy. If for any reason you want Tomcat to serve all HTTP requests, you need it to listen on port 80 (and possibly 443). However, only the superuser can bind to TCP ports below 1024 on Linux, so making Tomcat listen on port 80 requires some extra work.
The easiest-to-implement solution is to simply forward incoming port 80 requests
to port 8080, or whatever non-privileged port you are running Tomcat on.
The Amazon Linux AMI has iptables enabled by default,
but does not have any packet filtering rules defined as it apparently relies
on the surrounding AWS infrastructure for security.
That said, you can go ahead and add a rule:
sudo /sbin/iptables -t nat -I PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 8080
Check that Tomcat is running and have your browser connect to your instance
without specifying a port. If you succeed, save iptables rules:
sudo /sbin/service iptables save
The rules are stored in /etc/sysconfig/iptables and applied
upon iptables (re)start, e.g. if you reboot the instance.
Note: The above rule applies to all packets arriving from outside to any network interface. If you are running any applications on the same instance that need to talk to Tomcat on the HTTP port, you need to add another rule.
Finally, you need the servlets inside your Web application
to act as if the incoming requests were directed to port 80.
This will prevent the appearance of the non-privileged port
in any URLs sent back to the client.
Include the proxyPort attribute in your HTTP
connector config in server.xml:
<Connector port="8080" proxyPort="80" .../>
If you do not want to touch iptables, or if your boss is under a false
perception that it adversely affects performance, your other options are:
Use jsvc. Good news: you don't have to build it from source, as Amazon Linux already includes the binary:
sudo yum install jakarta-commons-daemon-jsvc
Bad news: installation of that package won't update the Tomcat startup script
/etc/init.d/tomcat6, so you'll have to figure it out yourself.
jsvc is also known to have issues with shutdowns and restarts.
authbind or
privbind
Problem is, they are not included in RHEL/CentOS and hence Amazon Linux, which
is a derivative of RHEL.
CAP_NET_BIND_SERVICE capability on the java launcher.
sudo setcap cap_net_bind_service=+ep path-to-java-launcher
Amazon Linux provides Linux Capabilities, but there is
evidence
that Sun/Oracle Java SE versions prior to 7 do not play well with them.
Elaborating on these options is well beyond the scope of this article.
Jason Brittain explains why jsvc is evil and authbind
is good in his blog post
A Better Tomcat for Ubuntu and Debian.
I hope you have gathered some useful information from this series. Now time has come for a vendor plug. I'll begin with an imaginary dialog with a prospect:
Q: Why should I bother with Java-to-native compilation? The performance of my app on the conventional JRE is good enough!
A: It significantly increases the resistance of your application code to reverse engineering and tampering.
Q: But why would anyone want to protect the code of a Web application, which does not run on end users' computers?
A: First, if you think that people who do not completely trust their Web hosting or cloud provider's perimeter security are overly paranoid, read this recent article by German resarchers who've found security holes in Amazon control interfaces. And then think again.
"A successful attack on a Cloud control interface grants the attacker a complete power over the victim's account, with all the stored data included."— All Your Clouds are Belong to Us
Then there is a particular use case: public AMIs. If you have created a Web app that your customers run in their own Amazon clouds, it is natural to deliver it as a custom AMI. Now, if you want to let your prospects "see for themselves", i.e. try your Web app against their data before making a purchase decision, you better make sure they cannot easily tamper with it, e.g. to remove trial restrictions.
Now if you want to see for yourself, it takes just a few easy steps:
Compile your Tomcat Web applications to native code and export them as a self-contained directory.
You may follow the video tutorial, just make sure to select Self-contained directory as a backend.
tar cvfz archive.tgz native-Tomcat-directory
Copy the archive to your Amazon EC2 instance, for example using scp:
scp -i key-pair-file archive.tgz ec2-user@instance-public-DNS:
tar xvfz archive.tgz
native-Tomcat-directory/bin/startup.sh
Now you can access your natively compiled Web applications at your EC2 instance's public DNS. Note that the JRE installed on your instance is not used, because the package includes the Excelsior JET Runtime.
__| __|_ ) | _ _| __| _ _| ___| | _ \ | | __|
_| ( / _ _| | _| | | | ( ) | | | _|
___|\___|___| _| __/ ___| _| ___| ____| \___/ \__/ ___|