Tutorial: AWS Lambda with API Gateway

Set up a function with an API Gateway trigger, then break it.

This tutorial covers how to set up a Lambda function and configure an API Gateway trigger to invoke it. This includes instrumenting the function code to be compatible with API Gateway and how to detect errors.

We will use a function that is capable of consuming much CPU or much memory depending on the parameters passed to it. We can use this capability to break it by making it timeout or exceed available memory. The function implements the Sieve of Eratosthenes, an ancient algorithm for calculating prime numbers up to certain limit. By configuring the maximum prime number limit we can control how much memory is consumed, since the Sieve uses an in-memory data structure in its calculation. We can also configure it to use more CPU by having it repeat the calculation many times.

Steps

  1. Visit the Lambda section of the AWS Console and click Create a Lambda function.
  2. Choose Blank Function on the “Select blueprint” page.
  3. Choose API Gateway on the “Configure triggers” page.
  4. Provide a name for your new API, such as “Eratosthenes”.
  5. -> Leave the Deployment stage as “prod”.
  6. -> Set Security to “Open”. This will enable your API to be invoked via HTTP without security credentials.
  7. Click Next.
  8. Provide a name for your function on the “Configure function” page, such as Eratosthenes.
  9. -> For the Runtime choose Python 2.7.
  10. -> Choose Edit code inline for the Lambda function code.
  11. -> Copy and paste the contents of the file eratosthenes_lambda.py into the text box.
  12. -> Choose Create new role from template(s) in the “Lambda function handler” section. Enter a name for this role such as lambdaExecutionRole.
  13. -> Choose 128 for “Memory (MB)” in the “Advanced settings” section, and 30 sec for “Timeout”. API Gateway imposes a 30 second timeout which is the reason for choosing that.
  14. -> Choose Next.
  15. Choose Create function on the Review page.
  16. You should see a “Congratulations!” confirmation message. On that page you should see the full URL to your new API endpoint. 
    Example: https://abcdefghij.execute-api.us-west-2.amazonaws.com/prod/eratosthenes 
    where "abcdefghij" is a token unique to your API
  17. Click the Actions and choose Configure test event.
  18. Under Input test event enter the following code: 
    { "queryStringParameters": { "max": 1000000, "loops": 1 } } 
    The parameter “max” is the limit for how high the function will search for prime numbers. The “loops” parameter controls how many times the function will repeat the prime number calculation (to consume more CPU without consuming more memory).
  19. Click Save and test. This will test the function directly, without invoking the API Gateway.
  20. Your function should now execute (it will take several seconds) then show the following output: 
    { 
    "body": "{\"durationSeconds\": 5.48261809349, \"max\": 1000000, \"loops\": 1}", 
    "headers": {"Content-Type": "application/json"}, 
    "statusCode": 200 
    }
  21. It should also show the following logs: 
    START RequestId: 23f39528-db63-11e6-a488-013190970ce0 Version: $LATEST 
    looping 1 time(s) 
    Highest 3 primes: 999983, 999979, 999961 
    END RequestId: 23f39528-db63-11e6-a488-013190970ce0 
    REPORT RequestId: 23f39528-db63-11e6-a488-013190970ce0 Duration: 5484.17 ms Billed Duration: 5500 ms Memory Size: 128 MB Max Memory Used: 65 MB
  22. Now let’s test using the API Gateway. This curl command will invoke the function with parameters to calculate all primes ≤1M and to do execute that calculation only once. Replace abcdefghij with the token from your API endpoint.
    curl https://abcdefghij.execute-api.us-west-
    2.amazonaws.com/prod/eratosthenes?max=1000000&loops=1
  23. That curl command should take several seconds to execute then show output like this:
    {"durationSeconds": 5.48421978951, "max": 1000000, "loops": 1}
  24. Your Lambda function with API Gateway trigger is now working!

Now Let’s Break It

Too Much Memory Consumption

If we increase our target to calculate prime numbers to 2.5M, that will be enough to exceed the function’s memory consumption limits (assuming you configured the function with 128 MB of memory), which will bring about an error in Lambda. Replace abcdefghij with the token in your API endpoint.

curl -i https://abcdefghij.execute-api.us-west-
2.amazonaws.com/prod/eratosthenes-128?max=2500000&loops=1

Output:

