Custom Runtimes
on AWS Lambda
My experience doing Serverless PHP
bene@theodo.co.uk
Ben Ellerby
@EllerbyBen
Runtimes
@EllerbyBen
Runtimes
@EllerbyBen
@EllerbyBen
@EllerbyBen
<?php
echo "Not my choice!";
?/>
@EllerbyBen
404
RUNTIME NOT FOUND
@EllerbyBen
200
Custom Runtimes
@EllerbyBen
Runtime Interface
HTTP API for custom runtimes to receive invocation events from Lambda and send response data back within the Lambda execution environment.
@EllerbyBen
Runtime Interface
curl
"http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next"
-
Next Invocation
-
Invocation Response
-
Invocation Error
-
Initialization Error
@EllerbyBen
/runtime/invocation/next
-
Body: JSON Payload
-
Headers: Meta Data
Retrieves an invocation event.
https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html
GET
@EllerbyBen
.../invocation/AwsRequestId
/response
-
Body: Response
Sends an invocation response to Lambda.
https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html
POST
@EllerbyBen
.../invocation/AwsRequestId
/error
-
Body: Error Object
Sends an error response
https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html
POST
@EllerbyBen
Custom Runtime
A runtime is a program that runs a Lambda function's handler method when the function is invoked.
https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html
@EllerbyBen
Custom Runtime
bootstrap
-
Shell Script
-
Script in supported language
-
Binary file compiled on Amazon Linux
@EllerbyBen
bootstrap
-
Setup
-
Reading Handler Name (env)
-
Polling for invocations from API
-
Calling Handler with body
-
Posting response
@EllerbyBen
#!/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
https://docs.aws.amazon.com/lambda/latest/dg/runtimes-walkthrough.html
@EllerbyBen
runtime-tutorial
├ bootstrap
└ function.sh
https://docs.aws.amazon.com/lambda/latest/dg/runtimes-walkthrough.html
@EllerbyBen
bootstrap
handler
@EllerbyBen
bootstrap
handler
34M
@EllerbyBen
Layers
@EllerbyBen
Layers
@EllerbyBen
A layer is a ZIP archive that contains libraries, a custom runtime, or other dependencies. With layers, you can use libraries in your function without needing to include them in your deployment package.
https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html
@EllerbyBen
bootstrap
handler
handler
handler
@EllerbyBen
-
Reduce deployment package size.
-
Large dependency deployed less often.
-
Easier to manage when change rarely
-
Reduced code storage (75GB)
@EllerbyBen
https://aws.amazon.com/blogs/aws/new-for-aws-lambda-use-any-programming-language-and-share-common-components/
@EllerbyBen
layers:
php7:
path: layers/runtime
functions:
setup:
handler: hello
layers:
- { Ref: php7 }
@EllerbyBen
- Find the AMI of the correct AWS Linux: amzn-ami-hvm-.*-gp2
- Spin up a Large EC2 and ssh in.
- Follow the download and build steps for your language of choice.
- scp a zip down to local
- touch and chmod +x 'bootstrap'
- zip a runtime directory with the binary and bootstrap
@EllerbyBen
#!/opt/bin/php
<?php
// amzn-ami-hvm-2017.03.1.20170812-x86_64-gp2
function getNextRequest()
{
$client = new \GuzzleHttp\Client();
$response = $client->get('http://' . $_ENV['AWS_LAMBDA_RUNTIME_API'] . '/2018-06-01/runtime/invocation/next');
return [
'invocationId' => $response->getHeader('Lambda-Runtime-Aws-Request-Id')[0],
'payload' => json_decode((string) $response->getBody(), true)
];
}
function sendResponse($invocationId, $response)
{
$client = new \GuzzleHttp\Client();
$client->post(
'http://' . $_ENV['AWS_LAMBDA_RUNTIME_API'] . '/2018-06-01/runtime/invocation/' . $invocationId . '/response',
['body' => $response]
);
}
// This is the request processing loop. Barring unrecoverable failure, this loop runs until the environment shuts down.
do {
// Ask the runtime API for a request to handle.
$request = getNextRequest();
// Obtain the function name from the _HANDLER environment variable and ensure the function's code is available.
$handlerFunction = array_slice(explode('.', $_ENV['_HANDLER']), -1)[0];
require_once $_ENV['LAMBDA_TASK_ROOT'] . '/src/' . $handlerFunction . '.php';
// Execute the desired function and obtain the response.
$response = $handlerFunction($request['payload']);
// Submit the response back to the runtime API.
sendResponse($request['invocationId'], $response);
} while (true);
?>
@EllerbyBen
Would Bref not be Brief?
@EllerbyBen
Would Bref not be Brief
-
Documentation
-
PHP runtimes for AWS Lambda
-
Deployment tooling
-
PHP frameworks integration
https://bref.sh/docs/
@EllerbyBen
Any Runtime
But at what cost?
- More complex local dev env
- Managing lambda layer versions
- Updating the underlying language
- OS concerns (e.g. OpenSSL)
Conclusion
-
The custom runtime API let's you run any binary you want.
-
Lambda layers are a great way to share large dependencies, like your runtime and bootstrap.
-
Lambda layers reduce your deployment size.
@EllerbyBen
-
AWS Lambda is a very flexible tool, use it wisely.
serverless-transformation
@EllerbyBen
npm install -g sls-dev-tools
@EllerbyBen
[LIGHTNING] Custom Runtimes on AWS Lambda: My experience doing Serverless PHP - AWS User Group Meetup 2019
By Ben Ellerby
[LIGHTNING] Custom Runtimes on AWS Lambda: My experience doing Serverless PHP - AWS User Group Meetup 2019
Lightning talk given at the AWS Community Summit London 2019.
- 1,134