AWS CloudFormation is the foundation of operational excellence on AWS. We code our infrastructure as JSON or YAML templates and test it as much as we need before deploying to production. We manage our infrastructure as code.
However, some new AWS resources may not be supported by AWS CloudFormation at the time they are launched. As of today, an example of this is the Elastic GPU resource. The solution is to define a custom CloudFormation resource and attach it to an AWS Lambda function which launches these resources. The Lambda function should also be in the same template. So let’s talk about how to do this in this blog post.
About AWS CloudFormation custom resources and AWS Lambda
While provisioning our resources using CloudFormation templates, we may need some custom logic. Also, we may need a resource type that is not supported by CloudFormation yet. This is why we need custom resources in our CloudFormation templates.
A custom resource is a resource which has Type
as AWS::CloudFormation::CustomResource
or Custom::<our-custom-resource-name>
such as Custom::MyEC2
. Another requirement is that it must have a ServiceToken
attribute that takes the ARN of an AWS Lambda function or Amazon SNS topic as value. Then, during the stack creation, deletion, and update, CloudFormation sends requests to this service. In our case, it will trigger our AWS Lambda function with some standard request inputs like RequestType
, ResponseURL
, and some custom inputs we may define, such as InstanceType
or KeyPair
.
When the Lambda function is triggered by CloudFormation, it performs the logic we coded as a regular Lambda function. However, before it finishes, it should send a response request to the ResponseUrl
provided by CloudFormation with some required attributes and custom data. We can use them later in our template.
So, this is how it works. Now, let’s continue with an example to launch an EC2 instance with an Elastic GPU.
Example: Provisioning an EC2 with an Elastic GPU
AWS Lambda function
First of all, we need to create an AWS Lambda function that will create our custom resource. CloudFormation provides an input structure to use in our function. As you see in the full code below, our function uses Boto3 to launch, get, and terminate the EC2 instances we create in this stack.
In this code, we use the value of the RequestType
to understand whether this function was triggered for a create
or delete
action. If CloudFormation triggered this function with the Create
value in the RequestType
, we simply create a new EC2 instance with an Elastic GPU. For the instance attributes, we use the ResourceProperties
dictionary that is passed to the Lambda function in the event
dictionary. Actually, we will define these values in our template. For now, you should know that CloudFormation sends our custom parameters in the ResourceProperties
.
The Lambda function sends success or failure responses to CloudFormation using StackId
, RequestId
, LogicalResourceId
values provided in the event dictionary. Besides, we use log_stream_name
attribute of the context
to construct PhysicalResourceId
. This is standard.
As you see, CloudFormation provides all of the required values to the AWS Lambda function in the event
dictionary. The only thing left to us is making an HTTP PUT request to the signed URL provided with either SUCCESS
or FAILED
status. Sending an empty status will also mean failure.
Another thing to note is the response_data
provided in the Data
attribute of the response sent by our AWS Lambda function. In this example, we do not need it at all. However, I added here to show that we can pass our custom key-value pairs in the response to reference them from another resource in our CloudFormation template later. For example, we could return an ‘InstanceId’ and then reference it in our template using the intrinsic GetAtt
function like below.
For the deployment, we need to package this function into a zip file and upload it to an S3 bucket. We will use this bucket in our CloudFormation template. Please let me remind you to install the requests
package into the same directory where your Lambda function app.py
resides. Then you should select and compress all files and folders into a zip package.
Important Note from 2020/08!
: I wrote this blog post before starting to use the Serverless Framework or AWS Serverless Application Model almost three years ago. AWS SAM was very new at that time, and AWS Lambda was around two years old. Now I recommend using AWS SAM to deploy your AWS Lambda functions.
The CloudFormation template
CloudFormation uses templates to provision our resources, and these templates can be in JSON or YAML format. I will not dive into the details of templates here. However, please check the AWS documentation, which I included in the References
section below, if you need more information.
Our template will provision these resources:
- A custom resource that triggers the Lambda function.
- The AWS Lambda function that I discussed above.
- An IAM role for our Lambda function with necessary CloudWatch and EC2 permissions.
You can provide LambdaS3Bucket
, LambdaS3Key
, ServerImageId
, KeyName
, ServerInstanceType
, GPUType
and ServerSecurityGroup
values in the Parameters
section of your template to make it reusable.
While creating an Elastic GPU using AWS CLI or Boto3 as in this post, AWS attaches the same security groups to the Elastic Network Interface (ENI) of the Elastic GPU and the EC2 instance. Hence you should add a rule to allow port 2007
to 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 define a VPC
parameter in the Parameters
section of the template and reference it in the resource.
Deployment result
We can check whether the Elastic GPU was attached successfully by establishing an RDP connection to our EC2 instance. The result should be similar to the below.
Conclusion
Infrastructure as code concept is one of the fundamentals of operational excellence on AWS. With custom resources and AWS Lambda, we can provision any AWS resource we like as long as AWS SDKs support it.
Thanks for reading!
Would you like to learn AWS CloudFormation?
If you would like to learn AWS CloudFormation to manage your infrastructure as code and automate the provisioning of your AWS resources, I would be happy to help you with my courses on Udemy. I divided the topics into two courses according to your knowledge level.
If you are a beginner to AWS CloudFormation, AWS CloudFormation Step by Step: Beginner to Intermediate will teach you its basics and most of the associate-level features. After finishing it, or if you know all those topics, you can enroll in my next-level CloudFormation course, AWS CloudFormation Step by Step: Intermediate to Advanced, which covers more advanced, professional-level features.
Besides, for all my available courses, please check out our Online Courses page. Hope to see you in them!