Best practice per Workflows

Quando orchestri i tuoi servizi utilizzando Workflows, puoi fare riferimento alle best practice elencate qui.

Non si tratta di un elenco esaustivo di consigli e non ti insegna le nozioni di base su come utilizzare Workflows. Questo documento presuppone che tu abbia già una conoscenza generale del panorama complessivo e Google Cloud di Workflows. Per saperne di più, consulta il Google Cloud framework Well-Architected e la panoramica di Workflows.

Seleziona un modello di comunicazione ottimale

Quando progetti un'architettura di microservizi per il deployment di più servizi, puoi scegliere tra i seguenti modelli di comunicazione:

  • Comunicazione diretta service-to-service

  • Comunicazione indiretta basata su eventi (nota anche come coreografia)

  • Configurazione, coordinamento e gestione automatizzati (noti anche come orchestrazione)

Assicurati di considerare i vantaggi e gli svantaggi di ciascuna delle opzioni precedenti e di selezionare un modello ottimale per il tuo caso d'uso. Ad esempio, la comunicazione diretta service-to-service potrebbe essere più semplice da implementare rispetto ad altre opzioni, ma accoppia strettamente i tuoi servizi. Al contrario, un' architettura basata su eventi consente di accoppiare in modo lasco i servizi; tuttavia, il monitoraggio e il debug potrebbero essere più complicati. Infine, un orchestratore centrale come Workflows, sebbene meno flessibile, consente di coordinare la comunicazione tra i servizi senza l'alto accoppiamento della comunicazione diretta service-to-service o la complessità degli eventi coreografati.

Puoi anche combinare i modelli di comunicazione. Ad esempio, nell'orchestrazione basata su eventi, i servizi strettamente correlati vengono gestiti in un'orchestrazione che è attivata da un evento. Allo stesso modo, potresti progettare un sistema in cui un'orchestrazione genera un messaggio Pub/Sub a un altro sistema orchestrato.

Suggerimenti di carattere generale

Dopo aver deciso di utilizzare Workflows come orchestratore di servizi, tieni presente i seguenti suggerimenti utili.

Evita di codificare gli URL

Puoi supportare workflow portatili in più ambienti e più facili da gestire evitando gli URL hardcoded. Puoi farlo nei seguenti modi:

  • Definisci gli URL come argomenti di runtime.

    Questo può essere utile quando il workflow viene richiamato tramite una libreria client o l'API. (Tuttavia, questa operazione non funzionerà se il workflow viene attivato da un evento di Eventarc e l'unico argomento che può essere passato è il payload dell'evento.)

    Esempio

    main:
      params: [args]
      steps:
        - init:
            assign:
              - url1: ${args.urls.url1}
              - url2: ${args.urls.url2}

    Quando esegui il workflow, puoi specificare gli URL. Ad esempio:

    gcloud workflows run multi-env --data='{"urls":{"url1": "URL_ONE", "url2": "URL_TWO"}}'
  • Utilizza le variabili di ambiente e crea un workflow configurato dinamicamente a seconda dell'ambiente in cui viene eseguito il deployment. In alternativa, crea un workflow che può essere riutilizzato come modello e configurato in base alle variabili di ambiente gestite separatamente.

  • Utilizza una tecnica di sostituzione che ti consente di creare un singolo file di definizione del workflow, ma di eseguire il deployment delle varianti utilizzando uno strumento che sostituisce i segnaposto nel workflow. Ad esempio, puoi utilizzare Cloud Build per eseguire il deployment di un workflow e, nel file di configurazione di Cloud Build, aggiungere un passaggio per sostituire gli URL dei segnaposto nel workflow.

    Esempio

    steps: id: 'replace-urls'
      name: 'gcr.io/cloud-builders/gcloud'
      entrypoint: bash
      args:
        - -c
        - |
          sed -i -e "s~REPLACE_url1~$_URL1~" workflow.yaml
          sed -i -e "s~REPLACE_url2~$_URL2~" workflow.yaml id: 'deploy-workflow'
      name: 'gcr.io/cloud-builders/gcloud'
      args: ['workflows', 'deploy', 'multi-env-$_ENV', '--source', 'workflow.yaml']

    Puoi quindi sostituire i valori delle variabili in fase di compilazione. Ad esempio:

    gcloud builds submit --config cloudbuild.yaml \
        --substitutions=_ENV=staging,_URL1="URL_ONE",_URL2="URL_TWO"

    Per saperne di più, consulta Inviare una build tramite interfaccia a riga di comando e API.

    In alternativa, puoi utilizzare Terraform per eseguire il provisioning dell'infrastruttura e definire un file di configurazione che crea workflow per ogni ambiente utilizzando le variabili di input.

    Esempio

    variable "project_id" {
      type = string
    }
    
    variable "url1" {
      type = string
    }
    
    variable "url2" {
      type = string
    }
    
    locals {
      env = ["staging", "prod"]
    }
    
    # Define and deploy staging and production workflows
    resource "google_workflows_workflow" "multi-env-workflows" {
      for_each = toset(local.env)
    
      name            = "multi-env-${each.key}"
      project         = var.project_id
      region          = "us-central1"
      source_contents = templatefile("${path.module}/workflow.yaml", { url1 : "${var.url1}-${each.key}", url2 : "${var.url2}-${each.key}" })
    }

    Quando le variabili vengono dichiarate nel modulo principale della configurazione, è possibile assegnare loro valori in diversi modi. Ad esempio:

    terraform apply -var="project_id=PROJECT_ID" -var="url1=URL_ONE" -var="url2=URL_TWO"
  • Utilizza il connettore Secret Manager per archiviare in modo sicuro gli URL in Secret Manager e recuperarli.

