AWS Compute Blog
Using API Gateway mapping templates to handle changes in your back-end APIs
Maitreya Ranganath, AWS Solutions Architect
Changes to APIs are always risky, especially if changes are made in ways that are not backward compatible. In this blog post, we show you how to use Amazon API Gateway mapping templates to isolate your API consumers from API changes. This enables your API consumers to migrate to new API versions on their own schedule.
For an example scenario, we start with a very simple Store Front API with one resource for orders and one GET method. For this example, the API target is implemented in AWS Lambda to keep things simple – but you can of course imagine the back end being your own endpoint.
The structure of the API V1 is:
Method: GET
Path: /orders
Query Parameters:
start = timestamp
end = timestamp
Response:
[
{
“orderId” : string,
“orderTs” : string,
“orderAmount” : number
}
]
The initial version (V1) of the API was implemented when there were few orders per day. The API was not paginated; if the number of orders that match the query is larger than 5, an error returns. The API consumer must then submit a request with a smaller time range.
The API V1 is exposed through API Gateway and you have several consumers of this API in Production.
After you upgrade the back end, the API developers make a change to support pagination. This makes the API more scalable and allows the API consumers to handle large lists of orders by paging through them with a token. This is a good design change but it breaks backward compatibility. It introduces a challenge because you have a large base of API consumers using V1 and their code can’t handle the changed nesting structure of this response.
The structure of API V2 is:
Method: GET
Path: /orders
Query Parameters:
start = timestamp
end = timestamp
token = string (optional)
Response:
{
“nextToken” : string,
“orders” : [
{
“orderId” : string,
“orderTs” : string
“orderAmount” : number
}
]
}
Using mapping templates, you can isolate your API consumers from this change: your existing V1 API consumers will not be impacted when you publish V2 of the API in parallel. You want to let your consumers migrate to V2 on their own schedule.
We’ll show you how to do that in this blog post. Let’s get started.
Deploying V1 of the API
To deploy V1 of the API, create a simple Lambda function and expose that through API Gateway:
- Sign in to the AWS Lambda console.
- Choose Create a Lambda function.
- In Step 1: Select blueprint, choose Skip; you’ll enter the details for the Lambda function manually.
- In Step 2: Configure function, use the following values:
- In Name, type getOrders.
- In Description, type Returns orders for a time-range.
- In Runtime, choose Node.js.
- For Code entry type, choose Edit code inline. Copy and paste the code snippet below into the code input box.
MILISECONDS_DAY = 3600*1000*24;
exports.handler = function(event, context) {
console.log('start =', event.start);
console.log('end =', event.end);
start = Date.parse(decodeURIComponent(event.start));
end = Date.parse(decodeURIComponent(event.end));
if(isNaN(start)) {
context.fail("Invalid parameter 'start'");
}
if(isNaN(end)) {
context.fail("Invalid parameter 'end'");
}
duration = end - start;
if(duration 5 * MILISECONDS_DAY) {
context.fail("Too many results, try your request with a shorter duration");
}
orderList = [];
count = 0;
for(d = start; d < end; d += MILISECONDS_DAY) {
order = {
"orderId" : "order-" + count,
"orderTs" : (new Date(d).toISOString()),
"orderAmount" : Math.round(Math.random()*100.0)
};
count += 1;
orderList.push(order);
}
console.log('Generated', count, 'orders');
context.succeed(orderList);
};
-
- In Handler, leave the default value of index.handler.
- In Role, choose Basic execution role or choose an existing role if you’ve created one for Lambda before.
- In Advanced settings, leave the default values and choose Next.

Finally, review the settings in the next page and choose Create function.
Your Lambda function is now created. You can test it by sending a test event. Enter the following for your test event:
{
"start": "2015-10-01T00:00:00Z",
"end": "2015-10-04T00:00:00Z"
}
Check the execution result and log output to see the results of your test.

Next, choose the API endpoints tab and then choose Add API endpoint. In Add API endpoint, use the following values:
- In API endpoint type, choose API Gateway
- In API name, type StoreFront
- In Resource name, type /orders
- In Method, choose GET
- In Deployment stage, use the default value of prod
- In Security, choose Open to allow the API to be publicly accessed
- Choose Submit to create the API

The API is created and the API endpoint URL is displayed for the Lambda function.
Next, switch to the API Gateway console and verify that the new API appears on the list of APIs. Choose StoreFront to view its details.
To view the method execution details, in the Resources pane, choose GET. Choose Integration Request to edit the method properties.

On the Integration Request details page, expand the Mapping Templates section and choose Add mapping template. In Content-Type, type application/json and choose the check mark to accept.

Choose the edit icon to the right of Input passthrough. From the drop down, choose Mapping template and copy and paste the mapping template text below into the Template input box. Choose the check mark to create the template.
{
#set($queryMap = $input.params().querystring)
#foreach( $key in $queryMap.keySet())
"$key" : "$queryMap.get($key)"
#if($foreach.hasNext),#end
#end
}
This step is needed because the Lambda function requires its input as a JSON document. The mapping template takes query string parameters from the GET request and creates a JSON input document. Mapping templates use Apache Velocity, expose a number of utility functions, and give you access to all of the incoming requests data and context parameters. You can learn more from the mapping template reference page.
Back to the GET method configuration page, in the left pane, choose the GET method and then open the Method Request settings. Expand the URL Query String Parameters section and choose Add query string. In Name, type start and choose the check mark to accept. Repeat the process to create a second parameter named end.

From the GET method configuration page, in the top left, choose Test to test your API. Type the following values for the query string parameters and then choose Test:
- In start, type
2015-10-01T00:00:00Z - In end, type
2015-10-04T00:00:00Z
Verify that the response status is 200 and the response body contains a JSON response with 3 orders.

Now that your test is successful, you can deploy your changes to the production stage. In the Resources pane, choose Deploy API. In Deployment stage, choose prod. In Deployment description, type a description of the deployment, and then choose Deploy.

The prod Stage Editor page appears, displaying the Invoke URL. In the CloudWatch Settings section, choose Enable CloudWatch Logs so you can see logs and metrics from this stage. Keep in mind that CloudWatch logs are charged to your account separately from API Gateway.
You have now deployed an API that is backed by V1 of the Lambda function.
Testing V1 of the API
Now you’ll test V1 of the API with curl and confirm its behavior. First, copy the Invoke URL and add the query parameters ?start=2015-10-01T00:00:00Z&end=2015-10-04T00:00:00Z and make a GET invocation using curl.
$ curl -s "https://your-invoke-url-and-path/orders?start=2015-10-01T00:00:00Z&end=2015-10-04T00:00:00Z"
[
{
"orderId": "order-0",
"orderTs": "2015-10-01T00:00:00.000Z",
"orderAmount": 82
},
{
"orderId": "order-1",
"orderTs": "2015-10-02T00:00:00.000Z",
"orderAmount": 3
},
{
"orderId": "order-2",
"orderTs": "2015-10-03T00:00:00.000Z",
"orderAmount": 75
}
]
This should output a JSON response with 3 orders. Next, check what happens if you use a longer time-range by changing the end timestamp to 2015-10-15T00:00:00Z:
$ curl -s "https://your-invoke-url-and-path/orders?start=2015-10-01T00:00:00Z&end=2015-10-15T00:00:00Z"
{
"errorMessage": "Too many results, try your request with a shorter duration"
}
You see that the API returns an error indicating the time range is too long. This is correct V1 API behavior, so you are all set.
Updating the Lambda Function to V2
Next, you will update the Lambda function code to V2. This simulates the scenario of the back end of your API changing in a manner that is not backward compatible.
Switch to the Lambda console and choose the getOrders function. In the code input box, copy and paste the code snippet below. Be sure to replace all of the existing V1 code with V2 code.
MILISECONDS_DAY = 3600*1000*24;
exports.handler = function(event, context) {
console.log('start =', event.start);
console.log('end =', event.end);
start = Date.parse(decodeURIComponent(event.start));
end = Date.parse(decodeURIComponent(event.end));
token = NaN;
if(event.token) {
s = new Buffer(event.token, 'base64').toString();
token = Date.parse(s);
}
if(isNaN(start)) {
context.fail("Invalid parameter 'start'");
}
if(isNaN(end)) {
context.fail("Invalid parameter 'end'");
}
if(!isNaN(token)) {
start = token;
}
duration = end - start;
if(duration <= 0) {
context.fail("Invalid parameters 'end' must be greater than 'start'");
}
orderList = [];
count = 0;
console.log('start=', start, ' end=', end);
for(d = start; d < end && count < 5; d += MILISECONDS_DAY) {
order = {
"orderId" : "order-" + count,
"orderTs" : (new Date(d).toISOString()),
"orderAmount" : Math.round(Math.random()*100.0)
};
count += 1;
orderList.push(order);
}
nextToken = null;
if(d < end) {
nextToken = new Buffer(new Date(d).toISOString()).toString('base64');
}
console.log('Generated', count, 'orders');
result = {
orders : orderList,
};
if(nextToken) {
result.nextToken = nextToken;
}
context.succeed(result);
};
Choose Save to save V2 of the code. Then choose Test. Note that the output structure is different in V2 and there is a second level of nesting in the JSON document. This represents the updated V2 output structure that is different from V1.

