Gracefully Deploying Plack Applications
I want to be able to deploy with impugnity or concern. This is a problem because of the following:
- Templates are tied to the application version.
- Server Restarts of backend code is required.
- Servers should not fail to respond. Either with new code, or with old code, a response is always required.
This shouldn’t be that hard, but yet it really is. We use Starman, which works really well at managing the application and as the backend web server (which then is load balanced and cached, depending upon instance.)
For the solution, leap ahead!
A brief explanation of infrastructure.
I usually use nginx in front of Starman-managed applications. If I need solid caching, I rely on Varnish. Varnish sits between nginx and the Plack/Starman application.
The backend application templates should always be cached in the application, so updating them on disk should not (in theory) update them in memory. The templates on disk in production should only change right before the application itself is restarted, storing them in memory works well.
nginx itself is hardly ever restarted, only for top level configuration changes.
The application should be able to be restarted all day long, for each change. I’m a fan of small, incremental updates rather than large planned releases. Continuous deployment as soon as QA signs off on the change.
Dealing with Starman.
Starman attempts to monitor for changes and then restart itself. So, you say, “Watch the lib dir, and restart on a change”. I don’t know why, but this has never worked well for me. I’ve tried to sort it out, but it just wasn’t working reliably.
The options here are basically to restart Starman forcibly, thus bringing the application down while it restarts, or give up on Starman managing restarts.
The Starman docs recommend looking at Server::Starter, which is a quick look as the documentation is lacking of any examples. I decided to really sit down and spend some time figuring this out. I want my guaranteed graceful restarts.
Server::Starter is very simple!
That was good once I finally realized just how little it actually does. It is a super-daemon, just handling whatever you tell it to. It doesn’t background itself, so I just needed something to do that for me.
Instead of trying to concoct an init script or something else more elaborate, I just focused on creating a shell script that would always do the right thing.
Do What I Mean
I obviously want my application running. Always. Never do I want it down, that means that each time I run this script, I want to make sure the application either gracefully restarts or starts the application. That’s pretty easy to define.
I did this by asking a simple question. Can the application gracefully restart? If that failed, then I do a hard start. If that fails, something is wrong and the deploy should be marked as a failure.
Fortunately, the start_server
command does well here. If the graceful restart fails, the exit code is properly set and I can then attempt a full start.
Backgrounding is annoying!
I first just ran this script and attempted to background start_server, which caused whiskey_disk to fail when I deployed. The application also failed to start. It worked just fine when I ran it from the terminal.
So, obviously I need something to manage my manager. I can easily rely on Debian tools to do this! Enter start-stop-daemon
, which handles starting start_server
and backgrounding it correctly.
No start-stop-daemon
? Show me a recipe for your platform and I’ll update this!
tl;dr, here’s a script:
Some important notes first:
- Update the environment variables at the top. Make sure to set
APP
andAPP_HOME
correctly. - Set the port. This, by default, only listens on
127.0.0.1
so it is private. You need nginx or something else in front. - If you are using Catalyst, set the
MYAPP_HOME
variables correctly. - For simplicity, I recommend using the
app.psgi
naming convention. - Use Perlbrew. There is no excuse not to. If you’re not and you ask anything, the first thing I will say is “Use perlbrew”.
- I use whiskey_disk for deployments. I just add this script as my
post_deploy_script
so it’s ran after each deployment.