AWS Lambda
Developer Guide

Tutorial – Publishing a Custom Runtime

In this tutorial, you create a Lambda function with a custom runtime. You start by including the runtime in the function's deployment package. Then you migrate it to a layer that you manage independently from the function. Finally, you share the runtime layer with the world by updating its resource-based permissions policy.

Prerequisites

This tutorial assumes that you have some knowledge of basic Lambda operations and the Lambda console. If you haven't already, follow the instructions in Getting Started to create your first Lambda function.

To follow the procedures in this guide, you will need a command line terminal or shell to run commands. Commands are shown in listings preceded by a prompt symbol ($) and the name of the current directory, when appropriate:

~/lambda-project$ this is a command this is output

For long commands, an escape character (\) is used to split a command over multiple lines.

On Linux and macOS, use your preferred shell and package manager. On Windows 10, you can install the Windows Subsystem for Linux to get a Windows-integrated version of Ubuntu and Bash.

You need an IAM role to create a Lambda function. The role needs permission to send logs to CloudWatch Logs and access the AWS services that your function uses. If you don't have a role for function development, create one now.

To create an execution role

  1. Open the roles page in the IAM console.

  2. Choose Create role.

  3. Create a role with the following properties.

    • Trusted entityLambda.

    • PermissionsAWSLambdaBasicExecutionRole.

    • Role namelambda-role.

    The AWSLambdaBasicExecutionRole policy has the permissions that the function needs to write logs to CloudWatch Logs.

Create a Function

Create a Lambda function with a custom runtime. This example includes two files, a runtime bootstrap file, and a function handler. Both are implemented in Bash.

The runtime loads a function script from the deployment package. It uses two variables to locate the script. LAMBDA_TASK_ROOT tells it where the package was extracted, and _HANDLER includes the name of the script.

Example bootstrap

#!/bin/sh set -euo pipefail # Initialization - load function handler source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh" # Processing while true do HEADERS="$(mktemp)" # Get an event EVENT_DATA=$(curl -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next") REQUEST_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2) # Execute the handler function from the script RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) "$EVENT_DATA") # Send the response curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$REQUEST_ID/response" -d "$RESPONSE" done

After loading the script, the runtime processes events in a loop. It uses the runtime API to retrieve an invocation event from Lambda, passes the event to the handler, and posts the response back to Lambda. To get the request ID, the runtime saves the headers from the API response to a temporary file, and reads the Lambda-Runtime-Aws-Request-Id header from the file.

Note

Runtimes have additional responsibilities, including error handling, and providing context information to the handler. For details, see Building a Custom Runtime.

The script defines a handler function that takes event data, logs it to stderr, and returns it.

Example function.sh

function handler () { EVENT_DATA=$1 echo "$EVENT_DATA" 1>&2; RESPONSE="Echoing request: '$EVENT_DATA'" echo $RESPONSE }

Save both files in a project directory named runtime-tutorial.

runtime-tutorial ├ bootstrap └ function.sh

Make the files executable and add them to a ZIP archive.

runtime-tutorial$ chmod 755 function.sh bootstrap runtime-tutorial$ zip function.zip function.sh bootstrap adding: function.sh (deflated 24%) adding: bootstrap (deflated 39%)

Create a function named bash-runtime.

runtime-tutorial$ aws lambda create-function --function-name bash-runtime \ --zip-file fileb://function.zip --handler function.handler --runtime provided \ --role arn:aws:iam::123456789012:role/lambda-role { "FunctionName": "bash-runtime", "FunctionArn": "arn:aws:lambda:us-west-2:123456789012:function:bash-runtime", "Runtime": "provided", "Role": "arn:aws:iam::123456789012:role/lambda-role", "Handler": "function.handler", "CodeSize": 831, "Description": "", "Timeout": 3, "MemorySize": 128, "LastModified": "2018-11-28T06:57:31.095+0000", "CodeSha256": "mv/xRv84LPCxdpcbKvmwuuFzwo7sLwUO1VxcUv3wKlM=", "Version": "$LATEST", "TracingConfig": { "Mode": "PassThrough" }, "RevisionId": "2e1d51b0-6144-4763-8e5c-7d5672a01713" }

Invoke the function and verify the response.

runtime-tutorial$ aws lambda invoke --function-name bash-runtime --payload '{"text":"Hello"}' response.txt { "StatusCode": 200, "ExecutedVersion": "$LATEST" } runtime-tutorial$ cat response.txt Echoing request: '{"text":"Hello"}'

Create a Layer

To separate the runtime code from the function code, create a layer that only contains the runtime. Layers let you develop your function's dependencies independently, and can reduce storage usage when you use the same layer with multiple functions.