Next, repeat the curl tests from the previous section. First, do a request for a short time duration. Notice that the response structure is nested differently from V1 and this is a problem for our API consumers that expect V1 responses.
$ curl -s "https://your-invoke-url-and-path/orders?start=2015-10-01T00:00:00Z&end=2015-10-04T00:00:00Z"
{
"orders": [
{
"orderId": "order-0",
"orderTs": "2015-10-01T00:00:00.000Z",
"orderAmount": 8
},
{
"orderId": "order-1",
"orderTs": "2015-10-02T00:00:00.000Z",
"orderAmount": 92
},
{
"orderId": "order-2",
"orderTs": "2015-10-03T00:00:00.000Z",
"orderAmount": 84
}
]
}
Now, repeat the request for a longer time range and you’ll see that instead of an error message, you now get the first page of information with 5 orders and a nextToken that will let you request the next page. This is the paginated behavior of V2 of the API.
$ curl -s "https://your-invoke-url-and-path/orders?start=2015-10-01T00:00:00Z&end=2015-10-15T00:00:00Z"
{
"orders": [
{
"orderId": "order-0",
"orderTs": "2015-10-01T00:00:00.000Z",
"orderAmount": 62
},
{
"orderId": "order-1",
"orderTs": "2015-10-02T00:00:00.000Z",
"orderAmount": 59
},
{
"orderId": "order-2",
"orderTs": "2015-10-03T00:00:00.000Z",
"orderAmount": 21
},
{
"orderId": "order-3",
"orderTs": "2015-10-04T00:00:00.000Z",
"orderAmount": 95
},
{
"orderId": "order-4",
"orderTs": "2015-10-05T00:00:00.000Z",
"orderAmount": 84
}
],
"nextToken": "MjAxNS0xMC0wNlQwMDowMDowMC4wMDBa"
}
It is clear from these tests that V2 will break the current V1 consumer’s code. Next, we show how to isolate your V1 consumers from this change using API Gateway mapping templates.
Cloning the API
Because you want both V1 and V2 of the API to be available simultaneously to your API consumers, you first clone the API to create a V2 API. You then modify the V1 API to make it behave as your V1 consumers expect.
Go back to the API Gateway console, and choose Create API. Configure the new API with the following values:
- In API name, type StoreFrontV2
- In Clone from API, choose StoreFront
- In Description, type a description
- Choose Create API to clone the StoreFront API as StoreFrontV2
Open the StoreFrontV2 API and choose the GET method of the /orders resource. Next, choose Integration Request. Choose the edit icon next to the getOrders Lambda function name.

Keep the name as getOrders and choose the check mark to accept. In the pop up, choose OK to allow the StoreFrontV2 to invoke the Lambda function.
Once you have granted API Gateway permissions to access your Lambda function, choose Deploy API. In Deployment stage, choose New stage. In Stage name, type prod, and then choose Deploy. Now you have a new StoreFrontV2 API that invokes the same Lambda function. Confirm that the API has V2 behavior by testing it with curl. Use the Invoke URL for the StoreFrontV2 API instead of the previously used Invoke URL.
Update the V1 of the API
Now you will use mapping templates to update the original StoreFront API to preserve V1 behavior. This enables existing consumers to continue to consume the API without having to make any changes to their code.
Navigate to the API Gateway console, choose the StoreFront API and open the GET method of the /orders resource. On the Method Execution details page, choose Integration Response.

Expand the default response mapping (HTTP status 200), and expand the Mapping Templates section. Choose Add Mapping Template.

In Content-type, type application/json and choose the check mark to accept. Choose the edit icon next to Output passthrough to edit the mapping templates. Select Mapping template from the drop down and copy and paste the mapping template below into the Template input box.
#set($nextToken = $input.path('$.nextToken'))
#if($nextToken && $nextToken.length() != 0)
{
"errorMessage" : "Too many results, try your request with a shorter duration"
}
#else
$input.json('$.orders[*]')
#end
Choose the check mark to accept and save. The mapping template transforms the V2 output from the Lambda function into the original V1 response. The mapping template also generates an error if the V2 response indicates that there are more results than can fit in one page. This emulates V1 behavior.

Finally click Save on the response mapping page. Deploy your StoreFront API and choose prod as the stage to deploy your changes.
Verify V1 behavior
Now that you have updated the original API to emulate V1 behavior, you can verify that using curl again. You will essentially repeat the tests from the earlier section. First, confirm that you have the Invoke URL for the original StoreFront API. You can always find the Invoke URL by looking at the stage details for the API.
Try a test with a short time range.
$ curl -s "https://your-invoke-url-and-path/orders?start=2015-10-01T00:00:00Z&end=2015-10-04T00:00:00Z"
[
{
"orderId": "order-0",
"orderTs": "2015-10-01T00:00:00.000Z",
"orderAmount": 50
},
{
"orderId": "order-1",
"orderTs": "2015-10-02T00:00:00.000Z",
"orderAmount": 16
},
{
"orderId": "order-2",
"orderTs": "2015-10-03T00:00:00.000Z",
"orderAmount": 14
}
]
Try a test with a longer time range and note that the V1 behavior of returning an error is recovered.
$ curl -s "https://your-invoke-url-and-path/orders?start=2015-10-01T00:00:00Z&end=2015-10-15T00:00:00Z"
{
"errorMessage": "Too many results, try your request with a shorter duration"
}
Congratulations, you have successfully used Amazon API Gateway mapping templates to expose both V1 and V2 versions of your API allowing your API consumers to migrate to V2 on their own schedule.
Be sure to delete the two APIs and the AWS Lambda function that you created for this walkthrough to avoid being charged for their use.
Powering your Amazon ECS Clusters with Spot Fleet
My colleague Drew Dennis sent a nice guest post that shows how to use Amazon ECS with Spot fleet.
—
There are advantages to using on-demand EC2 instances. However, for many workloads, such as stateless or task-based scenarios that simply run as long as they need to run and are easily replaced with subsequent identical processes, Spot fleet can provide additional compute resources that are more economical. Furthermore, Spot fleet attempts to replace any terminated instances to maintain the requested target capacity.
Amazon ECS is a highly scalable, high performance, container management service that supports Docker containers and allows you to run applications on a managed cluster of Amazon EC2 instances easily. ECS already handles the placement and scheduling of containers on EC2 instances. When combined with Spot fleet, ECS can deliver significant savings over EC2 on-demand pricing.
Why Spot fleet?
Amazon EC2 Spot instances allow you to bid on spare Amazon EC2 computing capacity. Because Spot instances are often available at a discount compared to On-Demand pricing, you can significantly reduce the cost of running your applications. Spot fleet enables customers to request a collection of Spot instances across multiple Availability Zones and instance types with a single API call.
The Spot fleet API call can specify a target capacity and an allocation strategy. The two available allocation strategies are lowest price and diversified. Lowest price means the instances are provisioned based solely on the lowest current Spot price available while diversified fulfills the request equally across multiple Spot pools (instances of the same type and OS within an Availability Zone) to help mitigate the risk of a sudden Spot price increase. For more information, see How Spot Fleet Works.
Using Spot fleet
The Spot fleet console is available at https://console.aws.amazon.com/ec2spot/home. It provides a simple approach to creating a Spot fleet request and setting up all necessary attributes of the request, including creating an IAM role and base64-encoding user data. The console also provides the option to download the request JSON, which can be used with the CLI if desired.
If you prefer not to use the Spot fleet console, you need to make sure you have an IAM role created with the necessary privileges for the Spot fleet request to bid on, launch, and terminate instances. Note that the iam:PassRole action is needed in this scenario so that Spot fleet can launch instances with a role to participate in an ECS cluster. You need to make sure that you have an AWS SDK or the AWS CLI installed.
This post assumes you are familiar with the process of creating an ECS cluster, creating an ECS task definition, and launching the task definition as a manual task or service. If not, see the ECS documentation.
Creating a Spot fleet request
Before you make your Spot fleet request, make sure you know the instance types, Availability Zones, and bid prices that you plan to request. Note that individual bid prices for various instance types can be used in a Spot fleet request. When you have decided on these items, you are ready to begin the request. In the screenshot below, a fleet request is being created for four c4.large instances using an Amazon Linux ECS-optimized AMI. You can obtain the most up-to-date list of ECS optimized AMIs by region in the Launching an Amazon ECS Container Instance topic.

