Excelsior Logo Home
Buy   
Download   
Support   
 Forum   
Blog   

Cat in the Cloud: Apache Tomcat in Amazon EC2

Part III - Advanced Topics

Last update: 12-Mar-2012

By Dmitry LESKOV

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.

Better Init Script

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

Using a Pid File

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

Even More Sophisticated

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.

Assigning an Elastic IP Address

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:

  1. Select Elastic IPs in your AWS Management Console navigation bar and click the Allocate New Address button:

    image

    image

  2. A confirmation dialog will appear. Click Yes, Allocate:

    image

  3. Select the newly allocated Elastic IP address and click the Associate Address toolbar button:

    image

  4. Select your instance from the dropdown list and click Yes, Associate:

    image

  5. Go back to My Instances. You will notice that the instance's public DNS has changed to match the Elastic IP address:

    image

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

Running Tomcat on port 80

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" .../>

Other options

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:

  • Run Tomcat as root. This is not a good security practice.
  • 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.

  • Use authbind or privbind Problem is, they are not included in RHEL/CentOS and hence Amazon Linux, which is a derivative of RHEL.
  • Set the 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.

Further Reading

Jason Brittain explains why jsvc is evil and authbind is good in his blog post A Better Tomcat for Ubuntu and Debian.

Natively Compile Tomcat Web Apps

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.

Case Study: Colabrativ

Now if you want to see for yourself, it takes just a few easy steps:

  1. Download an evaluation copy of Excelsior JET for Linux and install it on a Linux box. (If you develop in the cloud, and do not have a Linux machine handy, you may need a larger instance with 2GB of memory or more.)
  2. 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.

  3. Package the resulting directory into a single archive:
    tar cvfz archive.tgz native-Tomcat-directory
  4. Copy the archive to your Amazon EC2 instance, for example using scp:

    scp -i key-pair-file archive.tgz ec2-user@instance-public-DNS:
  5. Login to your instance via SSH.
  6. Unpack the archive:
    tar xvfz archive.tgz
  7. If Tomcat is running, stop it.
  8. Start the natively compiled Tomcat:
    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.

  __|  __|_  )      |      _  _| __| _  _|   ___|   |       _  \  |  |  __|
  _|  (     /     _  _|      |   _|    |            |    | ( ) |  |  |  _| 
 ___|\___|___|     _|      __/  ___|  _|     ___|  ____|  \___/  \__/  ___|

Was the above article useful? If yes, we have more content for you!

Here are the links to Part I and Part II of the series, just in case.

Check out other articles written by Excelsior staff members:

Home | Company | Products | Services | Resources | Contact

Store | Downloads | Support | Forum | Blog | Sitemap

© 1999-2013 Excelsior LLC. All Rights Reserved.