Create a layer archive that contains the bootstrap file.

runtime-tutorial$ zip runtime.zip bootstrap adding: bootstrap (deflated 39%)

Create a layer with the publish-layer-version command.

runtime-tutorial$ aws lambda publish-layer-version --layer-name bash-runtime --zip-file fileb://runtime.zip { "Content": { "Location": "https://awslambda-us-west-2-layers.s3.us-west-2.amazonaws.com/snapshots/123456789012/bash-runtime-018c209b...", "CodeSha256": "bXVLhHi+D3H1QbDARUVPrDwlC7bssPxySQqt1QZqusE=", "CodeSize": 584, "UncompressedCodeSize": 0 }, "LayerArn": "arn:aws:lambda:us-west-2:123456789012:layer:bash-runtime", "LayerVersionArn": "arn:aws:lambda:us-west-2:123456789012:layer:bash-runtime:1", "Description": "", "CreatedDate": "2018-11-28T07:49:14.476+0000", "Version": 1 }

This creates the first version of the layer.

Update the Function

To use the runtime layer with the function, configure the function to use the layer, and remove the runtime code from the function.

Update the function configuration to pull in the layer.

runtime-tutorial$ aws lambda update-function-configuration --function-name bash-runtime \ --layers arn:aws:lambda:us-west-2:123456789012:layer:bash-runtime:1 { "FunctionName": "bash-runtime", "Layers": [ { "Arn": "arn:aws:lambda:us-west-2:123456789012:layer:bash-runtime:1", "CodeSize": 584, "UncompressedCodeSize": 679 } ] ... }

This adds the runtime to the function in the /opt directory. Lambda uses this runtime, but only if you remove it from the function's deployment package. Update the function code to only include the handler script.

runtime-tutorial$ zip function-only.zip function.sh adding: function.sh (deflated 24%) runtime-tutorial$ aws lambda update-function-code --function-name bash-runtime --zip-file fileb://function-only.zip { "FunctionName": "bash-runtime", "CodeSize": 270, "Layers": [ { "Arn": "arn:aws:lambda:us-west-2:123456789012:layer:bash-runtime:7", "CodeSize": 584, "UncompressedCodeSize": 679 } ] ... }

Invoke the function to verify that it works with the runtime layer.

runtime-tutorial$ aws lambda invoke --function-name bash-runtime --payload '{"text":"Hello"}' response.txt { "StatusCode": 200, "ExecutedVersion": "$LATEST" } runtime-tutorial$ cat response.txt Echoing request: '{"text":"Hello"}'

Update the Runtime

To log information about the execution environment, update the runtime script to output environment variables.

Example bootstrap

#!/bin/sh set -euo pipefail echo "## Environment variables:" env # Initialization - load function handler source $LAMBDA_TASK_ROOT/"$(echo $_HANDLER | cut -d. -f1).sh" ...

Create a second version of the layer with the new code.

runtime-tutorial$ zip runtime.zip bootstrap updating: bootstrap (deflated 39%) runtime-tutorial$ aws lambda publish-layer-version --layer-name bash-runtime --zip-file fileb://runtime.zip

Configure the function to use the new version of the layer.

runtime-tutorial$ aws lambda update-function-configuration --function-name bash-runtime \ --layers arn:aws:lambda:us-west-2:123456789012:layer:bash-runtime:2

Share the Layer

Add a permission statement to your runtime layer to share it with other accounts.

runtime-tutorial$ aws lambda add-layer-version-permission --layer-name bash-runtime --version-number 2 \ --principal "*" --statement-id publish --action lambda:GetLayerVersion { "Statement": "{\"Sid\":\"publish\",\"Effect\":\"Allow\",\"Principal\":\"*\",\"Action\":\"lambda:GetLayerVersion\",\"Resource\":\"arn:aws:lambda:us-west-2:123456789012:layer:bash-runtime:2\"}", "RevisionId": "9d5fe08e-2a1e-4981-b783-37ab551247ff" }

You can add multiple statements that each grant permission to a single account, accounts in an organization, or all accounts.

Clean Up

Delete each version of the layer.

runtime-tutorial$ aws lambda delete-layer-version --layer-name bash-runtime --version-number 1 runtime-tutorial$ aws lambda delete-layer-version --layer-name bash-runtime --version-number 2

Because the function holds a reference to version 2 of the layer, it still exists in Lambda. The function continues to work, but functions can no longer be configured to use the deleted version. If you then modify the list of layers on the function, you must specify a new version or omit the deleted layer.

Delete the tutorial function with the delete-function command.

runtime-tutorial$ aws lambda delete-function --function-name bash-runtime