Custom Runtimes

on AWS Lambda

My experience doing Serverless PHP

bene@theodo.co.uk
Ben Ellerby

@EllerbyBen

@EllerbyBen

serverless-transformation

@EllerbyBen

@EllerbyBen

Serverless

What is this Serverless thing?

  • Architectural movement
    • Developers send application code which is run by the cloud provider in isolated containers abstracted from the developer.
    • Use 3rd party services used to manage backend logic and state (e.g. Firebase, Cognito)
  • A framework with the same name

 

@EllerbyBen

Why Serverless?

💰 Cost reduction

👷‍♂️ #NoOps

💻 Developers focus on delivering                   business value

📈 More scalable

🌳 Greener

@EllerbyBen

Not just Lambda (FaaS)

Lambda

S3

Dynamo

API Gateway

Compute

Storage

Data

API Proxy

Cognito

Auth

SQS

Queue

EventBridge

Event Bus

Power and Flexibility 

@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
cd $LAMBDA_TASK_ROOT
./node-v11.1.0-linux-x64/bin/node
runtime.js
https://docs.aws.amazon.com/lambda/latest/dg/runtimes-walkthrough.html

@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

  1. Find the AMI of the correct AWS Linux: amzn-ami-hvm-.*-gp2
  2. Spin up a Large EC2 and ssh in.
  3. Follow the download and build steps for your language of choice.
  4. scp a zip down to local
  5. touch and chmod +x 'bootstrap'
  6. zip a runtime directory with the binary and bootstrap
3

@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

@EllerbyBen

Local Dev

bootstrap

handler

local bootstrap

web

server

@EllerbyBen

Works on my cloud

@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

@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

@EllerbyBen

Custom Runtimes on AWS Lambda: My experience doing Serverless PHP - AWS User Group Meetup 2019

By Ben Ellerby

Custom Runtimes on AWS Lambda: My experience doing Serverless PHP - AWS User Group Meetup 2019

Talk given at the React-Native-London meetup.

  • 1,002