Warning You are probably in the wrong place.
If you are new to PHP on Lambda or Bref, check out bref.sh instead.
This project is a low-level internal piece of the Bref project. It contains the scripts to build the AWS Lambda layers and Docker images.
Thank you for diving into this very complex part of Bref.
If you are submitting a pull request to this repository, you probably want to test your changes:
- Build the Docker images and the Lambda layers (zip files) locally (to make sure it works).
- Run the test scripts.
- Publish the Lambda layers to your AWS account and test them in a real Lambda.
make
- Docker
- AWS CLI (if publishing layers)
- AWS credentials set up locally (if publishing layers)
You can build Docker images and Lambda layers locally:
make layers
The process takes about 4 minutes. It will create the Docker images on your machine, and generate the Lambda layer zip files in ./output
.
After building the images, run the automated tests:
make test
Note If automated tests fail, you can test layers manually using Docker. Check out the README in
tests/
.
You can build everything and upload Lambda layers to your AWS account. You will need to set up local AWS credentials. The following environment variables are recognized:
AWS_PROFILE
(optional): an AWS profile to use (instead of the default one).ONLY_REGION
(optional): if set, layers will only be published to this region, instead of all regions (useful for testing).
You can set those by creating a .env
file:
cp .env.example .env
# Now edit the .env file
# Then publish layers:
make upload-layers
You can also limit to ARM or X86 layers:
make -f cpu-x86.Makefile upload-layers
The published Lambda layers will be public (they are readonly anyway). You can find them in your AWS console (AWS Lambda service). Feel free to delete them afterwards.
If you ever need to check out the content of a layer, you can start a bash
terminal inside the Docker image:
docker run --rm -it --entrypoint=bash bref/php-80
Pro-tip: ldd
is a linux utility that will show libraries (.so
files) used by a binary/library. For example: ldd /opt/bin/php
or ldd /opt/bref/extensions/curl.so
. That helps to make sure we include all the libraries needed by PHP extensions in the layers.
Related: utils/lib-check
is a small utility-tool to check whether we're copying unnecessary .so
files into the layer (i.e. .so
files that already exist in Lambda).
The general idea is to copy php-81
into php-82
. Search/replace php-81
with php-82
, change PHP_VERSION in Makefile
, and adapt anything else if needed.
Check out utils/lambda-publish/Makefile
to add more regions.
To build Docker images, Lambda layers, publish layers to AWS (all regions) and publish Docker images to Docker Hub, the pipeline runs:
make everything
ldd
is a linux utility that will show libraries used by a binary e.g.ldd /opt/bin/php
orldd /opt/bref/extensions/curl.so
In a nutshell, a Lambda Layer is a zip
file. Its content is extracted to /opt
when a Lambda starts.
Anything we want to make available in AWS Lambda is possible by preparing the right files and packing them into a layer. To work properly, these files need to be compatible with the Lambda environment. AWS provides Docker images (e.g. public.ecr.aws/lambda/provided:al2-x86_64
) that replicate it.
/opt/ # where layers are unzipped/can add files
bin/ # where layers can add binaries
php # the PHP binary
php-fpm # the PHP-FPM binary (only for FPM layers)
lib/ # system libraries needed by Bref
bref/ # custom Bref files
extensions/ # PHP extensions
...
etc/php/conf.d/ # automatically loaded php.ini files
bref.ini
bootstrap.php # (for the function layer only)
php-fpm-runtime/ # the built-in FPM runtime
# TODO move to /opt/bref
bootstrap # entrypoint of the runtime
/var/runtime/
bootstrap # duplicated entrypoint, used when running in Docker
/var/task # the code of the Lambda function
php/conf.d/ # also automatically loaded php.ini files
The Dockerfile attempts at a best-effort to follow a top-down execution process for easier reading. It starts from
an AWS-provided Docker Image and installs PHP. Some standard files (such as the php binary) can already be
isolated into the /bref
folder. The use of multiple Docker Layers helps with investigations
because the developer can have a faster feedback loop by checking each step of the process incrementally instead
of trying to figure out why an entire build is failing.
The 2nd layer is the extensions
where all extensions are installed and isolated into the /bref
folder.
Reminder that ldd
is a linux utility that helps discover which files need isolating.
The 3rd layer is the isolation
layer where we'll start from the standard AWS-provided image all over again
(getting rid of any residual unnecessary file) and then copying /bref
into /opt
. PHP Configurations are
copied here as well.
The 4th layer is the function
layer where everything is packet together and the bootstrap
file is loaded.
The bref-internal-src
images (see layers/fpm) are used to load Bref
classes into the layer.
The 5th layer is zip-function
, where we get a small and fast Linux (Alpine) just to install and zip the entire
/opt
content. We use docker-compose volumes to map /tmp/bref-zip
from host to the container so that we can
zip everything and get the zipped file out of the container.
The 6th layer goes back to extensions
and start fpm-extension
. Here we're back at step 2 so that we can install
fpm
.
The 7th layer goes back to isolation
and start fpm
. It mimics steps 3th and 4th but for the FPM Layer.
Lastly, layer 8th zips FPM and pack everything ready for AWS Lambda.
Compiling PHP is a complex process for the average PHP Developer. It takes a fair amount of time
and can be cumbersome. Using remi-collet
as a PHP distributor greatly simplifies and help the
installation process. The code is more readable and approachable. Remi also distribute releases
even before they are announced on the PHP Internal Mailing List by the Release Manager.
The biggest downside is that we're no longer in control of compiling new releases whenever we want.
But for x86 architecture, we see that using remi-collet will not be a problem for this.
We can see the impact of this on arm64 (Graviton2 Support). Since remi-collet doesn't distribute arm64,
we may have to rely on amazon-linux-extras
, which is 5 months behind (as of this writing) with PHP 8.0.8.
While developing a new Runtime, the first attempt was to provide an "alpine-like" Bref Layer: only the PHP binary and no extension (or minimal extensions) installed. This turned out to slow down the process by a big factor because every layer needs to be compiled for multiple PHP versions and deployed across 21 AWS Region (at this time). Except for php-intl, most extensions are extremely small and lightweight. The benefits of maintaining a lightweight layer long-term didn't outweight the costs at this time.
Before landing on the current architecture, there was several attempts (7 to be exact) on a back-and-forth between more environment variables vs more repetitive code. Environment variables grows complexity because they require contributors to understand how they intertwine with each other. We have layers, php version and CPU architecture. A more "reusable" Dockerfile or docker-compose requires a more complex Makefile. In contrast, a simpler and straight-forward Makefile requires more code duplication for Docker and Docker Compose. The current format makes it so that old PHP layers can easily be removed by dropping an entire folder and a new PHP Version can be added by copying an existing folder and doing search/replace on the PHP version. It's a hard balance between a process that allows parallelization, code reusability and readability.
It would have been ideal to be able to upload the layers to a single S3 folder and then "publish" a new layer by pointing it to the existing S3Bucket/S3Key. However, AWS Lambda does not allow publishing layers from a bucket in another region. Layers must be published on each region individually.
Parallelization of all regions at once often leads to network crashing. The strategy applied divides AWS regions in chunks (7 to be precise) and tries to publish 7 layers at a time. AWS CodeBuild LARGE instance has 8 vCPU, so publishing 7 layers at a time should go smooth.
AWS CodeBuild is preferred for publishing the layers because the account that holds the layers has no external access. It is dedicated exclusively for having the layers only and only Matthieu Napoli has access to it. GitHub Actions require exposing access to an external party. Using AWS CodeBuild allows us to use IAM Assume Role so that one "Builder Account" can build the layers and then cross-publish them onto the "Layer Account". The assume role limitations can be seen on aws/access.yml
The tests
folder contains multiple PHP scripts that are executed inside a Docker container that Bref builds.
These scripts are suppose to ensure that the PHP version is expected, PHP extensions are correctly installed and
available and PHP is correctly configured. Some acceptance tests uses AWS Runtime Interface Emulator (RIE) to
test whether a Lambda invocation is expected to work.