Infrastructure & Automation

Using a long-lived compute resource as a custom resource in AWS CloudFormation

AWS CloudFormation enables you to create custom resources by using AWS Lambda functions to write custom provisioning logic in CloudFormation templates whenever you create, update, or delete stacks. This allows for the extension of CloudFormation in interesting ways. While Lambda is a powerful tool, some tasks, such as building/testing containers, require a longer-running compute environment with fewer restrictions.

It is possible to offload the custom resource request to another system or service other than Lambda. This is particularly useful for long-running tasks such as creating container images as part of a CloudFormation stack.

When building the AWX Quick Start, I needed a way to build the AWX containers as part of the AWS CloudFormation deployment. This blog post will outline the pattern I used to achieve that goal.

Building containers with AWS CodeBuild within AWS CloudFormation

By using AWS CodeBuild as a long-lived compute environment, you can leverage the flexibility of a containerized compute environment to build container images and import them into Amazon Elastic Container Registry (Amazon ECR) image repository.

While the concrete example here creates a Docker container image, the more general pattern for executing a CodeBuild job from a CloudFormation template may be useful for other long-lived compute tasks.

How this works

code build signaling to a w s cloud formation

Within the CloudFormation template, a custom Lambda-backed resource initiates a CodeBuild project. The CodeBuild resource includes code to signal back to CloudFormation the success or failure for the Lambda-backed custom resource, based off of the build succeeding or failing.
The signaling code, from CodeBuild back to CloudFormation, looks as follows:

- export UUID=1233244324 # A physical ID needed by CloudFormation
- |
    STATUS='SUCCESS'
    if [ $CODEBUILD_BUILD_SUCCEEDING -ne 1 ] # Test if the build is failing
    then
    STATUS='FAILED'
    fi
    cat <<EOF > /tmp/payload.json
    {
    "StackId": "$cfn_stack_id",
    "RequestId": "$cfn_request_id",
    "LogicalResourceId":"$cfn_logical_resource_id",
    "PhysicalResourceId": "$UUID",
    "Status": "$STATUS"
    }
    EOF
    curl -vv -i -X PUT -H 'Content-Type:' -d "@/tmp/payload.json" "$cfn_signal_url"

 

Try it out

I have created a small CloudFormation template to demonstrate this pattern and the code for it can be found in the GitHub repo.

The stack will do the following:

  • Create an Amazon ECR repository.
  • Run the CodeBuild project via the Lambda-backed custom resource. Then the CodeBuild project will do the following:
    • Create a Docker container image based on the Dockerfile.
    • Insert that container image into the Amazon ECR repository that the stack creates.

Launch the demo CloudFormation stack.

Note! During the Review step, under IAM Capabilities, be sure to tick the check box labeled I acknowledge that AWS CloudFormation might create IAM resources.

Once the CloudFormation stack is in CREATE_COMPLETE status, your new container image should be available in the Amazon ECR console.

Conclusion

When you require a longer-running compute environment, you can use CodeBuild to extend custom Lambda-backed resources for AWS CloudFormation.