How We Deploy Our Clojure Services
We’re using Clojure as a core development technology at my company. We have a basic web service implemented in Clojure running inside Jetty. I’m not using a full J2EE container, I just wanted a fairly simple strategy for running the Jetty based service. We’re already using Chef for provisioning our servers, for installing base dependencies, so that’s what I started with.
The main components are:
- chef recipe to install the foundation
- init script for controlling the service
- ruby based, capistrano-esque deployer.
- maven based project using the clojure-maven-plugin
The first part of our deployment is a Chef recipe to install our base components for the service. We set up an unprivileged user for the service to be run as, set up
/var/log directories to act as its install target and for its log files, set the correct permissions and install the init and start scripts for the service.
The init script is a standard LSB style init script that I copied from another daemon already in
/etc/init.d and then trimmed down and modified for our needs. The init script manages the running process via its pid (process id), which it expects to be stored in the file identified by
$PIDFILE. It uses
the-clj-service.sh to start the service – stopping the service is done by sending a signal to the PID of the JVM process. I’d like to introduce a more graceful shutdown procedure by asking our service to exit, this serves our needs for now.
The JVM isn’t really able to fork and background itself. The
start-stop-daemon utility can do this on behalf of the daemon, but according to its man page, that’s not recommended. This shell script represents that bridge between the init script controlling the daemon and the actual daemon, the JVM. It provides a simple runner that forks itself, writes out the child’s PID and then exits. This is one spot I’m not yet satisfied with: there is a hole here, where if the Jetty application fails to start or crashes, the init script will think it started when it in fact did not.
Finally we have our deployer, modeled after Ruby’s capistrano, for pushing the application up to the boxes where we’re going to run the service. I’ve left out the
ServiceUtils class because in our environment it contains some system configuration information, what you see below contains all the actual steps sans the configuration and credentials information for those hosts.
install_path matches the location you saw above in the init and service scripts.
The deployer builds the service using maven, using the assembly plugin to build a stand-alone Jar file that contains our application along with all of its dependencies. This produces a larger artifact than if it just contained our application code, but has some advantages: it freezes the dependencies into 1 archive; it is easily relocatable; we don’t have to perform a build or manage dependencies on the systems where we run the application.
As part of the deploy process we also create a few configuration files: a logging configuration, which for now we just copy the file appropriate for the environment from our project’s; a database configuration file and a service configuration file. The service properties file is built dynamically and is available via a special api call that allows us to monitor and validate that the version of the service that we think is installed is installed on a given server.
I’d love to hear how others are deploying their Clojure applications and about what we could improve. I’m happy with this as a starting point, but will definitely focus on improving it as we move forward.