Utilizza i passaggi nidificati

Ogni workflow deve avere almeno un passaggio. Per impostazione predefinita, Workflows considera i passaggi come se fossero in un elenco ordinato e li esegue uno alla volta finché non sono stati eseguiti tutti. Dal punto di vista logico, alcuni passaggi devono essere raggruppati e puoi utilizzare un blocco steps per nidificare una serie di passaggi. Questo è utile perché ti consente di puntare al passaggio atomico corretto per elaborare un insieme di passaggi.

Esempio

main:
    params: [input]
    steps:
    - callWikipedia:
        steps:
        - checkSearchTermInInput:
            switch:
                - condition: ${"searchTerm" in input}
                  assign:
                    - searchTerm: ${input.searchTerm}
                  next: readWikipedia
        - getCurrentDate:
            call: http.get
            args:
                url: https://timeapi.io/api/Time/current/zone?timeZone=Europe/Amsterdam
            result: currentDate
        - setFromCallResult:
            assign:
                - searchTerm: ${currentDate.body.dayOfWeek}
        - readWikipedia:
            call: http.get
            args:
                url: https://en.wikipedia.org/w/api.php
                query:
                    action: opensearch
                    search: ${searchTerm}
            result: wikiResult
    - returnOutput:
            return: ${wikiResult.body[1]}

Aggrega le espressioni

Tutte le espressioni devono iniziare con un $ ed essere racchiuse tra parentesi graffe:

${EXPRESSION}

Per evitare problemi di analisi YAML, puoi racchiudere le espressioni tra virgolette. Ad esempio, le espressioni contenenti i due punti possono causare un comportamento imprevisto quando i due punti vengono interpretati come definizione di una mappa. Puoi risolvere questo problema racchiudendo l'espressione YAML tra virgolette singole:

'${"Name: " + myVar}'

Puoi anche utilizzare espressioni che si estendono su più righe. Ad esempio, potresti dover racchiudere una query SQL tra virgolette quando utilizzi il connettore BigQuery di Workflows.

Esempio

- runQuery:
    call: googleapis.bigquery.v2.jobs.query
    args:
        projectId: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}
        body:
            useLegacySql: false
            useQueryCache: false
            timeoutMs: 30000
            # Find top 100 titles with most views on Wikipedia
            query: ${
                "SELECT TITLE, SUM(views)
                FROM `bigquery-samples.wikipedia_pageviews." + table + "`
                WHERE LENGTH(TITLE) > 10
                GROUP BY TITLE
                ORDER BY SUM(VIEWS) DESC
                LIMIT 100"
                }
    result: queryResult

