Skip to content

LocalStack

AWS Local es posible

Si te has preguntado si usar AWS Localmente es posible, existen algunos mecanismos para poder probar ciertos componentes localmente mas no productivos. LocalStack provee un entorno local y simulado de AWS facilitando un numero interesante de caracteristicas para desarrollo en tu máquina o red privada, es decir, sin consumir recursos de AWS en tu etapa de desarrollo.

Ten presente que lo que hace es simular ciertos servicios de AWS sin que esto sustituya una operación productiva. Sin embargo, esto permite replicar nuestros ambientes para buscar tener una alta consistencia en el despliegue. Por lo que podrías usar la nube solo para el entorno productivo, dependiendo de los servicios que implementes.

LocalStack posibilita bajo la versión OpenSource usar los siguietes servicios:

  • ACM
  • API Gateway (v1)
  • CloudFormation
  • CloudWatch
  • CloudWatch Logs
  • DynamoDB
  • DynamoDB Streams
  • ElasticSearch Service (OpenSearch)
  • EventBridge (CloudWatch Events)
  • IAM
  • Kinesis
  • Kinesis Data Firehose
  • KMS
  • Lambda
  • Route53
  • S3
  • SecretsManager
  • SES (v1)
  • SNS
  • SQS
  • SSM
  • StepFunctions
  • STS

En versión comunitaria, no se encuentran disponibles servicios como RDS, EC2, ECS, EKS, EFS, ElastiCache, Cognito, Amplify, CloudFront y SSM (pues hacen parte de versión comercial).
En el caso de Cognito se puede usar directamente la nube AWS (mezclando LocalStack).
En el caso de Amplify también se puede usar la nube de AWS (cuidando costos respectivos si quieres conservar capa gratuita)

Instalación de LocalStack

Para instalar LocalStack se puede descargar el paquete, por ejemplo ejecutando:

bash
python -m pip install localstack
localstack --version

Esto instalará una CLI usando Python3 (por lo que debe estar previamente instalado). Para macOS se usa python3.
La última línea simplemente visualiza la versión buscando verificar su instalación.

Para iniciar el servicio se ejecutaría:

bash
localstack start

stop se usa para detener el servicio

Y para verificar los servicios disponibles se usaría:

bash
localstack status services

Alternativa de instalación con Docker

Para instalar mediante Docker se ejecutaría lo siguiente:

bash
docker run --rm -it -p 4566:4566 localstack/localstack:3.8.0

Esto inicializa el servicio en la dirección: localhost:4566
De hecho, se puede verificar consultando con un navegador la dirección: localhost:4566/health

Puede ser que una manera alterna de gestionar la instalación sea usando los siguientes comandos:

bash
docker pull docker.io/localstack/localstack:3.8.0
docker run -d -p 4566:4566 --name localstack localstack/localstack:3.8.0
docker logs localstack

Podemos probar la disponiblidad del servicio S3 ejecutando desde otra terminal lo siguiente:

bash
aws --endpoint-url http://localhost:4566 s3 ls

aws (CLI) debe estar instalado previamente, por ejemplo, para Windows se descarga desde enlace en AWS.

Para acceder al contenedor de modo interactivo se podría ejecutar:

bash
docker exec -u root -it localstack bash
./bin/localstack status services

La útima línea se ejecuta dentro de la instancia para verificar los servicios disponibles.

Alternativa usando Docker Compose

Si se desea utilizar una imágen mas ajustada es posible usar Docker Compose, por ejemplo, estableciendo un archivo docker-compose.yml (dentro de una carpeta de proyecto) con un contenido como el siguiente:

yaml
version: "3.8"

services:
  localstack:
    container_name: localstack
    image: localstack/localstack:3.8.0
    ports:
      - "127.0.0.1:4566:4566"
      - "127.0.0.1:4510-4559:4510-4559"
    environment:
      - SERVICES=serverless,cognito,sqs,sns,cloudformation
      - DEBUG=1
      - LAMBDA_EXECUTOR=docker
      - DOCKER_HOST=unix:///var/run/docker.sock
      - DATA_DIR=/tmp/localstack/data
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"

Para armar nuestro contenedor debemos estar ubicados en la carpeta del proyecto que contiene el archivo docker-compose.yml y ejecutamos:

bash
docker-compose up

Se puede verificar consultando con un navegador la dirección: localhost:4566/health

Contexto sobre la configuración

Usando la CLI de LocalStack se podría ejecutar el comando anterior usando awslocal s3 ls. Para ello se instala lo siguiente:

bash
pip install awscli-local

Para macOS se usa pip3.

Además de tener instalada la CLI de AWS, debe estar configurada. Por ejemplo, usando:

bash
aws configure --profile default

Al ejecutar este comando se debe indicar el valor test en ambas claves de acceso (o variables de acceso) y la region us-east-1
Podría también definirse un perfil local y aplicarlo: aws configure --profile local

Ejemplo creando un Bucket S3

Para una mejor ilustración podemos crear un bucket de S3 y verficarlo, usando por ejemplo, los siguientes comandos:

bash
awslocal s3api create-bucket --bucket my-bucket
awslocal s3api list-buckets

Con primer comando creamos un bucket y con el segundo lo listamos para verificarlo

Podemos verificar desde el navegador consultando la dirección: http://s3.localhost.localstack.cloud:4566/

Ejemplo creando una Lambda

Para crear una Lambda se pueden usar comandos con un Script como el siguiente:

bash
#!/bin/bash

LAMBDA_FUNCTION_NAME="my-lambda"

zip -r $LAMBDA_FUNCTION_NAME.zip *.js package.json node_modules

