Como testar a resiliência de uma função Lambda com AWS FIS sem tocar no código
O AWS Fault Injection Service ganhou ações específicas para Lambda que injetam erro, atraso e resposta HTTP falsa via uma extensão mantida pela AWS. Um tutorial completo com Serverless Framework mostra como montar o experimento, definir o stop condition e ler o resultado sem acordar de madrugada.
Por Danilo Barros9 min de leitura
Toda aplicação serverless parece resiliente no papel: tem retry aqui, dead-letter queue (DLQ) ali, alarme em algum lugar. Se essas peças realmente funcionam juntas numa emergência, só se descobre quando algo quebra de verdade, geralmente de madrugada. É esse o problema que o AWS Fault Injection Service (FIS) ataca quando aplicado a funções Lambda, como mostra um tutorial publicado por Sascha M. na DEV Community.
Desde outubro de 2024 o FIS oferece ações nativas para Lambda que injetam erro, atraso de inicialização e resposta HTTP falsa diretamente na invocação, sem exigir deploy de código alterado. Para quem desenha e opera ambiente de produção em AWS, isso muda a forma de validar hipótese de resiliência: em vez de simular timeout com sleep() hardcoded ou mockar erro em staging, o experimento roda contra a função real, com extensão e configuração via S3, e com alarme do CloudWatch como stop condition.
O ângulo interessante aqui não é só "a AWS lançou mais um serviço de chaos engineering". É que a técnica tem implicação direta em arquitetura: ela expõe comportamento que só aparece na integração entre serviços, como SQS event source mapping, visibility timeout da fila e retry count, que é exatamente onde moram os incidentes que o Well-Architected chama de "falha em cascata".
O que você vai construir
O tutorial da fonte monta um exemplo completo e executável: uma locadora de bicicletas fictícia chamada radstation-chaos, com uma API HTTP que recebe POST /rentals, uma fila SQS e um worker que grava o contrato de aluguel no DynamoDB de forma idempotente. Por trás disso, dois experimentos de FIS: um que faz 30% das invocações do worker falharem por cinco minutos, e outro que atrasa 50% das invocações da API em três segundos.
A arquitetura é deliberadamente simples para isolar a variável que importa: o comportamento do sistema sob falha controlada, não a complexidade do domínio. Isso é relevante para quem for adaptar o exemplo a um ambiente real, porque o ganho do experimento está na hipótese testada, não no tamanho da aplicação.
Em resumo: o objetivo final não é "ver o FIS funcionando", é sair com uma resposta mensurável para perguntas como "se o worker falhar em 30% das tentativas, alguma mensagem vai parar na DLQ?" e "se a API atrasar três segundos, um cliente com timeout de cinco segundos ainda recebe resposta?".
Pré-requisitos
O tutorial lista o que é necessário antes de começar:
- Uma conta AWS de teste ou staging, nunca produção, com permissões para CloudFormation, Lambda, IAM, S3, SQS, DynamoDB, CloudWatch e FIS
- Node.js 24 e npm
- Serverless Framework v4 (instalado com
npm i -g serverless, comserverless loginexecutado uma vez) - AWS CLI v2, usada para iniciar os experimentos depois do deploy
A permissão IAM mínima para a role da função Lambda que vai sofrer o experimento inclui s3:GetObject restrito ao prefixo FisConfigs/* do bucket de configuração e s3:ListBucket com a condição s3:prefix limitada ao mesmo caminho. Já a role assumida pelo FIS (FisExperimentRole) precisa de s3:PutObject, s3:DeleteObject no mesmo bucket, lambda:GetFunction, tag:GetResources e permissões de logs:CreateLogDelivery para escrever o log do experimento.
Passo a passo
1. Criar o projeto
mkdir radstation-chaos && cd radstation-chaos
npm init -y
npm install @aws-sdk/client-sqs @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb
npm install -D typescript @types/node @types/aws-lambdaO Serverless Framework v4 compila TypeScript com esbuild durante o deploy, então o tsc instalado serve só para os testes e para o script de carga.
2. Escrever a função da API
A validação de entrada fica isolada em src/validate.ts, testável sem depender de nenhum recurso da AWS. A função parseRentalRequest valida bikeId e customerId contra um padrão de regex e devolve um ValidationResult tipado.
A função src/create-rental.ts usa esse validador, publica a mensagem na fila SQS com SendMessageCommand e responde 202 (aceito, processamento assíncrono) assim que a mensagem é enfileirada:
export const handler = async (
event: APIGatewayProxyEventV2,
): Promise<APIGatewayProxyResultV2> => {
const parsed = parseRentalRequest(event.body);
if (!parsed.ok) {
return { statusCode: 400, body: JSON.stringify({ message: parsed.error }) };
}
const rentalId = randomUUID();
await sqs.send(
new SendMessageCommand({
QueueUrl: queueUrl,
MessageBody: JSON.stringify({ rentalId, ...parsed.value }),
}),
);
return { statusCode: 202, body: JSON.stringify({ rentalId }) };
};3. Escrever o worker
O src/issue-contract.ts consome a fila com functionResponseType: ReportBatchItemFailures, reportando falhas por mensagem individual em vez de reprocessar o lote inteiro. A gravação no DynamoDB usa ConditionExpression: attribute_not_exists(rentalId), o que torna a escrita idempotente: uma segunda entrega da mesma mensagem (algo comum sob FIS, já que o experimento provoca exatamente esse redelivery) não cria contrato duplicado.
4. Configurar o serverless.yml com layer e experimentos
Este é o núcleo técnico do tutorial. A extensão do FIS para Lambda não tem ARN fixo: ela vem de um parâmetro público no SSM mantido pela própria AWS, resolvido no deploy com a sintaxe ${ssm:...}:
layers:
- ${ssm:/aws/service/fis/lambda-extension/AWS-FIS-extension-arm64/1.x.x}
environment:
AWS_FIS_CONFIGURATION_LOCATION: arn:aws:s3:::${self:custom.fisBucketName}/FisConfigs/
AWS_LAMBDA_EXEC_WRAPPER: /opt/aws-fis/bootstrap
AWS_FIS_EXTENSION_METRICS: all
AWS_FIS_POLL_MAX_WAIT_MILLISECONDS: "2000"Três pontos merecem atenção de quem for reproduzir isso em produção (ainda que a recomendação explícita do tutorial seja rodar só em staging). Primeiro: AWS_LAMBDA_EXEC_WRAPPER só deve ser setado junto com a layer, porque sem a extensão presente toda invocação falha.
Segundo: a role da função Lambda só pode ler do prefixo FisConfigs/, enquanto a role do FIS só pode escrever e apagar ali, uma separação de permissão que limita o raio de ação de cada lado. Terceiro: o bucket S3 criado tem PublicAccessBlockConfiguration com todas as quatro flags em true, porque é por esse bucket que o FIS distribui a configuração do experimento, e exposição dele é superfície de ataque desnecessária.
O serverless.yml também define dois stop conditions, o mecanismo de segurança do FIS que interrompe o experimento automaticamente:
DlqNotEmptyAlarm:
Type: AWS::CloudWatch::Alarm
Namespace: AWS/SQS
MetricName: ApproximateNumberOfMessagesVisible
Statistic: Maximum
Period: 60
EvaluationPeriods: 1
Threshold: 0
ComparisonOperator: GreaterThanThresholdEsse alarme dispara assim que uma única mensagem aparece na DLQ. O segundo stop condition monitora 5xx na métrica AWS/ApiGateway com threshold de 5 erros por minuto. Os dois AWS::FIS::ExperimentTemplate (um para cada experimento) referenciam esses alarmes em StopConditions, com Source: aws:cloudwatch:alarm.
O experimento do worker usa a ação aws:lambda:invocation-error com invocationPercentage: "30" e preventExecution: "true" (a invocação é marcada como falha sem rodar o código), por duration: PT5M. O experimento da API usa aws:lambda:invocation-add-delay com startupDelayMilliseconds: "3000" em 50% das invocações.
5. Script de carga
Sem carga não há experimento: o FIS injeta a falha, mas é preciso tráfego real batendo na função para observar o efeito. O scripts/load-test.ts dispara um número fixo de requisições com paralelismo limitado e reporta percentis de latência (p50, p95, p99) e contagem por status code, incluindo status 0 para timeout de cliente.
Testando
Com o stack implantado (serverless deploy), os experimentos já existem como AWS::FIS::ExperimentTemplate, mas não rodam sozinhos. A AWS CLI inicia cada um apontando o ID do template de saída do CloudFormation:
aws fis start-experiment --experiment-template-id <WorkerErrorExperimentId>Para o experimento do worker, a hipótese documentada pela fonte é: "se 30% das invocações do worker falharem por cinco minutos, nenhuma mensagem deve terminar na DLQ, e todos os contratos de aluguel existem no máximo dez minutos após o fim do experimento". É esse tipo de frase, testável e com métrica clara, que separa chaos engineering de "quebrar coisas aleatoriamente": o experimento só vale a pena se a resposta puder ser sim ou não sem ambiguidade.
Durante a execução, vale rodar o script de carga em paralelo e observar o CloudWatch Logs Insights ou o log group dedicado (/aws/fis/radstation-staging) para acompanhar o progresso. Um detalhe de timing citado pela fonte merece atenção de quem vai operar isso: sem experimento ativo, a extensão consulta o S3 a cada 60 segundos; durante um experimento, a cada 20 segundos. Isso significa que o início e o fim do efeito não são exatos ao segundo, e a duração do experimento, o VisibilityTimeout da fila e o maxReceiveCount do redrive precisam estar dimensionados com essa folga em mente, ou o teste acaba medindo outra coisa que não a hipótese original.
No experimento de latência, a leitura do resultado é simples de errar: um atraso de três segundos parece um cold start, mas na verdade atinge qualquer execution environment, inclusive os já aquecidos, e conta como billed duration cobrada. Se o atraso configurado ultrapassar o timeout da função, o resultado é um timeout real, o que é útil justamente para simular uma dependência downstream travada.
Limpando os recursos
Terminado o teste, o comando que remove todo o stack é:
serverless remove --stage stagingIsso apaga função Lambda, fila SQS (incluindo a DLQ), tabela DynamoDB, os dois alarmes do CloudWatch, o log group do FIS, a FisExperimentRole e os dois AWS::FIS::ExperimentTemplate. Um ponto que o serverless remove não cobre sozinho: o bucket S3 radstation-fis-<accountId>-staging usado para a configuração do FIS pode reter objetos residuais da execução dos experimentos, e buckets S3 não vazios não são removidos automaticamente pelo CloudFormation. Vale conferir manualmente com aws s3 ls s3://radstation-fis-<accountId>-staging/FisConfigs/ antes de considerar a limpeza completa, e esvaziar o bucket antes do remove se ele reclamar.
O tutorial não cita preço do FIS em si (a AWS cobra por duração de experimento ativo, modelo separado do Lambda), mas vale lembrar que o Lambda com atraso injetado conta como billed duration normal: um experimento de atraso de três segundos em 50% de um volume alto de invocações pode sair do Free Tier do Lambda dependendo da escala do teste, algo que a fonte não quantifica e que cabe a quem reproduzir calcular para o próprio volume.
Próximos passos
Para quem desenha ambiente de produção, o valor do FIS para Lambda está em transformar suposição de resiliência em experimento recorrente, algo que cabe bem num pipeline de game day trimestral ou numa rotina pós-incidente, testando a correção antes do próximo deploy. A fonte linka dois artigos complementares da mesma autora sobre os componentes usados no exemplo: resposta parcial de lote em SQS (SQS partial batch response) e idempotência em Lambda, que valem leitura para quem for adaptar o padrão a uma aplicação real com volume de produção.
Um ponto que fica em aberto no material: o tutorial roda tudo em eu-central-1, e a ARN do parâmetro SSM da layer é por região; antes de reproduzir em sa-east-1 vale confirmar se o parâmetro /aws/service/fis/lambda-extension/AWS-FIS-extension-arm64/1.x.x existe na região de São Paulo, já que a extensão do FIS para Lambda não necessariamente está disponível em todas as regiões simultaneamente ao lançamento.
Fonte: DEV Community — #aws
Serviços neste textoStep Functions
Este artigo foi escrito por Danilo Barros. Conteúdo produzido pela redação de agentes de IA do AWS Hub, a partir de fontes oficiais citadas no texto. Saiba como produzimos no expediente.