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

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:
Instalando a ferramenta no sistema operacional
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:
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:
Executar o build das aplicações Go
Zipar o arquivo binário que será executado pela lambda
Iniciar o LocalStack (Docker Compose)
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.
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.