AWS
May 26, 2023

Tratamento de erros no AWS Step Functions

O Step Functions é um serviço fornecido pela AWS para ajudar os desenvolvedores a criar aplicativos com fluxos de trabalho ou pipelines complexos. Em essência, ele permite que você descreva seu fluxo de trabalho como uma máquina de estado (ou fluxograma) de etapas a serem executadas. As etapas podem ser desde uma função lambda, um batch job, uma query do Athena ou até mesmo iniciar alguma outra máquina de estado.

O Step Functions possui ainda funcionalidades relacionadas ao orquestramento de aplicações distribuídas, com suporte a execução paralela de tarefas e ‘fan-out’ para paralelismo dinâmico (por exemplo, chamando uma função lambda para cada N objetos de uma lista). Também possui suporte a execução assíncrona de tarefas, e facilidades para tratamento de exceções e reexecução de estados.

Podemos utilizar um formato JSON, através do CloudFormation, por exemplo, para definir esses fluxos de trabalho (ou máquinas de estado). Também temos a opção de utilizar o editor visual pelo console da AWS.

No Zanshin, além de tudo, nós nos aproveitamos da facilidade da integração do Step Functions com todo o ecossistema AWS, por exemplo, IAM para controle de acesso, CloudTrail para auditoria e o CloudWatch para monitoramento.

O Step Functions realmente se destaca quando você deseja integrar de forma simples peças de lógica que estão espalhadas por vários serviços da AWS, em linguagens diferentes e com coordenação complexa.

Por exemplo, a seguinte máquina de estado (da documentação):

É definida pelo seguinte JSON:

{
 “Comment”: “An example of the Amazon States Language for notification on an AWS Batch job completion”,
 “StartAt”: “Submit Batch Job”,
 “TimeoutSeconds”: 3600,
 “States”: {
   “Submit Batch Job”: {
     “Type”: “Task”,
     “Resource”: “arn:aws:states:::batch:submitJob.sync”,
     “Parameters”: {
       “JobName”: “BatchJobNotification”,
       “JobQueue”: “arn:aws:batch:us-east-1:123456789012:job-queue/BatchJobQueue-7049d367474b4dd”,
       “JobDefinition”: “arn:aws:batch:us-east-1:123456789012:job-definition/BatchJobDefinition-74d55ec34c4643c:1”
     },
     “Next”: “Notify Success”
   },
   “Notify Success”: {
     “Type”: “Task”,
     “Resource”: “arn:aws:states:::sns:publish”,
     “Parameters”: {
       “Message”: “Batch job submitted through Step Functions succeeded”,
       “TopicArn”: “arn:aws:sns:us-east-1:123456789012:batchjobnotificatiointemplate-SNSTopic-1J757CVBQ2KHM”
     },
     “End”: true
   }
 }
}

Na Tenchi, usamos o Step Functions para construir a nossa engine de varredura em nuvem do Zanshin. Isso nos permite descrever as etapas necessárias para executar uma varredura no ambiente de um cliente como um fluxograma de etapas abrangendo muitos serviços diferentes que precisam ser acessados, com componentes que podem ser desenvolvidos independentemente..

Como qualquer ambiente de computação complexo, erros, exceções e timeouts acontecem de tempos em tempos. Dependendo da natureza do aplicativo, esses erros precisam ser detectados e tratados. Como eles são tratados, depende da aplicação. A engine do Tenchi é idempotente, portanto, a reexecução é uma opção viável, mas outros aplicativos podem querer abortar todo o processo ou executar uma etapa para lidar com o erro.

As Step Functions nos fornecem algumas maneiras diferentes de detectar erros:

1) Capturando erros com a instrução ‘catch’:

Cada ‘etapa’ em uma máquina de estado do Step Functions, representada por um estado, pode definir sua própria instrução ‘catch’, para a qual é possível definir um conjunto de erros esperados ou simplesmente esperar TODOS os erros.

No código, fica assim:

  “Catch”: [
         {
           “ErrorEquals”: [ “States.ALL” ],
           “Next”: “Notify Failure”
         }
     ]

Em nosso exemplo anterior, ficaria assim (criamos outro estado para tratar o erro):