Per la definizione completa del workflow, consulta Eseguire più job BigQuery in parallelo.

Utilizza le chiamate dichiarative

Utilizza Workflows per chiamare i servizi dal workflow stesso e gestire i risultati, nonché per eseguire attività semplici come effettuare una chiamata HTTP. Workflows può richiamare i servizi, analizzare le risposte e creare input per altri servizi connessi. La chiamata a un servizio consente di evitare le complicazioni di chiamate aggiuntive, dipendenze aggiuntive e servizi che chiamano altri servizi. Valuta la possibilità di sostituire i servizi privi di logica di business con chiamate API dichiarative e utilizza Workflows per astrarre la complessità.

Tuttavia, devi creare servizi per eseguire qualsiasi attività troppo complessa per Workflows, ad esempio l'implementazione di logica di business riutilizzabile, calcoli complessi o trasformazioni non supportate da espressioni di Workflows e dalla relativa libreria standard. In genere, un caso complicato è più facile da implementare nel codice anziché utilizzare YAML o JSON e la sintassi di Workflows.

Archivia solo ciò che ti serve

Tieni sotto controllo il consumo di memoria in modo da non superare i limiti delle risorse o riscontrare un errore che indica questo, ad esempio ResourceLimitError, MemoryLimitExceededError, o ResultSizeLimitExceededError.

Seleziona attentamente ciò che archivi nelle variabili, filtrando e archiviando solo ciò che ti serve. Se un servizio restituisce un payload troppo grande, utilizza una funzione separata per effettuare la chiamata e restituire solo ciò che è necessario.

Puoi liberare memoria cancellando le variabili. Ad esempio, potresti voler liberare la memoria necessaria per i passaggi successivi. In alternativa, potresti avere chiamate con risultati che non ti interessano e puoi ometterli completamente.

Puoi cancellare una variabile assegnando null. In YAML, puoi anche assegnare un valore vuoto o ~ a una variabile. In questo modo viene identificata la memoria che può essere recuperata in modo sicuro.

Esempio

  - step:
      assign:
        - bigVar:

Utilizza i workflow secondari e i workflow esterni

Puoi utilizzare i workflow secondari per definire una parte di logica o un insieme di passaggi che vuoi chiamare più volte, semplificando la definizione del workflow. I workflow secondari sono simili a una funzione o a una routine in un linguaggio di programmazione. Possono accettare parametri e restituire valori, consentendoti di creare workflow più complessi con una gamma più ampia di applicazioni.

Tieni presente che i workflow secondari sono locali alla definizione del workflow e non possono essere riutilizzati in altri workflow. Tuttavia, puoi chiamare i workflow da altri workflow. I connettori di Workflows possono aiutarti in questo. Per saperne di più, consulta le panoramiche dei connettori per l' API Workflow Executions e l'API Workflows.

Utilizza i connettori di Workflows

Workflows fornisce una serie di connettori che semplificano l'accesso ad altri Google Cloud prodotti all'interno di un workflow. I connettori semplificano la chiamata ai servizi perché gestiscono la formattazione delle richieste, fornendo metodi e argomenti in modo che tu non debba conoscere i dettagli di un' Google Cloud API. I connettori hanno anche un comportamento integrato per la gestione dei nuovi tentativi e delle operazioni a lunga esecuzione, in modo da evitare di eseguire l'iterazione e attendere il completamento delle chiamate; i connettori si occupano di questo.

Se devi chiamare un' Google Cloud API, controlla prima se esiste un connettore Workflows per questa. Se non vedi un connettore per un Google Cloud prodotto, puoi richiederlo.

Scopri come utilizzare un connettore e, per un riferimento dettagliato dei connettori disponibili, consulta la documentazione di riferimento dei connettori.

Esegui i passaggi del workflow in parallelo

Sebbene Workflows possa eseguire i passaggi in sequenza, puoi anche eseguire passaggi indipendenti in parallelo. In alcuni casi, questo può accelerare notevolmente l'esecuzione del workflow. Per saperne di più, consulta Eseguire i passaggi del workflow in parallelo.

Applica i nuovi tentativi e il modello Saga