HTTP/1.1 502 Bad Gateway
Content-Type: application/json
Content-Length: 36
Connection: keep-alive
Date: Fri, 27 Jan 2017 19:54:20 GMT
x-amzn-RequestId: 60e621e9-e4ca-11e6-9341-95f32c508002
X-Cache: Error from cloudfront
Via: 1.1 e1540eac963dc07d5d86b546d341af92.cloudfront.net (CloudFront)
X-Amz-Cf-Id: lY5dEKs0tFGq85h7zhmCGMpmMmGXaP63E1-5PaZziONeoo5N1qTqaA==
{"message": "Internal server error"}

Note that the output didn’t tell us that the problem was a Lambda error. The API Gateway returned a 502 which means that it didn’t understand the output returned by Lambda. API Gateway expects to see a json map with keys “body”, “headers”, and “statusCode”. Instead it received {“message”: “Internal server error”} so it returned a 502.

If logging is turned on for the API Gateway, the following error message will appear in the logs. See “Turning on Logging” below.

Execution failed due to configuration error: Malformed Lambda proxy response

Timeout

If we increase the number of loops to 4 (and keep max at 1M), that will be enough to exceed the API Gateway timeout of 30 seconds while staying within memory limits (these numbers assume you chose 128 MB for the function’s memory size). Replace abcdefghij with the token in your API endpoint.

curl -i https://abcdefghij.execute-api.us-west-
2.amazonaws.com/prod/eratosthenes-128?max=1000000&loops=4

Output:

HTTP/1.1 504 Gateway Timeout
Content-Type: application/json
Content-Length: 41
Connection: keep-alive
Date: Fri, 27 Jan 2017 20:30:41 GMT
x-amzn-RequestId: 67952699-e4cf-11e6-bff3-37077da4f916
X-Cache: Error from cloudfront
Via: 1.1 ae7118021d10200685649b5e21d1bf9f.cloudfront.net (CloudFront)
X-Amz-Cf-Id: 5Lyb9GkLAv2lrSYYl9d0cAnCapbWNhLDzis9t2h7tVknZCOsS7whbQ==
{"message": "Endpoint request timed out"}

This time the output did tell us the reason for the error. Note that the HTTP status code is 504 this time. API Gateway imposes a 30 second timeout and the Lambda function exceeded that.

If logging is turned on for the API Gateway, the following error message will appear in the logs:

Execution failed due to a timeout error

API Gateway Compatibility

The AWS API Gateway expects Lambda functions to return certain output. You must code your function accordingly. This is the structure it expects:

{
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": "body_text_goes_here"
}

In the case of the Eratosthenes function, we actually return json as the body so we have to use backslashes to escape our json-within-json:

{
"statusCode": 200,
"headers": {"Content-Type": "application/json"},
"body": "{\"durationSeconds\": 8.345243, \"max\": 1000000, \"loops\": 1}"
}

API Gateway will read the “body” element and return it as the body of the response:

{"durationSeconds": 8.345243, "max": 1000000, "loops": 1}

Turning On Logging

Logging for Lambda functions invoked by the API Gateway must be configured in the API Gateway section of the AWS Console.

  1. Under “APIs” in the left nav, choose Eratosthenes.
  2. Now choose Stages under “Eratosthenes” in the left nav.
  3. Click prod in the middle pane. You need to click the actual word “prod” rather than clicking the arrow next to it (tricky UI).
  4. In the main pain, you should be looking at the Settings tab.
  5. Under “CloudWatch Settings”, check the box Enable CloudWatch Logs and click Save Changes.
  6. -> You can leave the Log level set to “ERROR”.
  7. -> It is not necessary to check the checkbox Log full requests/responses data. If you check that, the logs will be very verbose.
  8. You may need to wait a few minutes before the logs start showing up.

To view your logs, visit the CloudWatch section of the AWS Console and choose Logs in the left nav. Look for the Eratosthenes log group.

More Fun

The following Github repo (jconning/lambda-cpu-cost) builds on the concepts in this tutorial by creating four Lambda functions, each with different memory size (128, 256, 512 and 1024 MB), so that the prime number calculation can be tested with various amounts of CPU power. A test harness is used to run each function many times concurrently. You can follow the “How to run it” section to try various settings of concurrency, number of executions, number of loops and prime number limit.