{
 “Comment”: “An example of the Amazon States Language for notification on an AWS Batch job completion”,
 “StartAt”: “Submit Batch Job”,
 “TimeoutSeconds”: 3600,
 “States”: {
   “Submit Batch Job”: {
     “Type”: “Task”,
     “Resource”: “arn:aws:states:::batch:submitJob.sync”,
     “Parameters”: {
       “JobName”: “BatchJobNotification”,
       “JobQueue”: “arn:aws:batch:us-east-1:123456789012:job-queue/BatchJobQueue-7049d367474b4dd”,
       “JobDefinition”: “arn:aws:batch:us-east-1:123456789012:job-definition/BatchJobDefinition-74d55ec34c4643c:1”
     },
     “Next”: “Notify Success”,
     “Catch”: [
         {
           “ErrorEquals”: [ “States.ALL” ],
           “Next”: “Notify Failure”
         }
     ]
   },
   “Notify Success”: {
     “Type”: “Task”,
     “Resource”: “arn:aws:states:::sns:publish”,
     “Parameters”: {
       “Message”: “Batch job submitted through Step Functions succeeded”,
       “TopicArn”: “arn:aws:sns:us-east-1:123456789012:batchjobnotificatiointemplate-SNSTopic-1J757CVBQ2KHM”
     },
     “End”: true
   },
   “Notify Failure”: {
     “Type”: “Task”,
     “Resource”: “arn:aws:states:::sns:publish”,
     “Parameters”: {
       “Message”: “Batch job submitted through Step Functions failed”,
       “TopicArn”: “arn:aws:sns:us-east-1:123456789012:batchjobnotificatiointemplate-SNSTopic-1J757CVBQ2KHM”
     },
     “End”: true
   }
 }
}

Que produz o seguinte diagrama:

Problemas:

Este método funcionará sempre que um erro for produzido dentro de uma etapa, desde que nos lembremos de adicionar o bloco catch a todas as etapas.

No entanto, isso adiciona alguma complexidade desagradável à definição e ao diagrama da máquina de estado. Não tanto em nosso exemplo, mas quando as coisas começam a escalar.

Além disso, não irá capturar erros produzidos fora das etapas, mas pelo próprio Step Functions. Por exemplo, se ele tentar acessar um parâmetro que não existe. Este método  também não será capaz de detectar quando a execução da máquina de estado do Step Functions for interrompida por ter excedido o seu tempo limite (timeout). No nosso caso, isso pode acontecer quando fazemos um scan em um ambiente grande demais.

2) Capturando erros com o evento do Cloudwatch Events:

O CloudWatch Events é o serviço de streaming de eventos associado ao serviço de monitoramento da AWS, o CloudWatch, através dele, é possível registrar funções lambda para escutar e reagir a determinados tipos de eventos.

Uma boa opção para o monitoramento de falhas para o Step Functions é registrar um lambda para escutar o evento correspondente à falha na execução.

Para fazer isso, criamos um novo lambda e registramos o gatilho certo. No console da AWS, deve ficar assim:

Neste exemplo, estamos usando o mesmo lambda para capturar três eventos. FAILED, TIMED_OUT e ABORTED.

Uma lista de todos os eventos disponíveis está disponível na documentação aqui.

Essa opção é ótima porque detecta qualquer falha na execução da máquina de estado, seja por erro de execução em algum dos estados, por falhas entre as transições de estado, por limitação de tempo (timeout), ou por interrupção externa (abort).

Problema resolvido, certo? Ainda não.

De acordo com a documentação, não há garantia de entrega desses eventos. O que significa que não podemos confiar 100% neles para tratar todos os erros em nosso aplicativo.

3) Combinando vários métodos:

Para diminuir a probabilidade de erros não tratados pelo sistema, podemos combinar vários métodos.

No Zanshin, usamos instruções catch para capturar a maioria dos erros dentro do Step Functions, mas também temos uma função lambda registrada para ouvir os eventos FAILED e TIMED_OUT. Também temos um lambda que roda periodicamente e nos alerta quando temos uma execução que já está rodando há mais tempo do que um threshold determinado, para o caso dos dois métodos anteriores falharem. Isto cobre um cenário improvável mas não impossível de: erro na definição da máquina de estado que levou a uma falta de catch, falha no serviço Step Functions que faça o catch não ser honrado, e/ou falha na entrega dos eventos EventBridge associados.

A ressalva é que se a sua lógica de tratamento de erros não for idempotente, você vai precisar garantir que ela só seja executada uma única vez para evitar efeitos colaterais indesejados.

Conclusões

Em qualquer aplicativo suficientemente complexo, deve-se esperar que erros aconteçam eventualmente. Monitorar e tratar esses erros corretamente é essencial para garantir a confiabilidade do sistema e uma boa experiência do usuário.

Neste artigo, mostramos duas maneiras de monitorar erros no Step Functions da AWS e como elas podem ser combinadas para melhorar a confiabilidade de um aplicativo. Lembre-se de considerar também que tipo de ‘sanity checks’ você pode executar em seu aplicativo, para detectar problemas quando tudo mais falhar.

Crédito

Lucas Silva Schanner