Despliega Laravel usando containers serverless con AWS Fargate ☁️☁️

JugueDev
6 min readDec 24, 2023

--

… usando IaC con CDK

En este post vamos a desplegar una aplicación PHP usando el framework Laravel utilizando contenedores, para ello haremos uso del servicio serverless de contenedores de AWS, Fargate. Adicionalmente desplegaremos toda la infraestructura utilizando IaC mediante CDK. 👨‍💻👨‍💻

Para ello dividiremos el trabajo en 3 partes, primero desplegaremos la infraestructura de red y los assets (como el repositorio de imagenes). En segundo lugar construiremos nuestra aplicación en laravel de manera local y la dockerizaremos. Por último subiremos esta imagen al repositorio de imágenes de containers de AWS, ECR, desde donde consumiremos la imagen para levantar los containers en la nube.

Network y Assets

Para poder desplegar nuestros contenedores dentro de la nube de AWS se necesita desplegar la infraestructura de red sobre la que se montaran nuestros servicios para ello desplegaremos un Stack dentro de CDK que se enfocará en desplegar estos recursos 👨‍🏫👨‍🏫:

import { Stack, StackProps, RemovalPolicy, CfnOutput } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as iam from 'aws-cdk-lib/aws-iam';



export interface NetworkStackProps extends StackProps {
env: { region: string; app: string; version: string; environment: string };
}

export class NetworkStack extends Stack {
public readonly ecs_app: ecs.ICluster;
public readonly ecr_repository: ecr.Repository;

constructor(scope: Construct, id: string, props: NetworkStackProps) {
super(scope, id, props);

const vpc = new ec2.Vpc(this, "vpc-test", {
maxAzs: 2,
vpcName: "vpc-name"
});

const appSG = new ec2.SecurityGroup(this, 'app-sg', {
vpc: vpc,
securityGroupName: "app-sg",
});

}
}

Adicionalmente, aprovecharemos este stack para poder desplegar el repositorio de imagenes de containers donde almacenaremos las imagenes de nuestra aplicación en Laravel y el cluster de ecs:

        const cluster = new ecs.Cluster(this, "cluster-test", {
clusterName: "cluster-name",
enableFargateCapacityProviders: true,
vpc: vpc,
});

this.ecr_repository = new ecr.Repository(this, "test-repo", {
repositoryName: "repository-name",
autoDeleteImages: true,
removalPolicy: RemovalPolicy.DESTROY
});

this.ecs_app = ecs.Cluster.fromClusterAttributes(this, 'ecs-cluster', {
clusterName: "cluster-name",
vpc: vpc,
securityGroups: [appSG]
});

new CfnOutput(this, 'repositoryUri', {
value: this.ecr_repository.repositoryUri,
});

Al desplegar los recursos obtendremos un uri del repositorio que usaremos para cargar las imagenes de los contenedores.

APP En Laravel

Una vez desplegada la infraestructura necesaria para subir las imagenes vamos a crear un proyecto en laravel, para ello es necesario tener tanto composer como laravel instalados en el entorno de php.

Creamos una carpeta y ejecutamos el siguiente comando:

laravel new app-prueba

Esto creará un directorio con todos para probar la aplicación en local se puede ejecutar el siguiente comando dentro de la carpeta que se creo:

php artisan serve

Dockerizamos la app

Para dockerizar la aplicación utilizaremos docker compose, crearemos un Dockerfile con el siguiente contenido:

# Dockerfile
FROM php:8.2-cli

RUN apt-get update -y && apt-get install -y libmcrypt-dev

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

WORKDIR /app
COPY /testing /app

RUN composer install

EXPOSE 8000
CMD php artisan serve --host=0.0.0.0 --port=8000

y un docker-compose.yaml :

version: '3'

services:
web:
container_name: laravel-dev
build:
dockerfile: Dockerfile
ports:
- '8000:8000'

Para construir la imagen del container utilizamos los siguientes comandos:

docker-compose build # Para construir la imagen
docker-compose up # Para probar la aplicación en el container local

Con la iamgen construida procedemos a subirla al repositorio creado en el paso anterior, para ello ejecutamos los siguientes comandos que se pueden encontrar en el repositorio creado.

Despues de pushear la imagen al repositorio tenemos todo listo para desplegar nuestra aplicación en Fargate.

Deploy Fargate

Para desplegar la aplicación en Fargate utilizaremos un segundo stack de cdk container-stack, el cual se encargará de desplegar un role de ejecución y un servicio usando Fargate. 🚀🚀

import { Construct } from 'constructs';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as iam from 'aws-cdk-lib/aws-iam';
import { RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib';

export interface ContainerStackProps extends StackProps {
env: { region: string; app: string; version: string; environment: string },
ecs_app: ecs.ICluster,
ecr_repository: ecr.Repository
}

export class ContainerStack extends Stack {

constructor(scope: Construct, id: string, props: ContainerStackProps) {
super(scope, id, props);

// Creamos un rol para asignarlo al ALB
const executionECSRole = new iam.Role(this, "ecs-execution-role-id", {
assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
roleName: "evs-execution-role",
description: "Rol de IAM para ejecutar Tasks de ECS.",
});

executionECSRole.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName(
'service-role/AmazonECSTaskExecutionRolePolicy',
));

const image = ecs.EcrImage.fromRegistry(props.ecr_repository.repositoryUri + ":latest");
const alb_fargate = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'alb-fargate', {
cluster: props.ecs_app,
taskImageOptions: {
image: image,
containerName: "container-name",
executionRole: executionECSRole,
containerPort: 8000
},
memoryLimitMiB: 512,
cpu: 256,
desiredCount: 2,
listenerPort: 80,
serviceName: "my-test-service",
publicLoadBalancer: true,
});

const fargate_service = alb_fargate.service
}
}

Para que funcione este stack requiere que le pasemos como parte de sus props el cluster y el repositorio creado en el stack anterior, esto se puede hacer a través del cdk-code.ts que está ubicado en bin desde donde se referencian los stacks creados:

#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { NetworkStack } from '../lib/network-stack';
import { ContainerStack } from '../lib/container-stack';
import {dev} from './environments';

const prefix = dev.app + "-" + dev.environment;


const app = new cdk.App();
const networkStack = new NetworkStack(app, prefix + "-network-stack" , {env: dev});
const containerStack = new ContainerStack(app, prefix + "-container-stack" , {
env: dev,
ecs_app: networkStack.ecs_app,
ecr_repository: networkStack.ecr_repository,
});

Desplegamos este segundo stack:

Y la aplicación estará online ejecutandosé en un container en la nube 🥳🥳🥳🥳:

NEXT STEPS

Hasta aquí el alcance de este post. Como siguientes pasos para poder completar esta aplicación quedarían pendientes añadir un dominio a nuestra aplicación para poder pegarle desde un url personalizado example.com. También añadir un certificado de seguridad con AWS ACM. 🤖🤖

REFERENCIAS

--

--

JugueDev

Solutions Architect | AWS x5 | Databricks x2 | Azure x1 | IoT | Machine Learning | Electronic Enginee