Nav & Search

How to Setup a VPS to Run Grails on Jetty

Projects

To keep costs down Virtual Private Servers (VPSs) are a very good way to host web applications, they provide all the benefits of a dedicated server but are much cheaper to hire. The disadvantage most of them have is that the cheapest options often have limited amounts of memory (256-512MB), when running a PHP application this is often more than enough but for running Java and Grails applications (which run on a Java application servers) this provides a little challenge. It’s not unfeasible at all, it’s just that some extra attention has to be payed when building the application stack.

The most important step is to start as minimal as possible and only adding applications and starting processes that are necessary. One way is to use Tomcat as an application server and configuring it to be used without Apache Web Server. I prefer Jetty to Tomcat since I find it easier to set up and deploy the web applications and they use about the same amounts of memory. An added benefit of using Jetty is the ability to run it on port 80 without using iptables, ipchains or a similar mechanism which sometimes can be really tricky on a VPS since you can’t fiddle with the kernel.

Basic Setup

I’m starting with a clean Ubuntu 9.04 32-bit installation, 64-bit operating systems need more than 512MB ram and I have 256MB guaranteed with an extra 256MB dynamic on my host. Unless you’re logged in as root or as a user with root privileges all altering commands have to be prepended with “sudo”.

The first step is to add Multiverse to the sources.list so that we can install Java and other non-open source packages:

# nano /etc/apt/sources.list

Add multiverse to the end of the first line so that it reads something like

.../ubuntu restricted universe multiverse

Update 2011-08-10: However, since Ubuntu 10.04 Canonical have removed Java from the Multiverse repository, in 10.04 it’s found in deb http://archive.canonical.com/ lucid partner and in 10.10 it can be found in ppa:sun-java-community-team/sun-java6. Just add either of those lines at the end of the sources.list-file if you’re using one these versions of Ubuntu, unless you want to use Open JDK, openjdk-6-jdk.

Update the system and the distribution:

# apt-get update
# apt-get dist-upgrade

Add a new user:

adduser USERNAME

And install the Java 6 JDK:

# apt-get install sun-java6-jdk
# echo 'export JAVA_HOME=/usr/lib/jvm/java-6-sun/' >> /etc/profile

If you want to you can change to this user now and use the su command to get admin rights. Now, Install zip, gcc, Maven and MySQL and then create MySQL user and database. By removing the option to run an InnoDB database we save over 100MB memory:

# apt-get install zip unzip gcc build-essential maven2 mysql-server mysql-client
# mysql -u root -p
create user USER@localhost identified by 'PASS';
create database DBNAME;
grant all on DBNAME.* to USER@localhost;
quit;
# echo 'skip-innodb' >> /etc/mysql/my.cnf

Install the Groovy build you want, when I wrote this 1.6.3 was the latest stable and neither Groovy nor Grails were available in the repositories. groovy -v displays the Groovy-version and is simply a test to see that it works:

# cd /home/USERNAME/
# wget http://dist.groovy.codehaus.org/distributions/groovy-binary-1.6.3.zip
# unzip groovy-binary-1.6.3.zip
# echo 'GROOVY_HOME=/home/USERNAME/groovy-binary-1.6.3/' >> /etc/profile
# echo ‘PATH=$PATH:$GROOVY_HOME/bin’ >> /etc/profile
# groovy -v

Fetch and install Grails, I used Grails 1.1.1 which was the latest stable release:

# cd /home/USERNAME/
# wget http://dist.codehaus.org/grails/grails-bin-1.1.1.zip
# unzip grails-bin-1.1.1.zip
# echo 'GRAILS_HOME=/home/andzak/grails-bin-1.1.1/' >> /etc/profile
# echo ‘PATH=$PATH:$GROOVY_HOME/bin:$GRAILS_HOME/bin’ >> /etc/profile

Time to install the application server Jetty, I think it’s available in the repositories but I didn’t feel that I could control it with that version so I installed it the same way as Groovy and Grails:

#cd /home/USERNAME/
# wget http://dist.codehaus.org/jetty/jetty-6.1.18/jetty-6.1.18.zip
# unzip jetty-6.1.18.zip

A crucial step is to limit the amount of memory java applications can allocate since Jetty will basically take as much as it can in order to improve performance. Limiting the Heap-size in the /etc/profile-file to min 32MB and max 96MB seems to do the trick with a 256MB server. The last step is to activate the change in the file:

# echo 'JAVA_OPTS="-client -Xms32M -Xms96M $JAVA_OPTS"' >> /etc/profile
# . /etc/profile

That does it for the basic installation and configuration of the server!

Running Jetty on Port 80 through Setuid

All browsers assume that the web is served on Port 80 but by default Jetty is serving its content on Port 8080 since the low numbers are reserved for processes run by admin. We don’t want to run Jetty as admin due to security reasons and luckily there’s a workaround. Basically Jetty is started as admin on Port 80 but the process owner is changed as soon as it’s up and running.

The setuid process requires some custom libraries to be built (that’s why we installed gcc and Maven) and a little configuration but its really quite easy. It should be a file called $JETTY_HOME/extras/setuid/etc/jetty-setuid.xml, if it doesn’t exist create it and fill it with the following code (replacing UNMASK and USERID). If you don’t know what to put in UNMASK just type 2 there (removes writing abilites for users) and remember to put in the user id and not user name.

<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd">
<Configure id="Server">
<Set name="umask">UMASK</Set>
<Set name="uid">USERID</Set>
</Configure>

When I did this the first time I got the following error since I tried to put the username instead of id in the .xml-file:

2009-08-27 15:22:44.361::INFO: Logging to STDERR via org.mortbay.log.StdErrLog
2009-08-27 15:22:44.416::WARN: Config error at <Set name="uid">andzak</Set>
2009-08-27 15:22:44.416::WARN: EXCEPTION
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at org.mortbay.xml.XmlConfiguration.set(XmlConfiguration.java:405)
at org.mortbay.xml.XmlConfiguration.configure(XmlConfiguration.java:248)
at org.mortbay.xml.XmlConfiguration.configure(XmlConfiguration.java:214)
at org.mortbay.xml.XmlConfiguration.main(XmlConfiguration.java:974)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.mortbay.start.Main.invokeMain(Main.java:194)
at org.mortbay.start.Main.start(Main.java:523)
at org.mortbay.start.Main.main(Main.java:119)
Caused by: java.lang.NumberFormatException: For input string: "andzak"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
at java.lang.Integer.parseInt(Integer.java:447)
at java.lang.Integer.<init>(Integer.java:620)
... 15 more

To find the uid and gid I ran the following command and replaced the strings for uid and gid in my $JETTY_HOME/etc/jetty-setuid.xml which I believe is created from the one in $JETTY_HOME/extras/setuid/etc.

# su andzak
# id
(uid=1000(USERNAME) gid=1000(USERNAME) groups=1000(USERNAME)

With this set my $JETTY_HOME/etc/jetty-setuid.xml looks like this:

<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://$

<!-- =============================================================== -->
<!-- Configure the Jetty SetUIDServer -->
<!-- this configuration file should be used in combination with -->
<!-- other configuration files. e.g. -->
<!-- java -jar start.jar etc/jetty-setuid.xml etc/jetty.xml -->
<!-- =============================================================== -->
<Configure id="Server">
<Set name="startServerAsPrivileged">false</Set>
<Set name="umask">2</Set>
<Set name="uid">1000</Set>
<Set name="gid">1000</Set>
</Configure>

The final step is to actually change the port Jetty uses from 8080 to 80. This is done by editing the default port line in $JETTY_HOME/etc/jetty.xml:

<Set name="port"><SystemProperty name="jetty.port" default="80"/></Set>

Stacktrace Problem

Grails creates a stacktrace.log in the directory where the servlet container starts. This can be all kinds of places where there’s not enough permissions (/ for example). The error looks like this:

log4j:ERROR setFile(null,true) call failed.
java.io.FileNotFoundException: stacktrace.log (Permission denied)
at java.io.FileOutputStream.openAppend(Native Method)
at java.io.FileOutputStream.<init>(FileOutputStream.java:177)
at java.io.FileOutputStream.<init>(FileOutputStream.java:102)
at org.apache.log4j.FileAppender.setFile(FileAppender.java:290)
at org.apache.log4j.FileAppender.activateOptions(FileAppender.java:164)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)

The remedy is to alter the way Grails handles stacktraces by either setting where to write the log explicitly or by turning it off. To turn it off change the grails-app/conf/Config.groovy-file, since Grails 1.1 so that the appenders in log4j includes “‘null’ name:’stacktrace’”:

log4j = {
appenders {
'null' name:'stacktrace'
}
...
}

Virtual Hosts

Running multiple websites on the same host is really easy, add the following snippet just before the closing tag in $JETTY_HOME/etc/jetty.xml. Replace the WEBAPP-0.1.war with the .war-file containing your web application and the url.com and www.url.com with the host-names that are defined in your DNS-server (or plain ip-adresses). This makes the application in WEBAPP-0.1.war available at url.com and www.url.com:

<!-- Virtual Hosts -->
<New>
<Arg><Ref id="Contexts"/></Arg>
<Arg><SystemProperty name="jetty.home"/>/webapps/WEBAPP-0.1.war</Arg>
<Arg>/</Arg>
<Set name="defaultsDescriptor"><SystemProperty name="jetty.home" default="."/>/etc/webdefault.xml</Set>
<Set name="VirtualHosts">
<Array type="java.lang.String">
<Item>url.com</Item>
<Item>www.url.com</Item>
</Array>
</Set>
</New>

Start it Up!

To start Jetty with Setuid type:

# java -jar start.jar etc/jetty-setuid.xml etc/jetty.xml

However, if we start to Jetty server this way in the console over SSH it will shut down as soon as we close the session. In order to keep it running we must use the GNU Screen utility which creates virtual terminals which we can detach and reattach to. Starting Jetty in a Screen session will keep it running when we close our remote SSH console connection. It can also be used to start new screens and to switch between them, like a text-based Alt-Tab for Unix. To start Jetty in a screen session, do the following:

# screen
# java -jar start.jar etc/jetty-setuid.xml etc/jetty.xml

After doing this the SSH session can be closed and the Jetty server will keep running. When returning (to close the server for maintenance or something else) the SSH session can be reattached to the screen session using:

# screen -R

To create a new window in the screen use Ctrl-a c, to kill a window type Ctrl-a k and to switch between windows Ctrl-a n to go to the next.

When starting screen from a non-root account the following error message might appear:

no more PTYs
Sorry, could not find a PTY
[screen is terminating]

It usually means that the current user doesn’t have the right permissions for the PTY devices. This can be fixed by setting them to 666 with root privileges:

# chmod 666 /dev/ptmx

References

Running Jetty on Port 80
Virtual hosts in Jetty
Fixing the stacktrace.log
Using GNU Screen
MySQL Console Commands



5 thoguhts on “How to Setup a VPS to Run Grails on Jetty

Leave a comment

Your email will not be published.

*