02 May 2023
¿Querés usar PHP con capacidades asíncronas y serverless? En este post voy a implementar una aplicación con uno de mis frameworks favoritos, Slim, en AWS/Lambda, usando Bref para ejecutar Swoole como runtime.
El ecosistema de PHP goza de muy buena salud. Además de llevar muchísimos años en el mercado, en la actualidad, con la incorporación de implementaciones asíncronas como Swoole (u Openswoole, si preferís), Laravel Octane, Amphp, ReactPHP y otros tantos, la cosa es más prometedora aún. Como si todo esto fuera poco, está Bref, “The serverless PHP framework”, como reza su cuenta de Twitter. Bref es un framework con el que podemos montar aplicaciones PHP como funciones lambda en AWS.
En la web de Bref se muestran ejemplos para desplegar distintos tipos de aplicaciones en PHP/FPM como funciones AWS/Lambda.
¿Y Swoole?
Existe un post en dev.to donde se muestra cómo usar el runtime de Swoole como una capa Lambda (Lambda layer) para ejecutar una aplicación en PHP/Swoole en AWS/Lambda. Basandome en el mencionado artículo, en este post voy a desplegar en AWS/Lambda una aplicación en Slim que pueda disponer de las características asíncronas de Swoole.
Para seguir el post, deberás clonar el código que se encuentra alojado en github. El repositorio contiene una aplicación en Slim, con dos routes. Uno de los routes retorna un simple “hello”. El otro efectúa un request http a cada una de las urls definidas en una variable de entorno de Lambda. Para definir tu lista de urls, crea un archivo terraform.tfvars
con las urls que invocará la aplicación. Por ejemplo:
urls = [
"https://example1.com/some-endpoint",
"https://example2.com/some-endpoint",
"https://example3.com/some-endpoint",
]
Una vez que tenemos el repositorio, vamos a preparar el cli de Swoole, que es un ejecutable de PHP con Swoole incluido. Subiremos este ejecutable como una lambda layer. Para ello, descargamos uno de los releases del runtime de Swoole. En mi caso, lo hice con la versión 5.0.3 para linux.
wget https://github.com/swoole/swoole-src/releases/download/v5.0.3/swoole-cli-v5.0.3-linux-x64.tar.xz
tar -xf swoole-cli-v5.0.3-linux-x64.tar.xz
mkdir bin
mv swoole-cli bin
zip -r ./runtime.zip bootstrap bin cacert.pem
Hemos descargado un tar.xz con el último release de Swoole cli, lo hemos descomprimido y al ejecutable swoole-cli
resultante lo hemos puesto en una carpeta llamada bin
. Finalmente, creamos el archivo runtime.zip
con esta carpeta bin
y otros dos archivos que se descargaron al clonar el repositorio.
De acuerdo a la documentación de AWS, los contenidos de una capa Lambda se suben en un zip y son descomprimidos en la carpeta /opt
. Cuando Lambda extraiga los contenidos de la capa que creamos en esta sección, las ubicaciones de cada archivo será:
El archivo cacert.pem
no es estrictamente necesario. Lo he incluido porque en mi aplicación de ejemplo voy a ejecutar peticiones http usando Guzzle y quiero contar con la validación SSL en esos requests.
Lo siguiente será generar la carpeta vendor, que también será una capa de lambda:
composer install
zip -r ./vendor.zip vendor
Cuando AWS descomprima este zip, la carpeta vendor estará disponible en /opt/vendor
.
Una vez que tenemos los zips generados, vamos a subirlos a AWS usando Terraform. Para ejecutar este paso, tendrás que tener las credenciales de tu cuenta de AWS configuradas y Terraform instalado:
export AWS_ACCESS_KEY_ID="anaccesskey"
export AWS_SECRET_ACCESS_KEY="asecretkey"
terraform apply
Terraform nos pedirá confirmación para crear los siguientes recursos:
vendor
.Como salida, el comando arrojará el endpoint correspondiente a nuestra api gateway.
...
Outputs:
endpoint = "https://SOMEHASH.execute-api.us-east-1.amazonaws.com"
Podemos validar que nuestra aplicación está debidamente instalada ejecutando la ruta GET /hello
y ver que obtenemos respuesta:
curl -i https://SOMEHASH.execute-api.us-east-1.amazonaws.com/hello
HTTP/2 200
...
{"status":"Hello!"}
Cada vez que se ejecuta una función lambda en AWS, ésta recibe un evento. Los eventos pueden venir desde múltiples componentes de AWS. En este caso, los eventos que recibe nuestra función lambda, vienen de Api Gateway. Bref se encarga de convertir esos eventos en requests PSR y pasárselos a nuestra aplicación. Esto está definido en el archivo bootstrap
, que forma parte de nuestra primer capa lambda:
#!/opt/bin/swoole-cli
<?php
use Bref\Context\Context;
use Bref\Runtime\LambdaRuntime;
use Swoole\Coroutine;
require_once __DIR__ . '/vendor/autoload.php';
$runtime = LambdaRuntime::fromEnvironmentVariable('swoole-cli');
$handler = require $_ENV['LAMBDA_TASK_ROOT'] . '/handler.php';
Coroutine\run(static function () use ($runtime, $handler): void {
while (true) {
$runtime->processNextEvent($handler);
}
});
En el archivo bootstrap
es donde se instancia nuestra aplicación, que reside en handler.php
. Dentro de la corrutina en bootstrap
se indica que será el handler quien va a recibir el evento. En Bref, en la función processNextEvent del LambdaRuntime está la llamada al método invoke
en donde, si el handler implementa la interfaz RequestHandlerInterface
, tal como nuestra aplicación en Slim, será provisto con la capacidad de gestionar eventos de Lambda como requests PSR7.
Por cada invocación a nuestra aplicación, a través de la api gateway, se va a ejecutar nuestra función lambda. Esto ya es ventajoso para la versión PHP/FPM. Al ejecutarlo con Swoole, tenemos la ventaja adicional de poder efectuar operaciones concurrentes dentro de un mismo código.
Para demostrar un caso donde observar ventajas de la ejecución concurrente, en la aplicación de ejemplo implementé un route en donde se efectúan peticiones http a diferentes servicios. Si esto mismo lo tuviese que hacer con PHP/FPM, tendrían que ser peticiones secuenciales o múltiples invocaciones lambda.
Ejecutar aplicaciones de PHP en AWS/Lambda nos trae un mundo de posibilidades. En AWS/Lambda se paga por uso. Esto nos permite tener pequeñas aplicaciones a muy bajo costo en AWS. Y si nuestra aplicación comienza a recibir muchas peticiones, será capaz de escalar gracias a Lambda para responder a la demanda.
Combinando Swoole con AWS/Lambda, además de disponer de la escalabilidad, podemos sacar aún más partido al disponer de ejecuciones asíncronas dentro de nuestro código.
¡Tu mensaje fue recibido! Una vez que sea aprobado, estará visible para los demás visitantes.
Cerrar