Progetta workflow resilienti in grado di gestire sia gli errori di servizio temporanei che quelli permanenti. Gli errori per Workflows potrebbero essere generati, ad esempio, da richieste HTTP, funzioni o connettori non riusciti oppure dal codice del workflow. Aggiungi la gestione degli errori e i nuovi tentativi in modo che un errore in un passaggio non causi il fallimento dell'intero workflow.

Alcune transazioni aziendali interessano più servizi, quindi è necessario un meccanismo per implementare transazioni che interessano più servizi. Il modello di progettazione Saga è un modo per gestire la coerenza dei dati tra i microservizi in scenari di transazioni distribuite. Una saga è una sequenza di transazioni che pubblica un evento per ogni transazione e che attiva la transazione successiva. Se una transazione non riesce, la saga esegue transazioni di compensazione che contrastano gli errori precedenti nella sequenza. Prova il tutorial Nuovi tentativi e modello Saga in Workflows su GitHub.

Utilizza i callback per attendere

I callback consentono alle esecuzioni del workflow di attendere che un altro servizio effettui una richiesta all' endpoint di callback; questa richiesta riprende l'esecuzione del workflow.

Con i callback, puoi segnalare al workflow che si è verificato un evento specificato e attendere l'evento senza eseguire il polling. Ad esempio, puoi creare un workflow che ti avvisa quando un prodotto è di nuovo disponibile o quando un articolo è stato spedito oppure che attende di consentire l'interazione umana ad esempio la revisione di un ordine o la convalida di una traduzione. Puoi anche attendere gli eventi utilizzando i callback e i trigger di Eventarc.

Orchestra i job a lunga esecuzione

Se devi eseguire carichi di lavoro di elaborazione batch a lunga esecuzione , puoi utilizzare Batch o i job Cloud Run e puoi utilizzare Workflows per gestire i servizi. In questo modo puoi combinare i vantaggi ed eseguire il provisioning e l'orchestrazione dell'intero processo in modo efficiente.

Batch è un servizio completamente gestito che consente di pianificare, inserire in coda ed eseguire carichi di lavoro batch su istanze di macchine virtuali (VM) Compute Engine. Puoi utilizzare il connettore Workflows per Batch per pianificare ed eseguire un job Batch. Per i dettagli, prova out il tutorial.

I job Cloud Run vengono utilizzati per eseguire codice che svolge un'attività (un job) e si chiude al termine dell'attività. Workflows consente di eseguire job Cloud Run all'interno di un workflow per portare a termine elaborazioni di dati più complesse o orchestrare un sistema di job esistenti. Prova il tutorial che mostra come utilizzare Workflows per eseguire un job Cloud Run.

Containerizza le attività a lunga esecuzione

Puoi automatizzare l'esecuzione di un container a lunga esecuzione utilizzando Workflows e Compute Engine. Ad esempio, puoi containerizzare un'attività a lunga esecuzione in modo che possa essere eseguita ovunque, quindi eseguire il container su una VM Compute Engine per la durata massima di un'esecuzione del workflow (un anno).

Utilizzando Workflows, puoi automatizzare la creazione della VM, l'esecuzione del container sulla VM e l'eliminazione della VM. In questo modo puoi utilizzare un server ed eseguire un container, ma la complessità della gestione di entrambi viene astratta e può essere utile se riscontri limiti di tempo quando utilizzi un servizio come le funzioni Cloud Run o Cloud Run. Prova il tutorial Container a lunga esecuzione con Workflows e Compute Engine su GitHub.

Esegui strumenti a riga di comando da Workflows

Cloud Build è un servizio che esegue le build come una serie di passaggi di build, dove ogni passaggio viene eseguito in un container Docker. Google Cloud L'esecuzione di passaggi di build è analoga all'esecuzione di comandi in uno script.

La Google Cloud CLI include gli strumenti a riga di comando gcloud, bq e kubectl, ma non esiste un modo diretto per eseguire i comandi gcloud CLI da Workflows. Tuttavia, Cloud Build fornisce immagini container che includono gcloud CLI. Puoi eseguire i comandi gcloud CLI in questi container da un passaggio di Cloud Build, e puoi creare questo passaggio in Workflows utilizzando il connettore Cloud Build.

Esempio