awslocal lambda get-function --function-name $LAMBDA_FUNCTION_NAME > /dev/null 2>&1

if [ $? -eq 0 ]; then
  echo "La función Lambda ya existe. Eliminándola..."
  awslocal lambda delete-function --function-name $LAMBDA_FUNCTION_NAME
fi

awslocal lambda create-function \
    --function-name $LAMBDA_FUNCTION_NAME \
    --runtime nodejs20.x \
    --handler index.handler \
    --zip-file fileb://$LAMBDA_FUNCTION_NAME.zip \
    --role arn:aws:iam::000000000000:role/lambda-role

awslocal lambda create-function-url-config \
    --function-name $LAMBDA_FUNCTION_NAME \
    --auth-type NONE

Básicamente se comprimen los fuentes (usando zip) y se usa comando create-function para desplegar función por primera vez. Se pueden reportar variables de entorno usando --environment (asociando por ejemplo, un archivo json).
El comando con create-function-url-config puede ser util para consumir la función a través de http

AWS CDK (Cloud Developer Kit) & LocalStack

En la plataforma de AWS (Amazon WebServices) es posible definir la infraesctructura usando código con un lenguaje de marcado como YAML, lo que se conoce como Infraestructura como Código (IaC - Infraestructure As Code). El componente tecnológico que permite esto se denomina CloudFormation. Es decir, con un archivo de estructura YAML se especifica un conjunto de servicios y como se aprovisionan en la plataforma de AWS de modo que puede ser replicable.

Luego, encontramos que también es posible usar un lenguaje de programación reconocido (tales como Javascript, Python, Java, C#, Go) para implementar IaC con un componente tecnológico que se denomina Cloud Developer Kit (CDK). Esto nos ofrece el uso de control de flujo con el lenguaje de programación y otras características para tener mayor control al definir nuestra IaC.

Como prerrequisitos de debe contar con un entorno Node.js instalado previamente, así como también contar con una cuenta configurada y la CLI (Command Line Interface) de AWS. A continuación veremos unos apuntes ágiles para iniciar con CDK.

Instalación de CDK

LocalStack provee un entorno local y simulado de AWS. Si se tiene LocalStack debidamente instalado y corriendo, es posible incorporar el uso del módulo cdklocal, el cual se instala ejecutando:

bash
npm install -g aws-cdk aws-cdk-local

Puede comprobarse la instalación con el comando npx cdk --version

Proyecto de inicio rápido

Para iniciar nuestro proyecto, nos ubicamos en una carpeta que se use como espacio de trabajo de proyectos y creamos nuestra subcarpeta para el proyecto. Luego inicializamos el proyecto, es decir, ejecutamos lo siguiente:

bash
mkdir project
cd project
cdk init app --language typescript

project corresponde a la carpeta asignada al proyecto (puede cambiarse).
En algunos casos puede ser necesario anteponer npx al comando cdk

A continuación estableceremos un Construct, que es un componente de servicio base para crear recursos. En nuestro ejercicio incluiremos DynamoDB para crear una tabla, por lo que ejecutamos lo siguiente:

bash
npm install @aws-cdk/aws-dynamodb

Nos dirigimos a nuestro proyecto para modificar el archivo lib/cdk-stack.ts, de modo que quede con el siguiente contenido:

typescript
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';

export class CdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    new dynamodb.Table(this, "user", {
      partitionKey: {
        name: "userId",
        type: dynamodb.AttributeType.STRING
      },
      sortKey: {
        name: "email",
        type: dynamodb.AttributeType.STRING
      }
    });
  }
}

Mediante el uso de dynamodb.Table se define nuestra tabla

CDK genera código de CloudFormation, así que para ver el resultado ejecutamos:

bash
cdklocal synth

En algunos casos puede ser necesario anteponer npx al comando cdklocal

Procedemos a establecer o asociar la cuenta simulada de AWS que usaremos con CDK ejecutando:

bash
cdklocal bootstrap

Finalmente, para desplegar nuestra IaC ejecutamos:

bash
cdklocal deploy

cdklocal destroy elimina lo que desplegamos

Completando nuestro ejercicio con una Lambda

Para acceder a la base de datos usaremos una Lambda, por lo que ejecutamos lo siguiente:

bash
npm install @aws-cdk/aws-lambda

El código de ejemplo a continuación sustituye el contenido anterior, siendo actualizado así:

typescript
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import { Runtime, FunctionUrlAuthType } from 'aws-cdk-lib/aws-lambda';
import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
import * as path from 'path';

export class CdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // dynamo
    const table = new dynamodb.Table(this, "user", {
      partitionKey: {
        name: "userId",
        type: dynamodb.AttributeType.STRING
      },
      sortKey: {
        name: "email",
        type: dynamodb.AttributeType.STRING
      }
    });

    // lambda
    const handler = new lambda.NodejsFunction(this, "userHandler", {
      runtime: Runtime.NODEJS_18_X,
      entry: path.join(__dirname, `/../run/lambda.ts`),
      handler: "handler",
      environment: {
        USER_TABLE_NAME: table.tableName,
      },
    });

    // prvileges
    table.grantReadWriteData(handler);

    const userUrl = handler.addFunctionUrl({
      authType: FunctionUrlAuthType.NONE,
      cors: {
        allowedOrigins: ['*'],
      }
    });

    new cdk.CfnOutput(this, 'userUrl', {
      value: userUrl.url,
    });
  }
}

Nótese que se hace referencia a una lambda en el archivo run/lambda.ts (el cual contiene el código de la función y deberá implementarse)