AWS CloudFormation is the foundation of operational excellency on AWS. We code our infrastructure in JSON of YAML templates and test it as much as we need before deploying on production. It is simply infrastructure as code concept.
However, some new resources may not be supported by AWS at the same time they are launched. As of today, an example to these is Elastic GPU resource. The solution is to define a custom CloudFormation resource and attach this resource to a Lambda function which launches these resources. The Lambda function should also be in the same template and I will describe the process in this blog post.
CloudFormation Custom Resources and Lambda
While provisioning our resources using CloudFormation templates we may need some custom logic. Or we may need a resource type which is not supported by CloudFormation yet. This is where we use custom resources in our CloudFormation templates.
A custom resource is a resource which has
Custom::<our-custom-resource-name> such as
Custom::MyEC2. Another requirement is that it must have a
ServiceToken attribute which can have value of arn of a Lambda function or SNS topic. During stack creation, deletion and update, CloudFormation sends requests to this service. In our case, it will trigger our Lambda function with some standard request inputs like
ResponseURL and some custom inputs we may define such as
When the Lambda function runs, it makes what it has to do according to the logic we coded, then it should send a response request to
ResponseUrl with some required attributes and custom data that we can use later in our template.
This is how it works, let’s continue with an example to launch an EC2 instance which has Elastic GPU attached.
Provisioning EC2 Example
First of all, we need to create a Lambda function. CloudFormation provides an input structure that we can use in our function. As you can see blow, our function uses Boto3 to launch, get and terminate instances we create in this stack.
We use “RequestType” key value to understand whether this function was called for create or delete action. If CloudFormation triggered this function with
Create value in
RequestType, we simply create a new Ec2 instance with an Elastic GPU attachment. We use
ResourceProperties dictionary values that CloudFormation passed to Lambda function in event. Actually, we will define these values in our template. For now, the important thing is that CloudFormation sends our custom parameters in
We also created a function to send successful or failure responses to CloudFormation using
LogicalResourceId key values supplied by CloudFormation in Lambda event dictionary. Besides, we use
log_stream_name attribute of
context to construct
PhysicalResourceId. This is standard. As you can see, CloudFormation supplies all of the required values to Lambda function. The only thing is to make an HTTP PUT request to the signed url provided with either
FAILURE status. An empty status also means failure.
One thing to note is
response_data which is provided in
Data attribute of the response our Lambda function returns to CloudFormation. In this example, we do not use at all. However, I added here to show that we can pass our custom response key-value pairs to use in CloudFormation in another resource. For example, we could return an “InstanceId” and then reference in our template like below.
Anyway, we do not need this here and let’s see our final Lambda function:
After development, we need to package this function into a zip file and upload to an S3 bucket. We will use this bucket in our CloudFormation template. Please let me remind you that you should install
requests package into the same directory where your Lambda function
app.py resides,then select all files and folder and compress into a zip package.
CloudFormation uses templates to provision our resources. These templates can be JSON or YAML and should have some required attributes like
Resources. I will not dive into the details, please check AWS documentation which I included in
References section below.
Our template will provision these resources:
- A custom resource that triggers the Lambda function
- An AWS Lambda function that our custom will trigger
- An IAM role for our Lambda function with necessary CloudWatch, EC2 permisions
You can supply
ServerSecurityGroup values in
Parameters section of your template.
While creating an Elastic GPU using aws-cli or Boto3 as in this post, AWS attaches the same security group ids to the Elastic Network Interface (eni) of the Elastic GPU and the EC2 instance. Hence you should include a rule to allow port
2007 access to other members of the same security group.
You can also create a
ServerSecurityGroup resource to grant RDP (port 3389) access and then a circular ingress rule as below. Again, you can get
VPC value in
Parameters section of the template during stack creation.
When we make an RDP connection to our instance, we can see that Elastic GPU was attached successfully to our Windows EC2 Instance.
Infrastructure as code concept is one of the most important fundamentals of operational excellency on AWS. With custom resources and AWS Lambda, we have the ability to provision any resource we like as long as AWS SDKs support.
Thanks for reading!