LocalStack: Desenvolver e testar aplicações AWS localmente com eficiência

David VilaçaDavid Vilaça
6 min read

Introdução

Desenvolver aplicações que utilizam serviços em nuvem como AWS pode ser desafiador. Cada teste feito diretamente na cloud provider pode gerar custos e demanda tempo extra. Nesse contexto, surge o LocalStack, uma ferramenta poderosa que permite replicar localmente serviços AWS, agilizando o ciclo de desenvolvimento e reduzindo custos.

O que é o LocalStack e como funciona?

O LocalStack é uma ferramenta de código aberto que simula serviços essenciais da AWS como Lambda, S3, DynamoDB, SNS, SQS, API Gateway, entre outros. Tudo através de containers Docker. Ele emula a interface e comportamento desses serviços permitindo testes locais com alta fidelidade em relação ao ambiente real. Dessa forma, não é necessário que os desenvolvedores tenham conexão à nuvem real, proporcionando um ambiente de desenvolvimento mais rápido e seguro.

O LocalStack expõe interfaces que replicam as APIs da AWS, permitindo que ferramentas e bibliotecas que interagem com a AWS funcionem com o LocalStack com pouca ou nenhuma modificação. Por exemplo, ao usar a AWS CLI, é possível direcionar os comandos para o LocalStack especificando o endpoint local, como --endpoint-url=http://localhost:4566. Além disso, o LocalStack pode ser integrado em pipelines de CI/CD, permitindo testes automatizados de infraestrutura como código (IaC) usando ferramentas como Terraform. Essa integração facilita a validação de configurações e a detecção precoce de erros, melhorando a eficiência do ciclo de desenvolvimento.

Instalação e setup inicial

Sem muita firula já vamos para parte prática, então para começar basta seguir os passos abaixo.

Primeiramente temos que ter instalado o Docker e o AWS CLI.

O LocalStack pode ser utilizado de duas formas principais:

  1. Instalando a ferramenta no sistema operacional

  2. Rodando via Docker

Neste artigo vamos explorar apenas a execução via Docker Compose para melhor portabilidade. Então, no projeto onde queremos utilizar o LocalStack, vamos criar ou editar o arquivo docker-compose.yml para adicionar o serviço:

 services:
  localstack:
    image: localstack/localstack:4
    ports:
      - "4566:4566"
      - "4510-4559:4510-4559"
    environment:
      - DEBUG=${DEBUG:-1}
      - PERSISTENCE=${PERSISTENCE:-1}
      - LAMBDA_EXECUTOR=${LAMBDA_EXECUTOR:-docker}
      - LAMBDA_REMOVE_CONTAINERS=true
      - DOCKER_HOST=unix:///var/run/docker.sock

      - SERVICES=s3,lambda,rds,sts,iam,logs,cloudformation

      - RDS_PORT_RANGE=5432-5433

      - LAMBDA_DOCKER_NETWORK=localstack_default

      - AWS_ENDPOINT_URL=http://localhost:4566

    volumes:
      - "./volume:/var/lib/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"
      - "./localstack-setup.sh:/etc/localstack/init/ready.d/init-aws.sh"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
      interval: 30s
      timeout: 10s
      retries: 5
      start_period: 60s

Deve-se atentar a variável de ambiente “SERVICES” que contém a lista dos serviços que será utilizado. Para saber sobre os serviços veja na documentação oficial.

Após a configuração correta do docker-compose.yml, rode o serviço com docker compose up -d.

Para testar se tudo está funcionando você deve ter configurado o AWS CLI corretamente. Digite aws configure e insira uma access key e secret key qualquer, pois não será utilizada pelo local stack, e a region deixe “us-east-1” (ou outra qualquer), já o output format deixe “json”, caso prefira pode ser utilizado o recurso de profiles também. Após isso podemos executar o comando aws --endpoint-url=http://localhost:4566 s3 ls e esperar uma resposta vazia porém sem erros. Se preferir pode criar um bucket de teste e depois verificar na lista se criou corretamente: aws --endpoint-url=http://localhost:4566 s3api create-bucket --bucket test-bucket.

Exemplo prático

Para exemplificar, criaremos uma função Lambda em Go. O repositório do projeto também conterá o código da infraestrutura como código (IaC) utilizando Terraform. O projeto se trata de uma API simples em Go com um endpoint para fazer upload de arquivos txt para um bucket S3. Sempre que um arquivo for enviado para o bucket, será automaticamente disparada uma função Lambda também escrita em Go, que realizará uma ação simples, como ler e imprimir os conteúdo do arquivo no log. Dessa forma teremos o código fonte e a infraestrutura de forma real no repositório. O projeto terá a seguinte arquitetura:

PlantUML diagram

PlantUML diagram

