As I considered the requirements of moving an existing Grails app from a Cloud-Foundry-based host to a VPS (virtual private service), there was one thought nagging in my mind. It wasn't choosing between the Oracle and OpenJRE, nor configuring PostgreSQL, nor minimizing downtime to be caused by the database restore and DNS updates, nor... It was deployment. That thing I'd have to do as part of developing and supporting the application. I had become accustomed to pushing the app and having it copy over to the host and start up all on its own.
1 2 3 4
Poof! Validation was one Chrome tab away. Then --most of the time-- I'd find my updates happily humming along. So I set about replicating this work-flow on a naked VPS.
After logging in to the Debian Linux VPS through SSH, I began to implement my infrastructure plan. Installing and configuring as if I had a gun to my head. There was a hard deadline and I wasn't about to miss it.
In the end, I got it done waaaay ahead of time. Hell, I didn't know that was going to happen!
I found the app a comfortable home in
/opt/myapp/ and while I was at it, dumped the boring ol' Tomcat and replaced her with a new exiting pal to play footsies with: Jetty. Actually, I replaced Tomcat with Jetty to simply the deployment process I was cooking up; sometimes I loose myself in hyperbole.
With everything in place, and my app running, the only thing missing was the deployment process. How to get new versions of the app copied over and have the app restart with the new code. With an idea in mind, I paid a visit to the mistress. I mean, the other mistress: Mercurial.
One Mercurial hook a day, keeps the project lead away
The idea was simple:
The developer (me) would check in the latest WAR file into a Mercurial repository. The repository would be a clone of a repo on the VPS. So a push would copy over the changesets and then run a Mercurial hook on the VPS. The hook would first stop the app, then check out the latest WAR file, and finally start the app.
Right away, my first question was: How do I go about stopping and starting the app? And just as quickly a dormant corner of my brain responded: By making the app an ordinary Operating System service. Duh!
I looked about at various ways to wrap my app in something that would make it function like an OS service, a daemon, but the solutions gave me indigestion. Besides, I preferred to avoid modifying the code to make this happen. With the app being executed by Jetty, a simple SIGTERM/SIGKILL was enough to shut it down. So I played with the idea of coding a wrapper which could be executed by the OS's init system. The wrapper would be able start/stop the app, and the Mercurial hook could then simply call the wrapper. Being a Gentoo Linux user, I'm familiar with OpenRC. Debian uses systemd, (which I quickly learned is quite nice), so off to creating a systemd service.
Here's a fun fact: Due to Gentoo Linux's rolling-release, my current Gentoo installation is the same one --though up-to-date-- installed about 14 years ago.
The systemd service for my app looks like this:
1 2 3 4 5 6 7 8 9 10 11
That was easy. There was no need to manage a PID file! Next, the configuration file for the service.
1 2 3 4
Then it was a matter of enabling the service to run on boot and... giving it a go.
1 2 3
It worked beautifully.
Now, I didn't want to log in as root when pushing to the repo, so I granted my developer account access to the necessary
systemctl commands via sudo. Then I was ready for the final piece. The Mercurial hook:
1 2 3 4 5 6 7
I configured the hook to run on
changegroup, which means after the entire push completes.
Happy as a clam
Having just confirmed the meaning of the expression happy as a clam, I can honestly say that's me. Deployment of my app is still a simple push away, and I had a good time set it all up.