Uno dei servizi serverless più famosi di AWS è Step Functions, questo servizio permette la definizione di flussi di lavoro con macchine a stati, pensato per lavorare con la maggior parte degli altri servizi AWS e permette di costruire applicazioni distribuite con processi automatizzate. Spesso le singole definizione vengono chiamate State machine (in italiano macchine a stati), spesso abbreviato con la sigla sm. Ad oggi è studiato per poter lavorare con oltre 200 servizi AWS e oltre 1000 API, con la possibilità di creare strutture logiche condizionali e cicli di esecuzioni. Il servizio è studiato per non avere limiti di tempo (il timeout di default per una step function è un mese), prevede 4.000 transazioni di stato gratuite al mese e il costo di 0,025 $ per ogni mille transizioni di stato successive.
Spesso una macchina a stati viene rappresentata nella console web in maniera grafica con un grafico:
I casi d’uso più semplici sono l’invocazione di funzioni Lambda e integrazione con gli altri servizi serverless come Dynamo, SNS e SQS; per esempio si può pensare di voler definire una macchina a stati per salvare un ordine di un e-business, i passi da definire sono il salvataggio dell’ordine in una tabella (Dynamo), l’inserimento dell’ordine in una coda per il magazzino (SQS) e l’invio di una notifica al cliente (SNS).
Tuttavia anche questo servizio ha alcune caratteristiche che lo limitano:
- ogni SM è sequenzale e, a meno di struttute molto complesse, non si può tornare indietro
- ogni SM ha un unico inizio e, per terminare correttamente, deve avere un solo punto di fine
- le esecuzioni hanno dei limiti di esecuzione descritti nella pagina ufficiale
- ogni esecuzione ha un nome e una arn che identifica l’esecuzione
- ogni esecuzione può avere in input ma deve essere in formato json
- una esecuzione non può essere messa in pausa, può solo essere terminata
Tecnicamente i passi da eseguire in una macchina a stati sono rappresentati in un documenti json che prevede un unizio (startAt), una serie di passi che terminano con un End finale.
{ "StartAt":"CopyInputFile", "States":{ "CopyInputFile":{ "Type":"Task", "Resource":"arn:aws:states:::aws-sdk:s3:copyObject", "Parameters":{ "Bucket":"bucket1", "CopySource.$":'States.Format("bucket1/path/{}', $.filename)", "Key.$":"States.Format('bucket2/path2/{}-{}', $.filename, $$.Execution.StartTime)" }, "ResultPath":null, "Next":"DeleteInputFile" }, "DeleteInputFile":{ "Type":"Task", "Resource":"arn:aws:states:::aws-sdk:s3:deleteObject", "Parameters":{ "Bucket":"bucket1", "Key.$":"States.Format(\"path/{}\", $.filename)" }, "ResultPath":null, "Next":"End" }, "End":{ "Type":"Pass", "End":true } } }
AWS ha studiato un ambiente grafico nella console grafica dove è possibile creare una macchina a stati e definire tutte le configurazioni degli stati:
La documentazione ufficiale e il blog sono ricchi di esempi di questo tipo ovviamente è possibile gestire questo servizio con Cli e in maniera programmatica con SDK e CloudFormation.
La CLI un un numeroso elenco di comandi per la gestione delle state machine, i comandi più usati sono:
- lista delle macchine a state definite
aws stepfunctions list-state-machines aws stepfunctions list-state-machines --output table --query 'stateMachines[*].[name,stateMachineArn]'
- dettaglio di una macchina a stati
aws stepfunctions describe-state-machine --state-machine-arn arn:aws:states:eu-west-1:740456629644:stateMachine:sfBonificiWaitingDaCedacri --output text
- elenco di esecuzioni
aws stepfunctions list-executions --state-machine-arn arn:aws:states:eu-west-1:740456629644:stateMachine:sfBonificiWaitingDaCedacri --output table --query 'executions[*].[name,status,startDate,stopDate,executionArn]'
- elenco di esecuzioni
aws stepfunctions list-executions --state-machine-arn arn:aws:states:eu-west-1:740456629644:stateMachine:sfBonificiWaitingDaCedacri --output table --query 'executions[*].[name,status,startDate,executionArn]' --status-filter FAILED
- dettaglio di una esecuzione
aws stepfunctions describe-execution --execution-arn arn:aws:states:eu-west-1:740456629644:execution:sfBonificiWaitingDaCedacri:testDaCli --output text
- avvio di una macchina a stati
aws stepfunctions start-execution --state-machine-arn arn:aws:states:eu-west-1:740456629644:stateMachine:sfBonificiWaitingDaCedacri
- avvio di una macchina a stati con un parametro
aws stepfunctions start-execution --state-machine-arn arn:aws:states:eu-west-1:740456629644:stateMachine:sfBonificiWaitingDaCedacri --name testDaCli --input "{ \"filename\": \"prova.csv\" , \"SkipDeleteSourceFile\" : false }"
Con SDK è possibile gestire le macchine a stati definire nel servizio Step Function le funzioni base:
def state_machine_list(profile_name): boto3.setup_default_session(profile_name=profile_name) client = boto3.client('stepfunctions') response = client.list_state_machines(maxResults=100) if 'stateMachines' in response: return response['stateMachines'] return[] def state_machine_detail(profile_name,smArn): boto3.setup_default_session(profile_name=profile_name) client = boto3.client('stepfunctions') response = client.describe_state_machine( stateMachineArn=smArn) return response def state_machine_execution(profile_name,smArn): boto3.setup_default_session(profile_name=profile_name) client = boto3.client('stepfunctions') response = client.list_executions( stateMachineArn=smArn, #statusFilter='RUNNING'|'SUCCEEDED'|'FAILED'|'TIMED_OUT'|'ABORTED', maxResults=100, ) if 'executions' in response: return response['executions'] return [] def state_machine_execution_detail(profile_name,esecutionArn): boto3.setup_default_session(profile_name=profile_name) client = boto3.client('stepfunctions') response = client.describe_execution(executionArn=esecutionArn) return response def state_machine_start(profile_name,stateMachineArn,name,input): boto3.setup_default_session(profile_name=profile_name) client = boto3.client('stepfunctions') response = client.start_execution( stateMachineArn=stateMachineArn, name=name, input=input ) return response
Con CloudFormation è possibile definire macchine a stati e lavorare con altri servizi in maniera “automatica”, la miglior strada è creare un file con la definizione della macchina e richiamare il file da un template, per esempio la definizione della risorsa:
StateMachine: Type: AWS::Serverless::StateMachine Properties: Name: !Ref SFName Type: STANDARD DefinitionUri: statemachine.yaml Role: !GetAtt StateMachineRole.Arn DefinitionSubstitutions: SourcePath: !Ref SourcePath SourceBucket: !Ref SourceBucket DestBucket: !Ref DestBucket DestFilePath: !Ref DestFilePath Logging: Destinations: - CloudWatchLogsLogGroup: LogGroupArn: !GetAtt StateMachineLogGroup.Arn Level: ALL IncludeExecutionData: True
Nell’esempio si può intuire che c’è da definire una regola IAM per impostare i permessi della macchina (che di default non può fare nulla se non specificato chiaramente in una una regola) e il gruppo di log su CloudWatch. La definizione della macchina può essere descritta in json o un yaml, un semplice esempio di step function che sposta un file è:
StartAt: CopyInputFile States: CopyInputFile: Type: Task Resource: arn:aws:states:::aws-sdk:s3:copyObject Parameters: Bucket: ${DestBucket} CopySource.$: States.Format('${SourceBucket}/${SourcePath}/{}', $.filename) Key.$:States.Format('${DestFilePath}/{}-{}',$.filename,$$.Execution.StartTime) ResultPath: null Next: DeleteInputFile DeleteInputFile: Type: Task Resource: arn:aws:states:::aws-sdk:s3:deleteObject Parameters: Bucket: ${SourceBucket} Key.$: States.Format('${SourcePath}/{}', $.filename) ResultPath: null Next: End End: Type: Pass End: true
Come è ovvio questo servizio non lavora mai da solo ma è studiato per essere integrato con altri servizi, nell’esempio completo si può vedere come EventBridge avvia una funzione lambda che poi esegue la macchina a stati che copia il file e poi lo elabora con un altra funzione lambda:
L’esempio completo funzionante può essere trovato al solito repository:
https://github.com/alnao/AWSCloudFormationExamples/tree/master/Esempio08stepFunction