Best practice per Workflows

Puoi fare riferimento alle best practice elencate qui quando orchestri i tuoi servizi utilizzando Workflows.

Questo non è un elenco esaustivo di consigli e non ti insegna le basi di come utilizzareWorkflowso. Questo documento presuppone che tu abbia già una conoscenza generale del panorama Google Cloud e di Workflows. Per saperne di più, consulta il Google Cloud framework Well-Architected e la panoramica dei workflow.

Selezionare un pattern di comunicazione ottimale

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

  • Comunicazione diretta tra servizi

  • 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 seleziona un pattern ottimale per il tuo caso d'uso. Ad esempio, la comunicazione diretta tra servizi 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'accoppiamento stretto della comunicazione diretta da servizio a servizio o la complessità degli eventi coreografati.

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

Suggerimenti di carattere generale

Una volta deciso di utilizzare Workflows come orchestratore di servizi, tieni presente i seguenti suggerimenti utili.

Evitare di codificare gli URL

Puoi supportare flussi di lavoro 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 flusso di lavoro viene richiamato tramite una libreria client o l'API. Tuttavia, questa operazione non funzionerà se il flusso di lavoro 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 flusso di lavoro, 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 flusso di lavoro configurato dinamicamente a seconda dell'ambiente in cui viene implementato. In alternativa, crea un workflow che possa essere riutilizzato come modello e configurato in base a variabili di ambiente gestite separatamente.

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

    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 al momento della creazione. Ad esempio:

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

    Per saperne di più, consulta la sezione Invia una build tramite CLI e API.

    In alternativa, puoi utilizzare Terraform per eseguire il provisioning dell'infrastruttura e definire un file di configurazione che crea flussi di lavoro per ogni ambiente utilizzando 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, possono essere assegnati 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.

Utilizzare i passaggi nidificati

Ogni workflow deve avere almeno un passaggio. Per impostazione predefinita, Workflows trattano i passaggi come se fossero in un elenco ordinato e li eseguono 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. Questa operazione è comoda perché ti consente di indicare il 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]}

Espressioni aggregate

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 il 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 flusso di lavoro, consulta Esegui più job BigQuery in parallelo.

Utilizzare 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 possono richiamare servizi, analizzare le risposte e creare input per altri servizi connessi. Chiamare un servizio ti consente di evitare le complicazioni di invocazioni aggiuntive, dipendenze aggiuntive e servizi che chiamano 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 lavoro troppo complesso per i workflow, ad esempio l'implementazione di logica di business riutilizzabile, calcoli complessi o trasformazioni non supportate dalle espressioni di Workflows e dalla relativa libreria standard. Un caso complesso è in genere 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 per non superare i limiti delle risorse o ricevere un messaggio di errore che lo indica, ad esempio ResourceLimitError, MemoryLimitExceededError o ResultSizeLimitExceededError.

Seleziona con cura i dati da archiviare nelle variabili, filtrando e memorizzando 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. Oppure, potresti avere chiamate con risultati che non ti interessano e puoi ometterli del tutto.

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

Esempio

  - step:
      assign:
        - bigVar:

Utilizzare i flussi di lavoro secondari e quelli esterni

Puoi utilizzare i flussi di lavoro secondari per definire una parte di logica o un insieme di passaggi che vuoi chiamare più volte, semplificando la definizione del flusso di lavoro. I subworkflow 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 flussi di lavoro secondari sono locali alla definizione del flusso di lavoro e non possono essere riutilizzati in altri flussi di lavoro. Tuttavia, puoi chiamare i workflow da altri workflow. I connettori di Workflows possono aiutarti in questo. Per ulteriori informazioni, consulta le panoramiche dei connettori per l'API Workflow Executions e l'API Workflows.

Utilizzare i connettori di Workflows

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

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

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

Esegui i passaggi del workflow in parallelo

Anche se Workflows può eseguire i passaggi in sequenza, puoi anche eseguire passaggi indipendenti in parallelo. In alcuni casi, questo può velocizzare notevolmente l'esecuzione del flusso di lavoro. Per saperne di più, vedi Eseguire i passaggi del flusso di lavoro in parallelo.

Applicare i nuovi tentativi e il pattern saga

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

