Using img2lambda to publish your Serverless PHP layer

This interesting tweet by Clare Liguori came to my attention last week:

Img2lambda tweet2

This new img2lambda tool will take the layers of a Docker container and convert them to AWS layers for use in Lambda.

I poked around with Clare’s example and updated my lambda-php project in order to understand how it works. I also rewrote my runtime’s bootstrap to make it clearer.

The clever thing from my point of view is that you can build your PHP runtime layer locally using Docker and then publish the layers to AWS for use in your Lambda functions. This means you can now use the layer in different projects and have them all reference the same PHP runtime.

The magic of Docker

This is all done with the magic of Docker. Specifically, we create a Dockerfile that creates to containers:

  • A container to build the PHP binary
  • A container containing the layers that img2lambda will upload to AWS

Simplified, it looks like this:

# Build PHP in the Lambda container
FROM amazonlinux:2017.03.1.20170812 as builder

# PHP version of create
ARG ver="7.3.1"

RUN sed -i 's;^releasever.*;releasever=2017.03;;' /etc/yum.conf && \
    yum clean all && \
    yum install -y autoconf bison gcc gcc-c++ make libcurl-devel {etc}

RUN curl -sL https://github.com/php/php-src/archive/php-${ver}.tar.gz | tar -xvz && \
    cd php-src-php-${ver} && \
    ./buildconf --force && \
    ./configure --prefix=/opt/php/ --with-openssl --with-curl --{etc} && \
    make install && \
    /opt/php/bin/php -v

# copy php binary into /runtime/bin/php
RUN mkdir -p /runtime/bin && \
    cp /opt/php/bin/php /runtime/bin/php

# copy bootstrap files into /runtime
COPY src/* /runtime/


# Create runtime container for use with img2lambda
FROM lambci/lambda:provided as runtime

 COPY --from=builder /runtime /opt/

The first part download and compiles the PHP binary and puts it into /runtime/bin/php and also copies in the bootstrap files required to make the layer act as a Lambda runtime.

The second part simply copies all the files in /runtime in the first container into a new container. This new container containers a single layer which is our PHP runtime.

To build it we do:

$ docker build -t lambda-php-runtime .

The tag name can be anything.

Create the the layer in AWS

To create the AWS layer we use img2lambda:

$ img2lambda -i lambda-php-runtime:latest -r eu-west-2 -n lambda-php73

This tool will find the layer in the runtime container and upload it to eu-west2 and then store the identifier in output/layers.json which looks like this.

[
  "arn:aws:lambda:eu-west-2:66...06:layer:lambda-php73-sha256-e3c4...2e618b:1"
]

Using in Serverless Framework

You can take the ARN number and use it for your actions in serverless.yml like this:

functions:
  hello:
    handler: handler.hello
    layers: 
      - "arn:aws:lambda:eu-west-2:66...06:layer:lambda-php73-sha256-e3c4...2e618b:1"

This is the way that we can re-use our PHP runtime in all our actions across multiple projects.

Serverless also allows you to import data from JSON files into your serverless.yml file, so you can also do:

functions:
  hello:
    handler: handler.hello    # {file name}.{function name}. In this case: hello() in handler.php
    layers:
      ${file(../php-runtime/output/layers.json)}

This has the advantage that when you re-build and upload PHP runtime layer, your project picks it up, which could also be convenient.

Testing locally

One of the benefits of building the runtime in Docker is that we can run it locally for testing our Lambda function. As our runtime container inherits from lambci/lambda:provided, we get an environment that looks very much like Lambda itself.

To test locally, we use another container and inherits from our runtime one and copies in the PHP files for our function. The Dockerfile looks something like this:

FROM lambda-php-runtime as function

COPY handler.php /var/task/src/handler.php

We create our container:

$ docker build -t lambda-php-test .

And then we can test it:

$ docker run lambda-php-test handler.hello '{"name": "Rob"}'

To get the output:

START RequestId: 52fdfc07-2182-154f-163f-5f0f9a621d72 Version: $LATEST
{"msg":"hello from PHP 7.3.1","eventData":{"name":"world"}}
END RequestId: 52fdfc07-2182-154f-163f-5f0f9a621d72
REPORT RequestId: 52fdfc07-2182-154f-163f-5f0f9a621d72	Init Duration: 144.73 ms	Duration: 6.31 ms	Billed Duration: 100 ms	Memory Size: 1536 MB	Max Memory Used: 23 MB

Very convenient.

Summary

I really like the separation of publishing the PHP runtime layer from my project and the ability to reuse this layer in multiple projects is magic.

Ideally, I would like to be able to update the layer with a new patch release of PHP and use the ARN so that all my functions don’t need to be re-deployed. Maybe I’ll look at how that can be done at some point.

Have a play with my lambda-php project to see if this approach works for you. Also check out Bref for another way to run PHP on Lambda.

from Rob Allen’s DevNotes http://bit.ly/2Rw4Bc2
via IFTTT

Leave a Reply