Crear custom runtime para PHP en AWS Lambda

Actualización 2019-06-27: Añadida extensión para MongoDB.

En el proyecto que estamos desarrollando, tenemos algunas funciones Lambda en Python, con las que no tenemos problemas (por ahora); las dependencias de estas funciones Python las gestionamos con pipenv.

Pero dado que el frontend está desarrollado en PHP, hay veces que necesitamos acceder a determinadas propiedades y funciones desde las funciones Lambda, y nos planteamos migrar o desarrollar nuevas funciones Lambda en PHP. Esto no era posible hasta que hace unos meses, AWS anunció el soporte de custom runtimes, que básicamente consiste en subir el ejecutable con un determinado nombre.

En resumen, lo que el post anterior viene a decir es que:

  • Tenemos que compilar PHP
  • Tenemos que crear un script llamado bootstrap que será al que llame Lambda; este script será el encargado de conectarse a un endpoint “mágico” (http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next), que nos devolverá un JSON con las propiedades de la petición a procesar. Este JSON se lo podemos pasar entonces a una función PHP de nuestra elección.

Un ejemplo de fichero bootstrap se encuentra en este gist:

El motivo de este post, es que AWS ha publicado una noticia diciendo que va a cambiar la imagen base donde se ejecutan los custom runtimes. Hasta ahora se basaba en una Amazon Linux 2017.03, pero a partir de ahora se van a ejecutar sobre una Amazon Linux 2018.03; esto puede causar que determinados runtimes dejen de funcionar, si están compilados con versiones determinadas de librerías del sistema operativo.

Ya tenía hecho un scriptillo para la versión antigua del runtime, pero como tenía que revisarlo, he decidido “ponerlo bonito” y publicarlo en el blog. Manos a la obra.

Lo primero que tenemos que hacer es determinar una imagen de Docker válida para la imagen donde se ejecutaría el runtime; en nuestro caso, según la documentación sería una AMI Linux 2018.03, y yendo al Docker Hub, podemos ver la versión de la imagen Docker equivalente.

Una vez que sabemos la imagen Docker, lanzamos un contenedor:

docker run -it --name php-lambda-layer --rm amazonlinux:2018.03.0.20190514 bash

Una vez lanzado, lo primero que haremos será actualizar el sistema e instalar una serie de librerías necesarias para compilar PHP y las dependencias que queremos:

cd
yum update -y
yum install autoconf bison gcc gcc-c++ libcurl-devel libxml2-devel openssl-devel git tree zip vim python36 python36-pip libicu-devel unzip diff libpng-devel -y

A continuación, nos bajamos la última versión de PHP y la compilamos, indicando los módulos que queremos incluir:

cd
git clone https://github.com/mongodb/mongo-php-driver.git mongodb
cd mongodb
git checkout 1.5.5
git submodule update --init

cd
mkdir ~/php-7-bin
curl -sL https://github.com/php/php-src/archive/php-7.3.6.tar.gz | tar -xz
cd ~/php-src-php-7.3.6
mv ~/mongodb ext/mongodb
./buildconf --force
./configure --prefix=/root/php-7-bin/ --with-openssl --enable-intl --enable-mbstring --with-pdo-mysql --with-curl --with-zlib --with-gd --enable-bcmath --enable-mongodb --without-pear
make
make install
~/php-7-bin/bin/php -v
~/php-7-bin/bin/php -m
rm -Rf ~/php-7-bin/{include,lib,php,var}
rm -Rf ~/php-7-bin/bin/{php-cgi,phpdbg}

Con los parámetros anteriores al compilar, tendremos PHP con los siguientes módulos:

Tras esto, nos bajamos composer; lo utilizaremos para utilizar la librería guzzlehttp/guzzle para facilitar la manera en la que obtenemos el evento y reportar el estado (y no estar peleando con llamadas a curl):

cd
curl -sS https://getcomposer.org/installer | ~/php-7-bin/bin/php

Lo siguiente sería crear un proyecto de composer e instalar la dependencia mencionada antes; al mismo tiempo, también nos bajamos el fichero bootstrap que será el punto de entrada de nuestro runtime.

mkdir ~/php-runtime
cd ~/php-runtime
curl -qL https://gist.github.com/okelet/afe16efea9b89ce90e4690dd752cb4ae/raw > bootstrap
chmod 755 bootstrap
~/php-7-bin/bin/php ~/composer.phar require guzzlehttp/guzzle

Por último, lo metemos todo en un fichero ZIP comprimido

rm -f ~/runtime.zip
cd ~/php-7-bin
zip -r ~/runtime.zip bin/php
cd ~/php-runtime
zip -r ~/runtime.zip .

La estructura final de este fichero ZIP es la siguiente:

├── bin
│   └── php
├── bootstrap
├── composer.json
├── composer.lock
└── vendor
    ├── autoload.php
    ├── composer
    ├── guzzlehttp
    ├── psr
    └── ralouphie

Antes de finalizar el contenedor, desde una shell externa, copiaremos el fichero generado:

docker cp php-lambda-layer:/root/runtime.zip .

Ahora subimos la capa (layer) a AWS:

aws lambda publish-layer-version --layer-name php-cake-amzlx201803 --zip-file fileb://runtime.zip --compatible-runtimes provided --region eu-west-1

Lo único que nos quedaría sería subir la función en PHP, indicando que queremos usar un custom runtime y añadirle este layer que hemos subido.

Hay que tener en cuenta que el script bootstrap hace un mapeo entre el directorio src y el namespace App, de modo que si especificamos App\Lambda\MyFunction::handler como handler (en la configuración de la función Lambda), buscaría un fichero src/Lambda/MyFunction.php, que debería contener una función estática handler, que aceptaría un único parámetro, que sería el evento; algo así como:

<?php
namespace App\Lambda;

require dirname(__DIR__) . '/../vendor/autoload.php';

class MyFunction
{
    public static function handler($event)
    {
        return "Hello " . $event["name"] . "!; full event is " . json_encode($event);
    }

    public static function runner() {
        return self::handler([
            "name" => "Foo",
            "email" => "no@reply.com",
            "groups" => [
                [
                    "id" => 1,
                    "name" => "admin"
                ],
                [
                    "id" => 8,
                    "name" => "bar"
                ]
            ]
        ]);
    }
}

La función runner es una función de ayuda para desarrollar en local, de tal forma que podamos indicar dentro de ella el evento al llamar a la función handler, ejecutándolo de la siguiente forma:

php -r "require 'src/Lambda/MyFunction.php' ; print_r(call_user_func('App\Lambda\MyFunction::runner'));"

Referencias:

comments powered by Disqus

Relacionado