Alcune transazioni aziendali si estendono su più servizi, quindi hai bisogno di un meccanismo per implementare transazioni che si estendono su più servizi. Il pattern 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 va a buon fine, la saga esegue transazioni di compensazione che contrastano gli errori precedenti nella sequenza. Prova il tutorial sui tentativi e sul pattern Saga in Workflows su GitHub.

Utilizzare i callback per attendere

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

Con i callback, puoi segnalare al tuo flusso di lavoro che si è verificato un evento specificato e attendere l'evento senza polling. Ad esempio, puoi creare un flusso di lavoro che ti avvisa quando un prodotto è di nuovo disponibile o quando un articolo è stato spedito oppure che attende 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 Eventarc.

Orchestrare job di lunga durata

Se devi eseguire carichi di lavoro di elaborazione batch a lunga esecuzione, puoi utilizzare Batch o Cloud Run Jobs e puoi utilizzare Workflows per gestire i servizi. In questo modo puoi combinare i vantaggi e 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 maggiori dettagli, prova il tutorial.

I job Cloud Run vengono utilizzati per eseguire codice che svolge un lavoro (un job) e terminano quando il lavoro è completato. Workflows consente di eseguire job Cloud Run all'interno di un flusso di lavoro 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à di lunga durata

Puoi automatizzare l'esecuzione di un container a lunga esecuzione utilizzando Workflows e Compute Engine. Ad esempio, puoi inserire in un container 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 flusso di lavoro (un anno).

Utilizzando i flussi di lavoro, 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 viene astratta la complessità della gestione di entrambi e può essere utile se riscontri limiti di tempo quando utilizzi un servizio come Cloud Run Functions o Cloud Run. Prova l'esercitazione Long running containers with Workflows and Compute Engine su GitHub.

Eseguire strumenti a riga di comando da Workflows

Cloud Build è un servizio che esegue le tue build su Google Cloud come una serie di passaggi di build, ognuno dei quali viene eseguito in un container Docker. L'esecuzione di passaggi di build è analoga all'esecuzione di comandi in uno script.

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 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")}

Utilizzare Terraform per creare il flusso di lavoro

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

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

Per aiutarti a gestire e mantenere i flussi di lavoro di grandi dimensioni, puoi creare il flusso di lavoro in un file YAML separato e importarlo in Terraform utilizzando la funzione templatefile che legge un file in un determinato percorso e ne esegue il rendering dei contenuti 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 flusso di lavoro principale che chiama più flussi di lavoro secondari, puoi definire il flusso di lavoro principale e i flussi di lavoro 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 flusso di lavoro, tutti i file YAML importati tramite il file di configurazione Terraform vengono uniti e implementati come un unico flusso di lavoro.

Esegui il deployment di un flusso di lavoro da un repository Git

Cloud Build utilizza i trigger di build per abilitare l'automazione CI/CD. Puoi configurare gli attivatori in modo che rilevino gli eventi in entrata, ad esempio quando viene eseguito il push di un nuovo commit in un repository o quando viene avviata una richiesta pull, e poi 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 flusso di lavoro da un repository Git. Puoi configurare il trigger per eseguire il deployment del flusso di lavoro 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 flusso di lavoro in un ambiente di staging, eseguire test su quell'ambiente e poi lanciare in modo incrementale queste modifiche nell' ambiente di produzione. Per maggiori informazioni, consulta Eseguire il deployment di un flusso di lavoro da un repository Git utilizzando Cloud Build.

Ottimizzare l'utilizzo

Il costo per eseguire un flusso di lavoro è minimo. Tuttavia, per un utilizzo ad alto volume, applica le seguenti linee guida per ottimizzare l'utilizzo e ridurre i costi:

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

  • Applica un criterio di ripetizione personalizzato che bilanci le tue 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 connettori che attendono operazioni a lunga esecuzione, imposta un'apposita strategia di polling che ottimizzi la latenza in funzione del costo. Ad esempio, se prevedi che un'operazione richieda più di un'ora, potresti volere 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 l'uso eccessivo di passaggi sys.log. Prendi in considerazione l'utilizzo della registrazione delle chiamate.

Riepilogo delle best practice

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

Suggerimenti di carattere generale
Best practice

Passaggi successivi