The more I use the Play Web Framework, the more I like it. The “dev mode” is great, compiling everything that’s changed on the fly, including the statically checked routes and templates. But sooner or later we want to deploy our app for reals.
dist target will package up your app into a zip file with a simple shell script to launch it. That’s fine as far as it goes, but of course I want to run it as a proper service that starts automatically when the machine boots and can be easily shutdown and restarted. My production OS of choice is Ubuntu. Let’s do this.
I only learned about Upstart recently while researching this problem. It’s essentially an improved version of the traditional System V init system. This discovery made me very glad I chose Ubuntu, as Upstart is both powerful and remarkably simple to use once you know the basics. The Upstart “cookbook” might look a bit intimidating at first, but after a bit of playing around I found it was quite simple to do what I wanted.
By the way, Ubuntu still has the traditional System V init thing as well. I was happily using that for ages without any idea that it had a whole other more advanced init system controlling it. This is because Debian still uses the System V init by default, so all Debian packages use System V, and Ubuntu doesn’t want to re-do all that work.
This is how I got my Play server running with Upstart. This was all done on Ubuntu 12.10 and seems to be working well so far. Obviously, there are a number of choices I’ve made that could be made differently, so this is not the only way to do it.
1. System user for Play
I wanted a user dedicated to running Play servers. Easy:
$ sudo adduser --system play
2. Unzip your Play app
Just unzip your Play app somewhere. For this explanation, I’m going to assume it’s unzipped to
play user will need to be able to write to the app’s directories:
$ sudo chown -R play:adm /play/myapp-1.0
3. Create an Upstart configuration file
All Upstart “jobs” are defined by a file in
jobname.conf. The “jobname” bit is what you call the job when starting it or checking its status or whatever. So let’s create a file
/etc/init/myapp.conf as follows:
# Upstart configuration for myapp Play application description "Magellan Config Server" start on runlevel  stop on runlevel [!2345] setuid play console log env PLAY_DIR=/play/myapp-1.0 env JAVA_HOME=/usr/lib/jvm/jre1.7.0_17 exec $JAVA_HOME/bin/java -Dhttp.port=9001 -classpath $PLAY_DIR/lib/\* play.core.server.NettyServer $PLAY_DIR
All this stuff (known as stanzas) is described in the above mentioned Upstart cookbook, but I’ll go through it briefly.
stop on lines indicate that these actions are triggered by changes in the system’s runlevel. Upstart uses runlevels just like System V, and on Ubuntu levels 2 through 5 are all the same. There are many events that can be used to trigger start and stop, but for a standalone daemon process, these do the job.
setuid stanza means the job will be executed as the specified user instead of root. This seems like a simple thing, but you’d be amazed how fiddly it can be to do this via traditional means if you need to pass environment variables through.
console log stanza means that stdout and stderr are redirected to
/var/log/upstart/myapp.log. This was useful when debugging why my job wasn’t starting. I’m going to change it to
console none on my server as I no longer need this log.
I’m just using the
env stanzas to break out the path settings. There’s seems to be a whole lot more they can be used for in Upstart, but I haven’t needed to worry about that yet.
exec stanza specifies the command to run to run the job. This is basically the command from the
start script that Play provides for us. I’ve just replaced the explicit listing of all the jars with the newer classpath wildcard feature, and specified the port for Play to use.
4. Start it
Upstart jobs are controlled by the
initctl command. But there are various alias commands to save some typing. For example,
sudo start foo is the same as
sudo initctl start foo. So, let’s start our Play app:
$ sudo start myapp
Hopefully, you’ll be rewarded with:
myapp start/running, process 1234
You can confirm with:
$ ps -p 1234
If something went wrong, check
/var/log/upstart/myapp.log for clues.
Some other commands:
$ sudo status myapp $ sudo stop myapp $ sudo restart myapp $ sudo initctl list # list all Upstart jobs
Of course, we like our HTTP servers to use port 80 (and or 443). There are a few ways this can be arranged, here’s the ones I know of:
- Run as root (don’t do this)
- Redirect port 80 using the firewall
- Use authbind(1)
- Use a HTTP reverse proxy. I’m experimenting with nginx
I’ve used authbind, it’s pretty straight-forward and works with the JVM. It’s good when you just have a single server that you want on port 80/443.
As Play apps run standalone with their own embedded Netty web server, an HTTP reverse proxy seems like it will prove quite useful. If you have a bunch of Play apps using different ports, you can use the proxy to make them all appear as part of a single “server” on port 80 (distinguished by something like a path in the URL or by the HTTP “host” header). Nginx (pronounced “Engine-X”) can also do simple load balancing, which should allow us to leverage Play’s stateless style. I’m still experimenting with these features at this stage.
I was pleasantly surprised at how easy it was to get my Play app up and running as an Ubuntu service. I had to spend some time learning Upstart, but it was well worth it. Upstart takes care housekeeping such as tracking the service’s PID for you. Compare the short configuration file above to the System V script included with Ubuntu’s Tomcat 7 package, which weighs in at 296 lines!