Serverless PHP on AWS Lambda
Like, Simon Wardley, I think that serverless computing is an interesting space because the billing is granular (pay only when your code executes) and you don't need to worry about maintaining and provisioning servers or containers. So much so, that I maintain the Open Source PHP Runtime for Apache OpenWhisk which is available commercially as IBM Cloud Functions
There are other serverless providers, and AWS Lambda is the market leader, but until recently PHP support could most charitably described as cumbersome. That all changed at the end of 2018 with Lambda's new runtime API and support for layers.
Let's look at the practicalities of serverless PHP on Lambda with Serverless Framework.
TL;DR
The source code for a simple Hello World is in my lambda-php-sls-hello-world Github repository. Just follow the Notes section and you should be good to go.
PHP runtime
The runtime API allows for any runtime to be used with Lambda. In some ways it looks a bit like the way OpenWhisk runtimes work in that there's an HTTP API between the serverless platform and the runtime. One very obvious difference is that with Lambda, the runtime calls back to the platform to get its invocation data whereas OpenWhisk calls an endpoint that the runtime must implement. More details are in Michael Moussa's article on the AWS blog, which inspired my work.
To get back on track, we need a PHP runtime for Lambda! This will comprise the PHP binary, the code to invoke our PHP serverless function and a bootstrap file as required by the platform. We put these three things into a layer. Layers are re-usable across accounts, so I'm quite surprised that AWS doesn't provide a PHP one for us. Stackery do, but they aren't using PHP 7.3, so we'll build our own.
We'll put all the files in the layer/php directory in our project.
Building the PHP binary
We need a PHP binary that will run inside Lambda's containers. The easiest way to do this is to compile it on the same platform as Lambda, so we use EC2. Michael's article explains how to do it and so I turned those commands into a compile_php.sh script, so that I could copy it up to the EC2 instance, run it & then copy the binary back to my computer:
$ export AWS_IP=ec2-user@{ipaddress} $ export SSH_KEY_FILE=~/.ssh/aws-key.rsa
$ scp -i $SSH_KEY_FILE compile_php.sh $AWS_IP:doc/compile_php.sh $ ssh -i $SSH_KEY_FILE -t $AWS_IP "chmod a+x compile_php.sh && ./compile_php.sh 7.3.0" $ scp -i $SSH_KEY_FILE $AWS_IP:php-7-bin/bin/php layer/php/php
This makes it nicely repeatable and hopefully it will be fairly simple to update to newer versions of PHP.
Bootstrapping
As we are using the runtime API, we need a bootstrap file. This filename is required by Lambda and is responsible for invoking the function by making relevant API calls in a while loop.
Essentially, we need to sit in a loop and call the /next endpoint to find out what to invoke, invoke it and then send the response to the /response endpoint.
AWS provides an example in BASH using curl:
while true do # Get an event HEADERS="$(mktemp)" 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
(You don't need to understand this code in detail as the three comments explain the required process well enough!)
We want to do the same thing in PHP and while I could write it myself, Parikshit Agnihotry has already done so in
Truncated by Planet PHP, read more at the original (another 4439 bytes)