Notice the very useful warnings if your bid price is below the minimum price to initially launch the instance. From here, you can also access the Spot pricing history and Spot Bid Advisor to better understand past pricing volatility. After choosing Next, you see options to spread the request across multiple zones, specify values for User data, and define other request attributes as shown below. In this example, the user data sets the ECS cluster to which the ECS container agent connects.

Other examples could create a Spot fleet request that contains multiple instance types with Spot price overrides for each instance type in a single Availability Zone. The allocation strategy could still be diversified, which means it will pull equally from the two instance-type pools. This could easily be combined with the previous example to create a fleet request that spans multiple Availability Zones and instance types, further mitigating the risk of Spot instance termination.
Running ECS tasks and services on your Spot fleet
After your instances have joined your ECS cluster, you are ready to start tasks or services on them. This involves first creating a task definition. For more information, see the Docker basics walkthrough. After the task definition is created, you can run the tasks manually, or schedule them as a long-running process or service.
In the case of an ECS service, if one of the Spot fleet instances is terminated due to a Spot price interruption, ECS re-allocates the running containers on another EC2 instance within the cluster to maintain the desired number of running tasks, assuming that sufficient resources are available.
If not, within a few minutes, the instance is replaced with a new instance by the Spot fleet request. The new instance is launched according to the configuration of the initial Spot fleet request and rejoins the cluster to participate and run any outstanding containers needed to meet the desired quantity.
In summary, Spot fleet provides an effective and economical way to add instances to an ECS cluster. Because a Spot fleet request can span multiple instance types and Availability Zones, and will always try to maintain a target number of instances, it is a great fit for running stateless containers and adding inexpensive capacity to your ECS clusters.
Auto Scaling and Spot fleet requests
Auto Scaling has proven to be a great way to add or remove EC2 capacity to many AWS workloads. ECS supports Auto Scaling on cluster instances and provides CloudWatch metrics to help facilitate this scenario. For more information, see Tutorial: Scaling Container Instances with CloudWatch Alarms. The combination of Auto Scaling and Spot fleet provides a nice way to have a pool of fixed capacity and variable capacity on demand while reducing costs.
Currently, Spot fleet requests cannot be integrated directly with Auto Scaling policies as they can with Spot instance requests. However, the Spot fleet API does include an action called ModifySpotFleetRequest that can change the target capacity of your request. The Dynamic Scaling with EC2 Spot Fleet blog post shows an example of a scenario that leverages CloudWatch metrics to invoke a Lambda function and change the Spot fleet target capacity. Using ModifySpotFleetRequest can be a great way to not only fine-tune your fleet requests, but also minimize over-provisioning and further lower costs.
Conclusion
Amazon ECS manages clusters of EC2 instances for reliable state management and flexible container scheduling. Docker containers lend themselves to flexible and portable application deployments, and when used with ECS provide a simple and effective way to manage fleets of instances and containers, both large and small.
Combining Spot fleet with ECS can provide lower-cost options to augment existing clusters and even provision new ones. Certainly, this can be done with traditional Spot instance requests. However, because Spot fleet allows requests to span instance families and Availability Zones (with multiple allocation strategies, prices, etc.), it is a great way to enhance your ECS strategy by increasing availability and lowering the overall cost of your cluster’s compute capacity.
Amazon ECS launches new deployment capabilities; CloudWatch metrics; Singapore and Frankfurt regions
Today, we launched two improvements that make it easier to run Docker-enabled applications on Amazon EC2 Container Service (ECS). Amazon ECS is a highly scalable, high performance container management service that supports Docker containers and allows you to easily run applications on a managed cluster of Amazon EC2 instances.
The first improvement allows more flexible deployments. The ECS service scheduler is used for long running stateless services and applications. The service scheduler ensures that the specified number of tasks are constantly running and can optionally register tasks with an Elastic Load Balancing load balancer. Previously, during a deployment the service scheduler created a task with the new task definition; after the new task reached the RUNNING state, a task that was using the old task definition was drained and stopped. This process continued until all of the desired tasks in the service were using the new task definition. This process maintains the service’s capacity during the deployment, but requires enough spare capacity in the cluster to start one additional task. Sometimes that’s not desired, because you do not want to use additional capacity in your cluster to perform a deployment.
Now, a service’s minimumHealthyPercent lets you specify a lower limit on the number of running tasks during a deployment. A minimumHealthyPercent of 100% ensures that you always have the desiredCount of tasks running and values below 100% allow the scheduler to violate desiredCount temporarily during a deployment. For example, if you have 4 Amazon EC2 instances in your cluster, and 4 tasks each running on a separate instances, changing minumumHealthyPercent from 100% to 50% would allow the scheduler to stop 2 tasks before deploying 2 new tasks.
A service’s maximumPercent represents an upper limit on the number of running tasks during a deployment, enabling you to define the deployment batch size. For example, if you have 8 instances in your cluster, and 4 tasks, each running on a separate instance, maximumPercent of 200% starts 4 new tasks before stopping the 4 old tasks. For more information on these new deployment options, see the documentation.
To illustrate these options visually, consider a scenario where you want to deploy using the least space. You could set minimumHealthyPercent to 50% and maximumPercent to 100%. The deployment would look like this:

Another scenario is to deploy quickly without reducing your service’s capacity. You could set set minimumHealthyPercent to 100% and maximumPercent to 200%. The deployment would look like this:

The next improvement involves scaling the EC2 instances in your ECS cluster automatically. When ECS schedules a task, it requires an EC2 instance that meets the constraints in the task definition. For example, if a task definition requires 1 GB RAM, ECS finds an EC2 instance that has at least that much memory so that the container can start. If the scheduler cannot find an EC2 instance that meets the constraints required to place a task, it fails to place the task.
Managing the cluster capacity is thus essential to successful task scheduling. Auto Scaling can enable clusters of EC2 instances to scale dynamically in response to CloudWatch alarms. ECS now publishes CloudWatch metrics for the reserved amount of CPU and memory used by running tasks in the cluster. You can create a CloudWatch alarm using these metrics that adds more EC2 instances to the Auto Scaling group when the cluster’s available capacity drops below a threshold that you define. For more information, see Tutorial: Scaling Container Instances with CloudWatch Alarms.
Last, Amazon ECS is now available in the Asia Pacific (Singapore) region and EU (Frankfurt) regions, bringing ECS to eight regions.
Using AWS Lambda with Auto Scaling Lifecycle Hooks
Nathan Mcguirt, AWS Solution Architect
Using automation to extend Auto Scaling functionality
Auto Scaling provides customers a great way to dynamically scale applications, and we frequently meet customers with new and interesting use cases who want to extend Auto Scaling with additional actions. For example, notifying an auditing system of a new instance launch or taking some sort of extra action on the instance like attaching a secondary network interface. To support these types of use cases, Auto Scaling supports adding a hook to the launching and terminating stages of the Auto Scaling instance lifecycle, which will send an SNS notification and then hold the instance in a pending state waiting for a callback to the API. It also includes a user configurable timeout and default action if your external operation doesn’t complete in a timely fashion or returns an error.
By attaching a Lambda function to the lifecycle hook by way of SNS, we can add a virtually limitless number of custom actions to our Auto Scaling group. For example, we recently had a customer with a requirement for their Auto Scaling instances to have a secondary network interface in an isolated administrative subnet in order to meet their compliance requirements. They achieved this by using a lifecycle hook to a Lambda function that created an Elastic Network Interface (ENI) in the appropriate subnet and attached it to the instance. For this use case, it was critical that that the instance have the secondary ENI before going into production, and they were able to meet this requirement by configuring the lifecycle hook to abandon the instance after a timeout period or if the Lambda function were to fail.
Demonstration: Adding Secondary Elastic Network Interface to Auto Scaling Instances
In this post, we’ll walk through a demonstration of how to implement the above use case of adding a secondary network interface to Auto Scaling instances. A caveat for console users, the functionality to add a lifecycle hook to an Auto Scaling group is available through the API only, so we’ll be using the AWS CLI for this demonstration rather than the AWS Console.
Prerequistites and Caveats
If you’re doing to follow along in your own account, you should have the following completed first:
- Your VPC, Subnets and LaunchConfig are prepared and you have their ID’s recorded.
- Your Auto Scaling group is configured with a desired size of 0 (the hook will only apply to new instances joining the pool.)
- You have a subnet and security group for the secondary ENI configured (and the subnet is in the same availability zone as the one you’re using for your Auto Scaling instances)
- You have AWS CLI installed and configured, and are using credentials with sufficient permissions.
A couple of caveats before we begin, the secondary ENI must be in the same availability zone the instance was launched in (the same zone as the primary ENI). For the sake of simplicity, the code we’re using in this demo isn’t aware of multiple subnets and availability zones. It will attempt to create the secondary ENI in whatever subnet is passed to it. If you’re going to do this in production, you’ll also likely want to configure a second lifecycle hook on instance termination and a Lambda function to delete the secondary ENI of the terminating instance. Also, pay attention to naming and capitalization as you create resources. The names of Auto Scaling groups and Lambda functions are case sensitive. You can name these resources however you wish, but consistent naming will help you avoid troubleshooting later.
Part 1: Configure the Notification Topic
First, we’ll need to create the Simple Notification Service notification topic.
$ aws sns create-topic --name ENI-Demo-Topic
In the response, Note the ARN of the topic for later use.
{
"TopicArn": "arn:aws:sns:us-west-2:012345678901:ENI-Demo-Topic"
}
Part 2: Configure an IAM role to allow posting to the SNS topic
The lifecycle hook uses an IAM role to send messages to SNS, so before we can create the hook we need to have the role prepared. To create the IAM role, we’ll need two policies, the first is a trust policy allowing Auto Scaling to assume the role, and the second gives the role permission to publish to the SNS topic. We’ll create the role in two steps, first creating the role and submitting the trust policy, and the second to apply the inline policy.
With the CLI, it’s a sometimes easier to first put the policies into text files to manage them, rather than trying to escape them in your shell, so we’ll do that here. We can specify these files on the command line later. The assume role policy (sometimes called trust policy) should look as follows, and we’ll save it in a text file called SNS-Role-Trust-Policy.json.
{
"Version": "2012-10-17",
"Statement": [ {
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "autoscaling.amazonaws.com"
},
"Action": "sts:AssumeRole"
} ]
}
The next policy will be the inline policy granting the role access to publish to our SNS topic, and should look as follows. We’ll save it as SNS-Role-Inline-Policy.json for the example. Don’t forget to replace the example ARN with the one from your SNS topic from step 2.
{
"Version": "2012-10-17",
"Statement": [ {
"Effect": "Allow",
"Resource": "arn:aws:sns:us-west-2:012345678901:ENI-Demo-Topic",
"Action": [
"sns:Publish"
]
} ]
}
With these policies ready, we can make the call to create the role as shown below, specifying the text files with the policy documents.
$ aws iam create-role \ --role-name ENI-Demo-Topic-Publisher-Role \ --assume-role-policy-document file://SNS-Role-Trust-Policy.json
And we then apply the inline policy to it.
$ aws iam put-role-policy \ --role-name ENI-Demo-Topic-Publisher-Role \ --policy-name AllowPublishToEniDemoTopic \ --policy-document file://SNS-Role-Inline-Policy.json
Part 3: Configure the Lambda Function’s IAM role
With the lifecycle hook in place, the next step is to configure the Lambda function. Lambda functions need an IAM role to give them their execution permissions, so we’ll start there. If you are using the CloudFormation sample, you can skip this, the IAM role has already been configured by CloudFormation for you.
First, like the above IAM role, we’ll need some policy documents, and will go ahead and put them in text files first for ease of use. We’ll start with the role’s Assume Role Policy (Trust Policy) which we’ll save as Lambda-Role-Trust-Policy.json, and it’s contents should be as follows:
{
"Version": "2012-10-17",
"Statement": [ {
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
} ]
}
Next we need the inline policy that defines what the Lambda function is allowed to do. We’ll save it as Lambda-Role-Inline-Policy.json, and it should look like the below.
{
"Version": "2012-10-17",
"Statement": [ {
"Effect": "Allow",
"Resource": "*",
"Action": [
"ec2:DescribeInstances",
"ec2:CreateNetworkInterface",
"ec2:AttachNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"autoscaling:CompleteLifecycleAction"
]
},
{
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*",
"Effect": "Allow"
} ]
}
With the policies prepared, we can create the role, specifying the Assume Role (trust) Policy.
$ aws iam create-role \ --role-name ENI-Demo-Lambda-Role \ --assume-role-policy-document file://Lambda-Role-Trust-Policy.json
Note the ARN of the new role, we’ll need it when we go to set up the Lambda function. Once the role is created, we apply the inline policy, specifying the file we created earlier.
$ aws iam put-role-policy \ --role-name ENI-Demo-Lambda-Role \ --policy-name CreateAndAttachEnisWithLogging \ --policy-document file://Lambda-Role-Inline-Policy.json
Part 4: Put the lifecycle hook
Next, we’ll put the lifecycle hook on the Auto Scaling group. Lifecycle hooks support a metadata field that can be used to embed information specific to the hook in the message. In this case, we’ll specify the resource ids of the admin subnet and security groups, which are specific to this Auto Scaling group. This way, our Lambda function could be used across multiple different Auto Scaling groups. These metadata items should reflect the subnet and security groups you’d like to use for the secondary ENI. For ease of use on the CLI, we’ll write that metadata to a text file as we did with IAM. It should look like the below, and we’ll save it as Lifecycle-Hook-Metadata.json.
{
"SubnetId":"subnet-abcdefg0",
"SecurityGroups":["sg-abcdefg0"]
}
Now, we can put the lifecycle hook.
$ aws autoscaling put-lifecycle-hook \ --notification-metadata file://Lifecycle-Hook-Metadata.json \ --lifecycle-hook-name ENI-Demo-Hook \ --auto-scaling-group-name ENI-Demo-ASG \ --notification-target-arn arn:aws:sns:us-west-2:0123456789012:ENI-Demo-Topic \ --role-arn arn:aws:iam::0123456789012:role/ENI-Demo-Topic-Publisher-Role \ --lifecycle-transition autoscaling:EC2_INSTANCE_LAUNCHING \ --heartbeat-timeout 60
Part 5: Create the Lambda function
With the rest of the building blocks in place, we can create the lambda function. We’ll include the main function here, but our sample code uses an extra library called Async to make it easier to have node.js do things like wait for object an to be ready. External packages are supported in Lambda by adding the additional modules along with your code and package them in a zip file. Instructions on how to package the code and module are available in our previous blog post on using packages in AWS Lambda. You’ll need to use Async version greater than 1.0.0 for this sample.
Below is the sample code for our lambda function. It receives the message from SNS as a parameter in JSON format and unpacks the message to get the required parameters from Auto Scaling, such as the instance, subnet, and security group IDs. With this data, the Lambda function can create the network interface. Once the interface has been created, the function waits, polling for the interface state to show ‘available’ and then attaches it to the instance.
// This is sample Node.js code for AWS Lambda, to attach a secondary Elastic
// Network Interface to an instance. To use this function, create an Auto Scaling
// lifecycle hook on instance creation notifying a SNS topic, and
// subscribe the lambda function to the SNS topic.
// Sane values for Memory and Timeout are 128MB and 30s respectively.
var AWS = require('aws-sdk');
var ec2 = new AWS.EC2();
var as = new AWS.AutoScaling();
var async = require('async');
exports.handler = function (notification, context) {
// Log the request
console.log("INFO: request Recieved.\nDetails:\n", JSON.stringify(notification));
var message = JSON.parse(notification.Records[0].Sns.Message);
var metadata = JSON.parse(message.NotificationMetadata);
console.log("DEBUG: SNS message contents. \nMessage:\n", message);
console.log("DEBUG: Extracted Message Data\nData:\n", metadata);
// Pull out metadata
var instanceId = message.EC2InstanceId;
var subnetId = metadata.SubnetId;
var securityGroups = metadata.SecurityGroups;
//define a closure for easy termination later on
var terminate = function (success, err) {
var lifecycleParams = {
"AutoScalingGroupName" : message.AutoScalingGroupName,
"LifecycleHookName" : message.LifecycleHookName,
"LifecycleActionToken" : message.LifecycleActionToken,
"LifecycleActionResult" : "ABANDON"
};
//log that we're terminating and why
if(!success){
console.log("ERROR: Lambda function reporting failure to AutoScaling with error:\n", err);
}else{
console.log("INFO: Lambda function reporting success to AutoScaling.");
lifecycleParams.LifecycleActionResult = "CONTINUE";
}
//call autoscaling
completeAsLifecycleAction (lifecycleParams, function lifecycleActionResponseHandler (err){
if(err){
context.fail();
}else{
//if we successfully notified AutoScaling of the instance status, tell lambda we succeeded
//even if the operation on the instance failed
context.succeed();
}
});
};
//Create the interface and wait for it to be ready
createEni(subnetId, securityGroups, function CreateEniCallback(err, eniId){
if(err){
console.log("ERROR: Could not create ENI. Errors:\n", err);
terminate(false,err);
}
//Wait for the ENI to be 'available'
waitEniReady(eniId, function waitEniReadyCallback (err){
if(err){
console.log("ERROR: Failure waiting for ENI to be ready");
terminate(false,err);
}
//attach it to the instance
attachNetworkInterface(eniId, instanceId, function attachNetworkInterfaceCallback(err,data){
if(err){
console.log("ERROR: Could not attach ENI. Error Data:\n", err);
terminate(false,err);
}else{
console.log("INFO: Successfully attached ENI");
terminate(true, err);
}
});
});
});
};
function attachNetworkInterface (networkInterfaceId, instanceId, callback){
//Attaches an ENI, passes the AttachmentId to callback.
var nic_params = {
'NetworkInterfaceId' : networkInterfaceId,
'InstanceId' : instanceId,
'DeviceIndex' : 1 // Should be safe to assume index 1 is available
};
ec2.attachNetworkInterface(nic_params, function evaluateEniAttachment(err,data) {
if (err) {
console.log("ERROR: ENI Attachment failed.\nDetails:\n", err);
callback(err, null);
}
console.log("INFO: ENI Attached.\nDetails:\n", data);
callback(null, data.AttachmentId);
});
}
function createEni(subnetId, securityGroups, callback){
//Create a network interface, pass the Interface ID to callback
var eniCreationParams = {
"SubnetId":subnetId,
"Groups":securityGroups
};
console.log("DEBUG: CreateEni Params:\n",eniCreationParams);
ec2.createNetworkInterface(eniCreationParams, function createEniCallback(err, data) {
if (err) {
console.log("ERROR: ENI creation failed.\nDetails:\n", err);
return callback(err, null);
}
console.log("INFO: ENI Created.\nData:\n", data);
return callback(null, data.NetworkInterface.NetworkInterfaceId);
});
}
function waitEniReady (eniId, waitEniReadyCallback){
//terminate is the termination function if there's an issue.
var getEniParams={
"NetworkInterfaceIds":[
eniId
]
};
console.log("INFO: Waiting on ENI to be ready:", eniId);
var eniStatus = undefined;
async.until(
function isReady (err) { return eniStatus === "available"; },
function getEniStatus(getEniStatusCallback){
ec2.describeNetworkInterfaces(getEniParams,function handleGetEniResponse(err,data){
eniStatus = data.NetworkInterfaces[0].Status;
console.log("DEBUG: ENI status is:", eniStatus);
getEniStatusCallback(err);
});
},
function waitEniReadyCallbackClosure(err){
if(err){
console.log("ERROR: error waiting for ENI to be ready:\n",err);
}
waitEniReadyCallback(err);
}
);
}
function completeAsLifecycleAction(lifecycleParams, callback){
//returns true on success or false on failure
//notifies AutoScaling that it should either continue or abandon the instance
as.completeLifecycleAction(lifecycleParams, function(err, data){
if (err) {
console.log("ERROR: AS lifecycle completion failed.\nDetails:\n", err);
console.log("DEBUG: CompleteLifecycleAction\nParams:\n", lifecycleParams);
callback(err);
} else {
console.log("INFO: CompleteLifecycleAction Successful.\nReported:\n", data);
callback(null);
}
});
}
Below are the CLI commands to create the lambda function. In a later part, we’ll have to come back and set the required permissions on it to allow SNS to invoke the function, but for now we’re just going to create it. We’ll create this function with the name ENI-Demo-Lambda-Func, configure the function to use the IAM role, and set the timeout for 30 seconds. The timeout should allow plenty of time for the resources to create and become ready during execution, in most cases the function will not require that much time. Note the fileb:// prefix on the URI for the zip file.
aws lambda create-function \ --function-name ENI-Demo-Lambda-Func \ --zip-file fileb://ENI-Demo-Lambda-1-0.zip \ --runtime nodejs \ --role arn:aws:iam::012345678901:role/ENI-Demo-Lambda-Role \ --handler ENI-Demo-Lambda-1-0.handler \ --timeout 30
Part 6: Subscribe the Lambda function to the SNS topic
Now that the lambda function has been created, we need to subscribe it to the SNS topic so that it will recieve the messages from the lifecycle hook.
To subscribe the Lambda function to the SNS topic, we’ll call the SNS Subscribe action, specifying the ARN for the topic and the Lambda function, with Lambda as the protocol. The command looks like this:
aws sns subscribe --protocol lambda \ --topic-arn arn:aws:sns:us-west-2:012345678901:ENI-Demo-Topic \ --notification-endpoint arn:aws:lambda:us-west-2:012345678901:function:ENI-Demo-Lambda-Func
Part 7: Grant permissions on the lambda function to the SNS topic
And Finally, with the function created and subscribed, we can set the permissions on it that allow the SNS topic to invoke the function. This is done with the AddPermission call to Lambda.
aws lambda add-permission \ --function-name ENI-Demo-Lambda-Func \ --statement-id 1 \ --action "lambda:InvokeFunction" \ --principal sns.amazonaws.com \ --source-arn arn:aws:sns:us-west-2:012345678901:ENI-Demo-Topic
Testing and Reviewing the Logs
To test, simply edit your Auto Scaling group to increase the desired size, causing an instance to be added to the group. Aside from just waiting to see the secondary ENI, you can monitor and troubleshoot in a few different ways. From Auto Scaling, you can describe the instance status for the group. New instances will be in a pending state until the function succeeds, fails or times out. If successful, they’ll show as in service. If it fails or times out, they’ll be abandoned and replaced with a new instance (that’s the default behavior we configured above.) From CloudWatch, you can look at the Invocation and Errors metric for the function (or view graphs from the Lambda web console.) If the Lambda function has CloudWatch logs access, which is included in the example policy above, the Lambda function will create a log group for itself and then each function execution will create a new log stream, creating log events for any output from the function. The example code here is configured for detailed logging of it’s actions. We’ll demonstrate what that looks like here.
First, we need to find the appropriate log stream. The following command will list the log streams within the Log Group for the function.
aws logs describe-log-streams --log-group /aws/lambda/ENI-Demo-Lambda-Func
From the output, choose the execution you want to review and run the following command, using the log-stream from the previous step.
$ aws logs get-log-events --log-group-name /aws/lambda/ENI-Demo-Lambda-Func \ --log-stream-name 2015/08/11/[HEAD]0123456789abcdef0123456789abcdef \ --start-from-head
And below is a short sample of what the output looks like:
{
"ingestionTime": 1439317268902,
"timestamp": 1439317254204,
"message": "2015-08-11T18:20:54.178Z12345678-0123-0123-0123-0123456789ab\tDEBUG: ENI status is: available\n"
},
{
"ingestionTime": 1439317268902,
"timestamp": 1439317254584,
"message": "2015-08-11T18:20:54.582Z12345678-0123-0123-0123-0123456789ab\tINFO: ENI Attached.\nDetails:\n { AttachmentId: 'eni-attach-01234567' }\n"
}
Using API Gateway stage variables to manage Lambda functions
Ed Lima, Cloud Support Engineer
There’s a new feature on Amazon API Gateway called stage variables. Stage variables act like environment variables and can be used to change the behavior of your API Gateway methods for each deployment stage; for example, making it possible to reach a different back end depending on which stage the API is running on. This blog post will demonstrate how to use stage variables with two different AWS Lambda functions.
For this example we will use the sample functions from the Lambda Walkthrough. Sign in to the AWS Management console, open the Lambda console, and create the required functions (make sure you’re using the appropriate IAM execution role:
GetHelloWorld
console.log('Loading event');
exports.handler = function(event, context) {
context.done(null, {"Hello":"World"}); // SUCCESS with message
};
GetHelloWorldWithName
console.log('Loading event');
exports.handler = function(event, context) {
var name = (event.name === undefined ? 'No-Name' : event.name);
context.done(null, {"Hello":name}); // SUCCESS with message
};
In the API Gateway console, create a new API called LambdaVar:

In the root resource, create a new GET method. In Integration type for the new method, choose Lambda Function, then select your Lambda Region, and type ${stageVariables.lbfunc} in the Lambda Function field. This tells API Gateway to read the value for this field from a stage variable at runtime:

The console detects the stage variable and displays the Add Permission to Lambda Function message:

Next, you manually give permissions to your Lambda functions, using the AWS CLI. This enables API Gateway to execute the functions. The CLI command must be issued with credentials that have permission to call the “add-permission” action of the Lambda APIs. The output from the AWS CLI will contain the policy statement that was set on the Lambda function resource policies.
aws lambda add-permission --function-name arn:aws:lambda:us-west-2:XXXXXXXXXXXXX:function:GetHelloWithName --source-arn arn:aws:execute-api:us-west-2:XXXXXXXXXXXXX:y91j2l4bnd/*/GET/ --principal apigateway.amazonaws.com --statement-id 95486b16-7d8a-4aca-9322-5f883ab702a6 --action lambda:InvokeFunction
# expected output
{
"Statement": "{\"Condition\":{\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:execute-api:us-west-2: XXXXXXXXXXXX:y91j2l4bnd/*/GET/\"}},\"Action\":[\"lambda:InvokeFunction\"],\"Resource\":\"arn:aws:lambda:us-west-2:XXXXXXXXXXXX:function:GetHelloWithName\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"apigateway.amazonaws.com\"},\"Sid\":\"95486b16-7d8a-4aca-9322-5f883ab702a6\"}"
}
aws lambda add-permission --function-name arn:aws:lambda:us-west-2:XXXXXXXXXXXXX:function:GetHelloWorld --source-arn arn:aws:execute-api:us-west-2:XXXXXXXXXXXXX:y91j2l4bnd/*/GET/ --principal apigateway.amazonaws.com --statement-id 95486b16-7d8a-4aca-9322-5f883ab702a6 --action lambda:InvokeFunction
# expected output
{
"Statement": "{\"Condition\":{\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:execute-api:us-west-2: XXXXXXXXXXXXX:y91j2l4bnd/*/GET/\"}},\"Action\":[\"lambda:InvokeFunction\"],\"Resource\":\"arn:aws:lambda:us-west-2: XXXXXXXXXXXXX:function:GetHelloWorld\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"apigateway.amazonaws.com\"},\"Sid\":\"95486b16-7d8a-4aca-9322-5f883ab702a6\"}"
}
Back in the console, you can now create your first stage. Choose Deploy API. In Stage name, type dev. In Stage description, type a description for your new stage, and then choose Deploy.