Primeiro vamos criar uma api com Go, utilizando Gin. Na api teremos uma rota POST “/upload” que receberá um arquivo e o enviará para o S3. Como o foco não é a construção de uma api, manterei detalhes apenas da comunicação com a AWS. Será utilizado o SDK oficial da AWS (https://github.com/aws/aws-sdk-go), e como qualquer SDK oficial da AWS, temos a opção “Endpoint” onde podemos alterar o valor para uma url local juntamente com as opções de region, credenciais, ssl, etc. Então nossa instância do S3 terá a seguinte configuração:

s, _ := session.NewSession(&aws.Config{
    Region:           aws.String("us-east-1"),
    Endpoint:         aws.String("http://localhost:4566"),
    S3ForcePathStyle: aws.Bool(true),
})

uploader := s3.New(s)

Com isso temos o SDK apontando corretamente para o LocalStack, e é claro que essa configuração deve ser alterada para ambiente de produção ou ser controlada via variável de ambiente.

Para implantar os recursos AWS e conseguir replicar a mesma configuração em ambientes produtivos, irei utilizar Terraform. A utilização do terraform é exatamente a mesma que você teria no dia a dia, porém o provider da AWS a gente vai configurar para apontar para o LocalStack:

provider "aws" {
  region                      = "us-east-1"
  access_key                  = "test"
  secret_key                  = "test"
  s3_use_path_style           = true
  skip_credentials_validation = true
  skip_requesting_account_id  = true
  endpoints {
    s3     = "http://localhost:4566"
    lambda = "http://localhost:4566"
    iam    = "http://localhost:4566"
  }
}

O projeto ficou bem simples, com apenas um arquivo (main.tf) para o terraform e os arquivos “cmd/api/main.go” e “cmd/file-processor/main.go” contendo a lógica da api e lambda respectivamente. Foi criado também um Makefile para automatizar as nossas tarefas de subir ambiente, build, etc. Então podemos rodar um “make prepare” ou apenas “make” para:

  1. Executar o build das aplicações Go

  2. Zipar o arquivo binário que será executado pela lambda

  3. Iniciar o LocalStack (Docker Compose)

  4. Provisionar a infraestrutura no LocalStack através do Terraform

Com isso já temos o projeto rodando com S3, Lambda e CloudWatch Logs completamente no LocalStack. Vamos executar um teste enviando um arquivo “teste.txt” para o endpoint da api contendo apenas um “hello world”. O código que a lambda vai executar será apenas exibir o conteúdo do arquivo nos logs. Assim como na API, o código da lambda deve adicionar a opção de endpoint apontando para o LocalStack:

func handler(ctx context.Context, s3Event events.S3Event) (string, error) {
    sess, _ := session.NewSession(&aws.Config{
        Region:           aws.String("us-east-1"),
        Endpoint:         aws.String("http://localstack:4566"),
        S3ForcePathStyle: aws.Bool(true),
    })
...

Uma observação é que como a lambda é executada na rede criada pelo Docker Compose, ao invés de localhost devemos colocar apenas o nome do serviço. Com isso a lambda ao ser executada já pode obter o arquivo do S3 para exibir o conteúdo. Vamos executar um curl enviando o arquivo e em seguida o comando make lambda-logs para exibir os logs da execução da lambda através do AWS CLI, mas primeiro em outro terminal devemos rodar a api com make run-api:

Perfeito! A API realizou o upload corretamente para o S3 e em seguida houve um trigger que executou a função lambda em um ambiente completamente local e muito parecido com ambiente real de produção.

Todo o código fonte deste projeto exemplo pode ser encontrado no Github: http://github.com/davidpvilaca/localstack-basics-article.

Conclusão

Utilizar o LocalStack como ambiente de desenvolvimento local para aplicações que interagem com serviços da AWS oferece inúmeras vantagens:

  • Eficiência: Reduz o tempo de desenvolvimento e testes, permitindo ciclos mais rápidos.

  • Custo: Evita custos associados ao uso de recursos reais da AWS durante o desenvolvimento.

  • Segurança: Permite testes em um ambiente isolado, evitando impactos em ambientes reais.

  • Produtividade: Facilita a automação de testes e integrações contínuas, melhorando o fluxo de trabalho.

Ao integrar o LocalStack com ferramentas como Terraform, desenvolvedores podem simular com precisão o comportamento de serviços da AWS em um ambiente local. Isso não apenas acelera o desenvolvimento, mas também proporciona uma maior confiança na qualidade e estabilidade das aplicações antes de serem implantadas em ambientes de produção.

Em resumo, o LocalStack é uma ferramenta essencial para desenvolvedores que buscam eficiência, economia e segurança no desenvolvimento de aplicações baseadas em serviços da AWS.

0
Subscribe to my newsletter

Read articles from David Vilaça directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

David Vilaça
David Vilaça

Software Architect | Software Engineer | Backend Developer Passionate about Software Engineering, I've spent 8 years crafting code, solving intricate challenges, and architecting robust systems. With a degree in Systems of Information from IFES, I've honed my skills to build the digital foundations of the future. My realm is the backend, where I dive deep into the intricacies of software engineering. I specialize in web development, leveraging technologies like Kubernetes, Kafka, Elasticsearch, and Node.js to engineer resilient, scalable, and performant solutions.