Use AWS Lambda functions in a CFT
Problem statement:
You have an existing VPC and you want to deploy Big-IP, and you want to add a default route to steer traffic to the ENI of the Big-IP after it's created.
We can easily use F5’s supported templates to deploy Big-IP in AWS with a CloudFormation Template (CFT) into this VPC, but what if you want to add some automation after the Big-IP is deployed? For example, what if you wanted to add a default route to steer traffic to the ENI of the Big-IP that was just created? You might think of these options:
- You could create the RouteTable(s) as part of your CFT and associate it with the subnet(s) you deploy your Big-IP into. But what if the route already exists in your VPC? You can’t edit a route via a CFT - only create one.
- You could use a tool to automate your deployment, like Ansible or Jenkins. This is a fantastic demonstration of these tools. You could deploy Big-IP, then use a CFT output to get the ENI id of the Big-IP interface, get the route table id, and make your update via the replace-route API call . But, you might be required to make your entire deployment in 1 CFT, or maybe you’re looking to ensure your CFT doesn’t rely on other tasks after deployment? So, what if you cannot use a 3rd party tool?
- You could write your automation into a Lambda function, then create and execute that Lambda function as part of your CFT. This gives you the ability to run tasks in AWS as part of your CFT deployment.
This article explains option #3. Modify the automation for your other use cases.
Solution:
The steps to follow when you need Lambda functionality as part of your CFT are:
- Create an IAM role in your CFT for your Lambda function to execute.
- Create a Lambda function
- Create a Custom Resource that is backed by the Lambda function. This will execute your Lambda function with parameters you can specify, and report back a Success or Failure to CloudFormation.
I’ll explain how to do each of these and provide an example CFT at the end.
Create an IAM role
Include code such as below to create an IAM role that is appropriate for your Lambda function. Do not provide more permission than required to this role. Very public breaches have occurred as a result of mis-use of IAM roles, so by keeping your IAM roles limited in scope, you decrease your risk if it is mis-used. (In fact, I wrote some best practices to decrease this risk.) In our case, we’re allowing my Lambda function permissions over EC2 service.
Create a Lambda function
Here’s where you’ll need to define the data you’ll pass to your Lambda function (if any), what you want Lambda to do, and what it should return (if anything). Then write your function in a supported language. I’ve chosen Python.
In this case, we’re updating a route in AWS, so we’re passing 3 things to the function: a CIDR range, an ENI id, and a RouteTable id. Upon creation, this Lambda function will update the specified route (CIDR) in the specified RouteTable, to point at the specified ENI. The Lambda function will also reference the IAM role we are creating.
Create a Custom Resource.
Custom Resources can be used to represent things we cannot define with AWS CFT types normally, like EC2 instances and VPC’s. A Lambda-backed Custom Resource is just one example of this. Our Custom Resource will pass an Event Type to the Lambda function (either Create, Update, or Delete), and then we’ll pass Resource Properties that will provide the function with the CIDR, ENI id, and RouteTable id required. In our case, the Lambda function returns the output of the API call upon Create, but returns nothing upon Delete.
Still with me? String these together, and you have a Lambda function that is executed as part of your CFT. CloudFormation is smart enough to know that these depend upon values from other resources in the template, so they will be created after the EC2 instance is created. You could use a DependsOn statement to ensure this behavior, if you had other reasons to wait for something before Lambda execution.
Demo
- Attached to this article is a ZIP file containing 2 CFT templates.
- First, deploy CFT #1, which is a file called "vpc.json".
- This will create a VPC that pre-exists your BigIP deployment. It is extremely simple: a single VPC, with a single subnet, which has an associated Route Table, with a default route of 0.0.0.0/0 pointing to an Internet Gateway.
- Take a note of the Outputs. There will be a RouteTableId and a SubnetId. You will need these Id's on the next step.
- Notice: the Route Table you created has a default route pointing toward an Internet Gateway.
- Then, deploy CFT #2, which is a file called "route-update.json".
- You will need to enter 2 parameters: the RouteTableId and SubnetId that were outputs from step #1.
- It will deploy an ENI (network interface) into the subnet, and then update the RouteTable that you have specified in your input parameters to point 0.0.0.0/0 at this ENI.
- Notice again: the Route Table's default route has been updated to point at the ENI, not the Internet Gateway.
Obviously you'll want to modify these to point at the ENI of a Big-IP device, but I've kept these templates very simple to show you just the updating of a route via Lambda. You may even want to modify the code of the Lambda function to do other things - go for it!
Enjoy. Use this article as a starting point, then think of cool use cases for Lambda functions. Prove them out and leave a comment to share your experience. And don’t forget -
- Your own role within the AWS account matters. You cannot create an IAM role that has permissions greater than you have yourself. (And if you’re deploying this with a service account using another IAM role, consider that)
Credit goes to this article, which I used to learn about Lambda-backed Custom Resources and then modified for this use case.
Demo Cloud Formation Template Files