After the API deploys, on the Stage Editor page, choose the Stage Variables tab and add the stage variable from your API configuration, lbfunc. As you can see from the screenshot the value assigned to the new stage variable is the name of the Lambda function we want to invoke:

The second Lambda function, GetHelloWithName, can also receive a name parameter. You can configure the API to read the incoming parameter from the query string and pass it to the JSON body for the Lambda function by using mapping templates. To do this, go back to the GET method and choose Integration Request. Under Mapping Templates, add the following mapping template for the application/json content type:
{ "name": "$input.params('name')" }

To apply the change, deploy the API to a new stage called prod:

Next, set up the stage variable in the new deployment stage to point to the second Lambda function, GetHelloWithName:

Now you are ready to test!
The dev stage invoke URL directs you to the GetHelloWorld Lambda function:

The prod stage invoke URL with the appropriate query string directs you to the GetHelloWithName Lambda function and returns a value:

If you try to use the query string on the first stage, the query string will simply be ignored because the Lambda function is not configured to handle the parameter:

There it is: a nice way to optimize your Amazon API Gateway resources by using a single method with 2 different stages that use 2 different Lambda functions.
Alternatively, you can mix and match static names with stage variables in the integrations. For example, instead of having 2 different Lambda functions you can have a single Lambda function with multiple versions and aliases. Then, in the integration setup, you can simply use the stage variables to point to the correct alias. For instance, using an alias to one of the Lambda functions above, add the following: GetHelloWithName:${stageVariables.lambdaAlias} as Integration Type:

In your stage, add the lambdaAlias stage variable accordingly:

This variable will refer to the Lambda alias of your function:

As you can see, the new stage variables feature enables you to dynamically access different back ends, using fewer configuration steps and resources/methods in your API Gateway. The variables add even more flexibility to stages when deploying your API, which can enable different use cases in your environments.
Getting Started with JAWS on Amazon Web Services
Nick Corbett, AWS Professional Services, Big Data Consultant
Amazon API Gateway and AWS Lambda empower developers to deliver a microservice architecture without managing infrastructure. Building scalable, secure, and durable applications has never been easier. However, managing the deployment of a large project is not always easy. A global app, deployed across multiple AWS regions in multiple environments will collect API Gateway resources, AWS Lambda functions, Amazon Identity and Access Management (IAM) roles and other AWS resources. As your project grows, so will the number of resources. The need to coordinate and organize your efforts quickly becomes apparent.
In this post I will introduce JAWS, an open source application framework that you can use to develop massively scalable and complex apps using API Gateway and AWS Lambda whilst helping you manage your codebase and deployments. I will show you how to build a simple microservice that you can use to manage users for a sample application. You will build CRUD methods to support the management of users and persist details in Amazon DynamoDB.
To get started, you’ll first need to install Node.js. Once you’ve done that, you can install JAWS using node.js’ package manger from a command prompt (note that on some systems you may need to run this command as super user):
npm install jaws-framework -g
Now that JAWS is installed, you are ready to create your first project. Navigate to the directory where you want to create your project and type:
jaws project create
JAWS will walk you through the process of creating a project. When prompted, enter the following information:
- Project Name: Specify “userManagement” as value. Camel case is recommended here. JAWS uses AWS CloudFormation to deploy your project and some items use the project name. CloudFormation tokenizes the project name with hyphens so it is best to avoid adding any more.
- Project Domain: Use any domain you own. It is important to make this unique for your project. The project domain is used as part of the name for a new Amazon Simple Storage Service (Amazon S3) bucket. This Amazon S3 bucket is used to deploy your solution.
- Email Address for CloudWatch Alarms: Your email address.
- Stage: Specify “dev“. A stage is an environment, such as dev, UAT, or production. Each region can have multiple stages. You can easily add more stages after the project is created.
- Region: Any AWS region. The AWS region in which you will deploy your solution. You can add other regions after the project is created. Regardless of the region you pick, API Gateway will create a global Amazon CloudFront distribution for your project to provide your users with the lowest possible latency for their API requests.
- Profile: Your AWS profile. JAWS uses a profile in your AWS Command Line Interface credentials file (in
~/.aws/credentials) to make API calls. If you have multiple profiles defined, you can select the one to use.
As it creates your project, the framework builds and runs a CloudFormation script containing some shared resources that are needed to support your project, such as IAM roles and the Amazon S3 bucket named after the project domain.
After this is complete, you are ready to create your first AWS Module (awsm). An awsm, is how JAWS describes your microservice and includes references to both your API Gateway endpoints and AWS Lambda functions. To create a module, navigate to the userManagement project directory that JAWS created and type:
jaws module create users create
This creates a new endpoint (users) with a method behind it for creating a new user (create). The following folders and files are created in the aws_modules directory of your project:

The create directory that JAWS made contains 4 files:
awsm.json: Contains configuration for the API Gateway endpoint and AWS Lambda methodindex.js: Contains the code you write to implement the methodhandler.jsevent.json: Defines the event that is used when your code is tested with the jaws run command
JAWS creates a thin wrapper around your code (in handler.js) to integrate with AWS Lambda. This means that you can develop and test your code (in index.js) before deploying to AWS Lambda. To demonstrate this, go to the index.js file in the create directory and update the code to:
// Export for Lambda Handler
module.exports.run = function(event, context, cb) {
return cb(null, action(event));
};
// Your code
var action = function(event) {
return {
message: 'You have created user ' + event.username
};
};
Next, edit the event.json file to read:
{
"username" : "Nick"
}
Finally, from the create directory, type the following command:
jaws run
The JAWS framework uses the event that is defined in event.json to test your code. The following message is returned:
JAWS: {"message":"You have created user Nick"}
The run command is good for simple testing but as your project grows, a unit test framework, such as Mocha, is recommended.
As well as developing an AWS Lambda function to implement your business logic, you also need to configure a REST endpoint in API Gateway. In our sample application, users are created using the following url:
/users called with POST
Go into awsm.json in the create directory and find the apiGateway section. Update the Path to users and the Method to POST. This indicates to JAWS that it should create a users resource in API gateway with a POST method that is integrated to your AWS Lambda code. There are other settings in the awsm.json file to control how your project is deployed, although there is no need to change anything else at the moment.
You are now ready to deploy the first iteration of your project to AWS. At the command line, type:
jaws dash
Use the arrow keys and enter to highlight both the endpoint and AWS Lambda function in yellow before navigating to deploy selected and pressing enter. Your code is then packaged, using Browserify and Minify to improve run-time efficiency, and zipped. This package is then uploaded to an S3 bucket.
For each project, JAWS maintains two CloudFormation stacks. The first stack, containing shared resources, was deployed when you made the project. JAWS now creates a second stack that contains your new AWS Lambda function (the code in the S3 bucket is used as a source). Any additional AWS Lambda functions that you write are added to this stack. After this stack is deployed, JAWS creates the API Gateway resources and methods.
You are now ready to test the deployment. Go to the AWS Management Console and open the Amazon API Gateway console. Click the userManagement-dev API and then click the POST method that JAWS created for the users resource. Click Test to test the function and use the JSON object from event.json in the request body. If everything has worked, you will see a response:
{
"message": "You have created user Nick"
}
You can view more detailed logging from your AWS Lambda function in Amazon CloudWatch Logs.
The next step is to add a similar stub for the GET function. This is accessed by the url:
/users/ called with GET
To create the endpoint and method, go to your command line and, from the project directory, type:
jaws module create users get
JAWS creates another sub-directory in your AWSM for the new method. Go into awsm.json in the get subdirectory and update the apiGateway section of the file by making the following changes to the cloudformation section:
"Path": "users/{username}"
"Method": "GET"
"RequestTemplates": {
"application/json": "{\"username\": \"$input.params('username') \"}"
}
This change indicates to JAWS that the AWS Lambda function will be invoked when the path users/{username} is called. It also specifies the format of the JSON event sent to the AWS Lambda function. For example, if the url users/Anna is called with a GET verb, then your AWS Lambda function is called with the following event:
{
"username": "Anna"
}
Go into index.js for the GET method and change the code to the following:
// Export For Lambda Handler
module.exports.run = function(event, context, cb) {
return cb(null, action(event));
};
var action = function(event) {
return {
message: "User requested: " + event.username
};
};
You are then ready to deploy your project again (using the jaws dash command). When you test this method in API Gateway console, you are asked for the username:

You’ve now created stub functions for the CREATE and GET methods. Hopefully you can see how this process can be used to make the UPDATE and DELETE methods to complete the set of CRUD functions. Its now time to replace your stub code with something more meaningful.
Before replacing your stub code, you need a data store for your users. Open the resources-cf.json file in the cloudformation folder for your stage and region. This file contains the shared resources CloudFormation stack that JAWS deployed when you created your project. Add the following to resource:
"myDynamoDBTable" : {
"Type" : "AWS::DynamoDB::Table",
"Properties" : {
"AttributeDefinitions": [
{
"AttributeName": "username",
"AttributeType": "S"
}
],
"KeySchema": [
{
"AttributeName": "username",
"KeyType": "HASH"
}
],
"ProvisionedThroughput": {
"ReadCapacityUnits": "5",
"WriteCapacityUnits": "5"
},
"TableName": {
"Fn::Join": [
"-",
[
"users",
{
"Ref": "aaDataModelStage"
}
]
]
}
}
}
In addition to creating the DynamoDB table, you also need to update the IAM role used by your AWS Lambda functions so they have permission to use the service. Find the IAMPolicyLambda policy in the same file and add the following extra statement to the policy document:
{
"Effect": "Allow",
"Resource": "*",
"Action": [
"dynamodb:*Item",
"dynamodb:Query",
"dynamodb:Scan"
]
}
You can deploy changes to the resources for your stage by running the following JAWS command:
jaws deploy resources dev
JAWS updates the CloudFormation stack for your resources, creating a DynamoDB table (named users-dev) and updating the IAM role used by your AWS Lambda functions. The only remaining task is to inject this dependency into your AWS Lambda function, so it can find the location to write the data. You can use the recently released API Gateway stage variables for this, or you can use an environment variable in JAWS.
Environment variables are set for each stage and region, allowing you to run the same code across multiple regions and environments and configure the code at runtime. In this case, for example, you can have a different users table for dev and production.
Open the aswm.json file for the GET function and modify the envVars section at the top of the file:
"lambda": {
"envVars": [
"TABLE_NAME",
"JAWS_STAGE"
],
This indicates to the framework to include environment variables called TABLE_NAME and JAWS_STAGE in the deployment package for your AWS Lambda function. Your code can access these as environment variables of the runtime:
// Export For Lambda Handler
module.exports.run = function(event, context, cb) {
return cb(null, action(event));
}
// Your Code
var action = function(event) {
const tableName = process.env.TABLE_NAME + "-" + process.env.JAWS_STAGE;
return {
message: "User requested: " + event.username,
table: tableName
};
};
The final task is to set the environment variable for the stage and region. To do this, use the following JAWS command:
jaws env set dev <region> TABLE_NAME users
JAWS maintains a file in S3 that contains the environment variables. You can now deploy your project again using the jaws dash command. Before packaging your code, JAWS pulls down the environment variables from S3 and includes them in your distribution.
At runtime, your code builds the name for the DynamoDB table by combining TABLE_NAME and the JAWS_STAGE environment variable. The JAWS_STAGE environment variable is maintained by the framework. If you test your method now in API Gateway, you should see a result similar to this:

Its now a simple task to update the logic of your AWS Lambda functions to read and write from the DynamoDB table.
After you finish building your sample app, you can tidy up by deleting the two CloudFormation stacks that JAWS created. You also need to manually delete the userManagement-dev API from API Gateway.
Summary
In this post I have shown you how to get started with JAWS, a framework for building microservices using API Gateway and AWS Lambda. As you build your project you can focus on producing exciting functionality since all of the infrastructure you need is fully managed. You can start to build your microservice application, leaving the heavy lifting to AWS and the organization of your project to JAWS.
If you have any questions or suggestions, please leave a comment below.
The Twelve Days of Lambda
Tim Wagner, AWS Lambda General Manager
The Twelve Days Hours* of Lambda
*It’s serverless, so it’s faster 😉
Stuffed with turkey or too many cookies? Not quite ready to face the relatives again? Here are some ideas to use your end of year or holiday downtime to explore serverless cloud computing with AWS Lambda instead. Each one comes with a blueprint to help you get started quickly. Enjoy!
- First step. Use the “hello, world” blueprint to create and test your first AWS Lambda function in the console…no IDE required. Changing the text to “a partridge in a pear tree” is optional but festive.
- Hook up a couple of event sources. Use the S3 blueprint to configure an Amazon S3 bucket to send events to a Lambda function. If you’re jolly and like to make lists, test it out with your personal “Naughty” and “Nice” categories. Then hook up SNS to stay informed of weather conditions and reindeer uptime.
- Automated log analysis. Was it three French hens or four? Figure it out by automatically analyzing CloudWatch logs with a Lambda function.
- Build a canary. Scheduled Lambda functions make it easy to do a task on a recurring schedule, and built-in integration with Amazon CloudWatch gives you an instant site checker just by typing in its URL.
- NoSQL database triggers. Data is golden, so hook up a Lambda function to Amazon DynamoDB to audit, transform, copy, or otherwise react to changes as they occur.
- Serverless test harness. Not sure whether your code is laying an egg? Find out with an automated test harness.
- Streaming data processing. Hook up Amazon Kinesis to a Lambda function and build a streaming data processor that can deliver business insights from data in real time.
- Access algorithms. Lambda functions make it easy to integrate with other services, so you can get insights from your shopping data through Splunk, handle party communication needs with Twilio’s advanced messaging capabilities, or figure out whether images are family-friendly with an advanced image processing algorithm from Algorithmia’s code library.
- Build an image processing service. Love Degas but wish you had thumbnail images of his dancers? Take a few minutes and build a scalable image processing service to help you out.
- Run processes or use native code. Make Lambda leap to do your bidding by firing up background processes, running other languages, or loading 3rd party code (even native code) as part of your Lambda function.
- Build a voice-enabled app. Don’t carol so much that your pipes are worn out so you can’t test your first Alexa app and use your Amazon Echo to trigger a Lambda function. Name your test app “presents” so you can run it by saying, “Alexa, open presents!”
- Mobile and IoT-enabled apps. Secretly hoping for an Arduino or Raspberry Pi but worried you’re getting socks? Take matters into your own hands and order your toys early, so you can hook them up to a Lambda function while the kids are playing with their own toys. Create a backend for a mobile or IoT app that’s backed by Amazon DynamnoDB with a single click. Drum away on your keyboard and have fun!
However you spend the end of 2015, the AWS Lambda team and I wish you and yours peace and happiness. The future of cloud computing has never been more exciting, and we look forward to another year of innovation and collaborating with all of you in 2016. Until next time, happy Lambda coding!
Amazon ECS improves console first run experience, ability to troubleshoot Docker errors
Today Amazon EC2 Container Service (ECS) added a new first run experience that streamlines getting your first containerized application running on ECS. Amazon ECS is a highly scalable, high performance container management service that supports Docker containers and allows you to easily run applications on a managed cluster of Amazon EC2 instances. Amazon ECS eliminates the need for you to install, operate, and scale your own cluster management infrastructure. With simple API calls, you can launch and stop Docker-enabled applications, query the complete state of your cluster, and access many familiar features like security groups, Elastic Load Balancing, EBS volumes, and IAM roles. You can start with the sample application or provide a Docker image and ECS will create all the resources required to run your containerized application on a cluster of Amazon EC2 instances.

For clusters created in the new first run experience, you can now scale EC2 instances up and down directly in the cluster’s ECS instances tab in the console. This gives you an easier way to manage your cluster’s capacity.

ECS also added task stopped reasons and task start and stop times. You can now see if a task was stopped by a user or stopped due to other reasons such as a failing Elastic Load Balancing health check, as well as the time the task was started and stopped.

Service scheduler error messages have additional information that describe why tasks cannot be placed in the cluster. These changes make it easier to diagnose problems.

These improvements came directly from your feedback. To get started with ECS, go to the console’s new first run wizard. And thank you for the input!
AWS Lambda sessions at re:Invent 2015 – Wrap up
Vyom Nagrani, Sr. Product Manager, AWS Lambda
Announcements
AWS Lambda announced four new features at re:Invent 2015
- Support for Python functions
- Increased function duration from 60 seconds to 300 seconds
- Function Versioning & Aliasing
- Scheduled functions (Cron) – Console only
You can read the details for these announcements here.
Breakout sessions
We had listed the AWS Lambda breakout sessions at re:Invent’15 earlier. Here is an easy reference to all videos and slide decks
- CMP301 – AWS Lambda and the Serverless Cloud [Video] [Slides]
- ARC308 – The Serverless Company Using AWS Lambda: Streamlining Architecture with AWS [Video] [Slides]
- MBL302 – Building Scalable, Serverless Mobile and Internet of Things Back Ends [Video] [Slides]
- BDT307 – Zero Infrastructure, Real-Time Data Collection, and Analytics [Video] [Slides]
- DEV203 – Using Amazon API Gateway with AWS Lambda to Build Secure and Scalable APIs [Video] [Slides]
- GAM401 – Build a Serverless Mobile Game with Amazon Cognito, Lambda, and DynamoDB [Video] [Slides]
- ARC201 – Microservices Architecture for Digital Platforms with AWS Lambda, Amazon CloudFront and Amazon DynamoDB [Video] [Slides]
- CMP403 – AWS Lambda: Simplifying Big Data Workloads [Video] [Slides]
- CMP407 – Lambda as Cron: Scheduling Invocations in AWS Lambda [Video] [Slides]
- DVO209 – JAWS: The Monstrously Scalable Serverless Framework – AWS Lambda, Amazon API Gateway, and More! [Video] [Slides]
Partners
We have a variety of partners that provide software that integrates with AWS Lambda. We announced a partnership with Algorithmia and Twilio as Code Library Partners for AWS Lambda. We also have new blueprints available for these partner integrations on the Lambda console. This adds to our existing integration with partners like CloudBees, Codeship, Zapier, and Splunk.
What’s next
AWS Lambda turns 1 today! AWS Lambda was announced on November 13th 2014 at re:Invent’14 as part of the Day 2 Keynote. A year later, we are as excited to be enabling new serverless architectures for building applications in the cloud. Our team is heads down working on the VPC functionality we pre-announced at re:Invent. We are only a few weeks away from enabling access to resources in a private VPC from your Lambda functions. We also plan to enable the API and CLI support for AWS Lambda scheduled functions.
Moving forward, we plan to add more language support for AWS Lambda, and expand availability to more AWS regions. We have a large number of other feature additions to Lambda on our roadmap for 2016, so stay tuned to the AWS Compute Blog and AWS Lambda Forums for updates!
Using Python in an AWS Lambda Function
Tim Wagner, AWS Lambda
REVISED: Python 2.7 is now a first-class language in AWS Lambda…no workarounds required!