Esegui gcloud in un workflow:

# This example shows how to execute gcloud commands from Workflows
# using Cloud Build and returns the output

main:
  steps:
  - execute_command:
      call: gcloud
      args:
          args: "workflows list"
      result: result
  - return_result:
      return: ${result}

gcloud:
  params: [args]
  steps:
  - create_build:
      call: googleapis.cloudbuild.v1.projects.builds.create
      args:
        projectId: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}
        parent: ${"projects/" + sys.get_env("GOOGLE_CLOUD_PROJECT_ID") + "/locations/global"}
        body:
          serviceAccount: ${sys.get_env("GOOGLE_CLOUD_SERVICE_ACCOUNT_NAME")}
          options:
            logging: CLOUD_LOGGING_ONLY
          steps:
          - name: gcr.io/google.com/cloudsdktool/cloud-sdk
            entrypoint: /bin/bash
            args: ${["-c", "gcloud " + args + " > $$BUILDER_OUTPUT/output"]}
      result: result_builds_create
  - return_build_result:
      return: ${text.split(text.decode(base64.decode(result_builds_create.metadata.build.results.buildStepOutputs[0])), "\n")}

Run kubectl in a workflow:

# This example shows how to execute kubectl commands from Workflows
# using Cloud Build and returns the output

main:
  steps:
  - execute_command:
      call: kubectl
      args:
          args: "--help"
      result: result
  - return_result:
      return: ${result}

kubectl:
  params: [args]
  steps:
  - create_build:
      call: googleapis.cloudbuild.v1.projects.builds.create
      args:
        projectId: ${sys.get_env("GOOGLE_CLOUD_PROJECT_ID")}
        parent: ${"projects/" + sys.get_env("GOOGLE_CLOUD_PROJECT_ID") + "/locations/global"}
        body:
          serviceAccount: ${sys.get_env("GOOGLE_CLOUD_SERVICE_ACCOUNT_NAME")}
          options:
            logging: CLOUD_LOGGING_ONLY
          steps:
          - name: gcr.io/cloud-builders/kubectl
            entrypoint: /bin/bash
            args: ${["-c", "kubectl " + args + " > $$BUILDER_OUTPUT/output"]}
      result: result_builds_create
  - return_build_result:
      return: ${text.split(text.decode(base64.decode(result_builds_create.metadata.build.results.buildStepOutputs[0])), "\n")}

Utilizza Terraform per creare il workflow

Terraform è uno strumento Infrastructure as Code che consente di creare, modificare e migliorare in modo prevedibile l'infrastruttura cloud utilizzando il codice.

Puoi definire ed eseguire il deployment di un workflow utilizzando la risorsa Terraform google_workflows_workflow. Per saperne di più, consulta Creare un workflow utilizzando Terraform.

Per aiutarti a gestire e gestire i workflow di grandi dimensioni, puoi creare il workflow in un file YAML separato e importarlo in Terraform utilizzando la templatefile funzione che legge un file in un determinato percorso e ne esegue il rendering del contenuto come modello.

Esempio

  # Define a workflow
  resource "google_workflows_workflow" "workflows_example" {
    name            = "sample-workflow"
    region          = var.region
    description     = "A sample workflow"
    service_account = google_service_account.workflows_service_account.id
    # Import main workflow YAML file
    source_contents = templatefile("${path.module}/workflow.yaml",{})
  }

Allo stesso modo, se hai un workflow principale che chiama più workflow secondari, puoi definire il workflow principale e i workflow secondari in file separati e utilizzare la funzione templatefile per importarli.

Esempio

  # Define a workflow
  resource "google_workflows_workflow" "workflows_example" {
    name            = "sample-workflow"
    region          = var.region
    description     = "A sample workflow"
    service_account = google_service_account.workflows_service_account.id
    # Import main workflow and subworkflow YAML files
    source_contents = join("", [
      templatefile(
        "${path.module}/workflow.yaml",{}
      ),

      templatefile(
        "${path.module}/subworkflow.yaml",{}
      )])
  }

Tieni presente che, se fai riferimento ai numeri di riga durante il debug di un workflow, tutti i file YAML importati tramite il file di configurazione di Terraform vengono uniti ed eseguiti come un singolo workflow.

