Wednesday, February 11, 2015

Random notes about AWS CodeDeploy

I've been playing around a bit with AWS CodeDeploy recently and found the documentation to be a bit lacking in places.  Here's what I've found so far that took a bit of digging to find, or that isn't what I expected the behavior to be.  It's only four things right now, but if I find more as I keep using it, I'll post them.

1. Scripts don't run during DownloadBundle, Install, or nonexistent steps, and no errors are thrown if you try.

The deployment lifecycle events are as follows:

-DownloadBundle
-BeforeInstall
-Install
-AfterInstall
-ApplicationStart
-ValidateService
-ApplicationStop

However, scripts that you list in your appspec.yml won't run during DownloadBundle (makes sense, as they aren't downloaded yet) or Install (which initially made no sense to me).  The Install step seems to just be copying over the files specified in the files block of the appspec file.  I imagine this is to avoid confusion about which happens first, the file copying or the script execution.  If you could run scripts in the same step as the file placement, you'd need some way to specify which came first, and if you let the user copy files, then run a script, then copy more files, specifying the exact order becomes prohibitively difficult.  I understand the need for this separate file-copy step, but the name is really misleading.  It should be something like CopyFiles instead of Install.

In addition, CodeDeploy won't complain at all if you have scripts specified in the DownloadBundle or Install blocks, it just won't ever do anything with them.  It won't give any indication that anything is wrong even if you add a block for a step that's completely made up, as long as everything is still valid YAML.  I really hope that this gets fixed.  I understand that the API call itself doesn't actually have the appspec file, as it's off in an archive somewhere, so they can't easily have the createDeployment call fail, but they could at least fail the deployment later or display a warning message in the console when you view the deployment.

(In fairness, this is included in the documentation (specifically, here), so the RTFM argument can always be made.  That said, I still wish they'd give some indication that you attempted something that won't work, as failing silently like this is a pretty bad user experience.)

2. The 'revision' argument structure for boto is poorly documented.

In boto, CodeDeploy deployments take in a "revision" argument that's just a dict.  It's never said specifically what the arguments are.  The names are the same as the ones contained in the API spec, but case matters, both for the keys ("revisionType" works but "RevisionType" fails) and the non-freeform values ("S3" is a valid revisionType, "s3" is not).  I've included an example S3 revision below.

(I imagine that for a GitHub revision it's the same, with the keys being the camelCased version of the argument name, but I haven't specifically tried.)

Here's an example of the proper format for an S3 revision:

    revision = {
        "revisionType": "S3",
        "s3Location": {
            "bucket": "MyBucket",
            "bundleType": "tar",
            "key": "PackagedStuff.tar"
        }
    }

I know it's traditional for Python and boto to use a much more free-form and less-documented data structure than type-safe languages like Java, but making the caller pass in a raw dict without clearly specifying how to set it up is a bit much.

3. DownloadBundle will fail due to any archive unpacking error.

The DownloadBundle step will fail if any of the archive doesn't unpack correctly.  This should probably be expected, but I've had archives where everything I cared about worked fine yet some random symlink was busted and caused the installation to fail.  If you normally unpack archives on the command line with verbose set to true, as I do, it can be easy to miss a couple files out of thousands failing, and so it's confusing when an archive that seemingly worked for you fails a deployment.  If you're test-unpacking a potential CodeDeploy archive with the utility "tar", make sure to call "echo $?" right afterwards to check the output code.

4. Install will fail if you try to copy a file over an existing file.

During the Install step, if there's already a file in a location where you're trying to put a file, it'll fail.  Even if they have the same contents, it'll fail.  I've gotten around this by copying the files to a staging directory in /tmp/, and then having an AfterInstall script that's basically:

#!/bin/bash
rsync /tmp/foo/bar/ /foo/bar/
rm -rf /tmp/foo/bar

There are probably countless ways to do this file swapover, depending on exactly your needs, but you'll need to pick and implement one as it isn't something that CodeDeploy handles nicely for you.  I can understand not wanting to pick how to handle this, as every possible choice they could make would break stuff for someone, but even still the current failure cases seem sub-optimal.

-----

Obligatory disclaimer: I'm currently a software engineer for Amazon Web Services; however, all of the random notes and views I post about AWS are 1. purely mine and not in any way a reflection of my employer's views and 2. based only on public information.  I don't work on CodeDeploy.

2 comments:

Charlie Gorichanaz said...

I find the failure on existing files issue to be quite maddening. I'd love to speed up deployments of a huge Git repo by only downloading and replacing changed files, but this is apparently not possible, and I must download the whole thing to a temporary directory as you did.

Steve said...

One possible solution might be to have a separate branch of your git repo that you use for the version(s) you're currently deploying, and then your CodeDeploy bundle doesn't contain the code but instead a script to update a repo that lives on each host. That way only the changes get downloaded. It breaks from the CodeDeploy model, for sure, but it'd work and get you at least some of what you're looking for.