Esegui il deployment di un workflow da un repository Git

Cloud Build utilizza i trigger di build per abilitare l'automazione CI/CD. Puoi configurare i trigger in modo che ascoltino gli eventi in entrata, ad esempio quando viene eseguito il push di un nuovo commit in un repository o quando viene avviata una richiesta di pull, e quindi eseguire automaticamente una build quando arrivano nuovi eventi.

Puoi utilizzare un trigger di Cloud Build per avviare automaticamente una build ed eseguire il deployment di un workflow da un repository Git. Puoi configurare il trigger in modo che esegua il deployment del workflow per ogni modifica al repository di codice sorgente oppure solo quando la modifica soddisfa criteri specifici.

Questo approccio può aiutarti a gestire il ciclo di vita del deployment. Ad esempio, puoi eseguire il deployment delle modifiche a un workflow in un ambiente di staging, eseguire test su questo ambiente e quindi avviare in modo incrementale queste modifiche nell'ambiente di produzione. Per saperne di più, consulta Eseguire il deployment di un workflow da un repository Git utilizzando Cloud Build.

Ottimizza l'utilizzo

Il costo di esecuzione di un workflow è minimo. Tuttavia, per un utilizzo di volumi elevati, applica le seguenti linee guida per ottimizzare l'utilizzo e ridurre i costi:

  • Anziché utilizzare domini personalizzati, assicurati che tutte le chiamate ai Google Cloud servizi utilizzino *.appspot.com, *.cloud.goog, *.cloudfunctions.net, o *.run.app in modo che ti vengano addebitati i passaggi interni e non quelli esterni.

  • Applica una policy di nuovi tentativi personalizzata che bilanci le esigenze di latenza e affidabilità con i costi. I nuovi tentativi più frequenti riducono la latenza e aumentano l'affidabilità, ma possono anche aumentare i costi.

  • Quando utilizzi i connettori che attendono operazioni a lunga esecuzione, imposta una policy di polling personalizzata che ottimizzi la latenza per i costi. Ad esempio, se prevedi che un'operazione richieda più di un'ora, potresti voler utilizzare una policy che esegua il polling inizialmente dopo un minuto in caso di errore immediato e poi ogni 15 minuti.

  • Combina le assegnazioni in un unico passaggio.

  • Evita un utilizzo eccessivo dei passaggi sys.log. Valuta la possibilità di utilizzare il logging delle chiamate invece.

  • Scopri quali operazioni sono considerate un passaggio. Le operazioni che non vengono conteggiate come passaggi a sé stanti vengono conteggiate quando vengono utilizzate all'interno di un passaggio applicabile. Ad esempio, quanto segue viene conteggiato come un passaggio:

    - type_check:
        return: if(get_type((int("6"))) == integer, 1, 2)
    

    Le operazioni chiave che vengono conteggiate e non conteggiate per il limite massimo di passaggi sono classificate nella tabella seguente:

    Categoria Operazione
    Conteggiato come passaggio
    • Operazioni sui dati: assegnazione, restituzione di valori
    • Controllo del flusso: salti (next), switch, avvio di un for ciclo e ogni iterazione di un for ciclo
    • Chiamate: richiamo di sys.get_env o di un'altra funzione della libreria standard funzione, di un altro workflow o di un connettore
    • Contemporaneità: generazione di thread ed esecuzione parallela
    • Gestione degli errori: ogni blocco raise, try, retry e except viene conteggiato come un passaggio separato, anche se altre operazioni fanno parte dello stesso passaggio più grande.

      Ad esempio, un passaggio che include un blocco try con un'operazione di chiamata viene conteggiato come tre passaggi: uno per il passaggio principale, uno per il tentativo e uno per la chiamata. L'aggiunta di un blocco retry aggiunge altri tre passaggi (uno per il nuovo tentativo, uno per il tentativo e uno per la chiamata), per un totale di sei passaggi.

    Non conteggiato come passaggio

Riepilogo delle best practice

La tabella seguente riassume i suggerimenti generali e le best practice consigliate in questo documento.

Suggerimenti di carattere generale
Best practice

Passaggi successivi