Práticas recomendadas para Workflows

Consulte as práticas recomendadas listadas aqui ao orquestrar seus serviços usando fluxos de trabalho.

Esta não é uma lista completa de recomendações e não ensina os conceitos básicos de como usar os fluxos de trabalho. Este documento pressupõe que você já tenha um entendimento geral do cenário Google Cloud e dos fluxos de trabalho. Para mais informações, consulte o Framework de arquiteturaGoogle Cloud e a Visão geral dos fluxos de trabalho.

Selecionar um padrão de comunicação ideal

Ao projetar uma arquitetura de microsserviços para implantar vários serviços, é possível selecionar um dos seguintes padrões de comunicação:

  • Comunicação direta entre serviços

  • Comunicação indireta baseada em eventos (também conhecida como coreografia)

  • Configuração, coordenação e gerenciamento automatizados (também conhecidos como orquestração)

Considere os benefícios e as desvantagens de cada uma das opções anteriores e selecione um padrão ideal para seu caso de uso. Por exemplo, a comunicação direta entre serviços pode ser mais simples de implementar do que outras opções, mas ela acopla os serviços de forma muito próxima. Por outro lado, uma arquitetura orientada a eventos permite acoplar os serviços com flexibilidade. No entanto, o monitoramento e a depuração podem ser mais complicados. Por fim, um orquestrador central, como os fluxos de trabalho, embora menos flexível, permite coordenar a comunicação entre serviços sem a acoplagem estreita da comunicação direta de serviço a serviço ou a complexidade de eventos coreografados.

Também é possível combinar padrões de comunicação. Por exemplo, na orquestração orientada a eventos, serviços intimamente relacionados são gerenciados em uma orquestração acionada por um evento. Da mesma forma, é possível projetar um sistema em que uma orquestração resulta em uma mensagem do Pub/Sub para outro sistema orquestrado.

Dicas gerais

Depois de decidir usar o Workflows como orquestrador de serviços, lembre-se destas dicas úteis.

Evite codificar URLs manualmente

Você pode oferecer suporte a fluxos de trabalho portáteis em vários ambientes e mais fáceis de manter evitando URLs codificados. Isso pode ser feito das seguintes maneiras:

  • Defina URLs como argumentos de execução.

    Isso pode ser útil quando o fluxo de trabalho é invocado por uma biblioteca de cliente ou pela API. No entanto, isso não vai funcionar se o fluxo de trabalho for acionado por um evento do Eventarc e o único argumento que pode ser transmitido for o payload do evento.

    Exemplo

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

    Ao executar o fluxo de trabalho, você pode especificar os URLs. Por exemplo:

    gcloud workflows run multi-env --data='{"urls":{"url1": "URL_ONE", "url2": "URL_TWO"}}'
  • Use variáveis de ambiente e crie um fluxo de trabalho que seja configurado dinamicamente de acordo com o ambiente em que ele é implantado. Ou crie um fluxo de trabalho que possa ser reutilizado como um modelo e configurado de acordo com as variáveis de ambiente mantidas separadamente.

  • Use uma técnica de substituição que permite criar um único arquivo de definição de fluxo de trabalho, mas implantar variantes usando uma ferramenta que substitui marcadores de posição no fluxo de trabalho. Por exemplo, é possível usar o Cloud Build para implantar um fluxo de trabalho e, no arquivo de configuração do Cloud Build, adicionar uma etapa para substituir URLs de marcador de posição no fluxo de trabalho.

    Exemplo

    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']

    Em seguida, você pode substituir valores de variáveis no momento da build. Exemplo:

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

    Para mais informações, consulte Enviar um build pela CLI e pela API.

    Também é possível usar o Terraform para provisionar a infraestrutura e definir um arquivo de configuração que cria fluxos de trabalho para cada ambiente usando variáveis de entrada.

    Exemplo

    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 as variáveis são declaradas no módulo raiz da configuração, elas podem ser atribuídas valores de várias maneiras. Por exemplo:

    terraform apply -var="project_id=PROJECT_ID" -var="url1=URL_ONE" -var="url2=URL_TWO"
  • Use o conector do Secret Manager para armazenar e recuperar URLs com segurança.

Usar etapas aninhadas

Cada fluxo de trabalho precisa ter pelo menos uma etapa. Por padrão, o Workflows trata as etapas como se estivessem em uma lista ordenada e as executa uma de cada vez até que todas sejam executadas. Logicamente, algumas etapas precisam ser agrupadas, e você pode usar um bloco steps para aninhar uma série de etapas. Isso é conveniente, porque permite apontar para a etapa atômica correta para processar um conjunto de etapas.

Exemplo

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]}

Unir expressões

Todas as expressões precisam começar com um $ e ser colocadas entre chaves:

${EXPRESSION}

Para evitar problemas de análise YAML, é possível colocar as expressões entre aspas. Por exemplo, expressões com dois-pontos podem causar um comportamento inesperado quando os dois-pontos são interpretados como a definição de um mapa. Para resolver esse problema, coloque a expressão YAML entre aspas simples:

'${"Name: " + myVar}'

Também é possível usar expressões que abrangem várias linhas. Por exemplo, talvez seja necessário colocar uma consulta SQL entre aspas ao usar o conector do BigQuery dos fluxos de trabalho.

Exemplo

- 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

Para conferir a definição completa do fluxo de trabalho, consulte Executar vários jobs do BigQuery em paralelo.

Usar chamadas declarativas

Use os fluxos de trabalho para chamar serviços do próprio fluxo de trabalho e processar os resultados, além de executar tarefas simples, como fazer uma chamada HTTP. Os fluxos de trabalho podem invocar serviços, analisar respostas e criar entradas para outros serviços conectados. Chamar um serviço permite evitar as complicações de invocações extras, dependências adicionais e serviços que chamam serviços. Considere substituir serviços livres de lógica de negócios por chamadas de API declarativas e use fluxos de trabalho para abstrair a complexidade.

No entanto, você precisa criar serviços para fazer qualquer trabalho que seja muito complexo para os fluxos de trabalho. Por exemplo, implementar lógica de negócios reutilizável, cálculos complexos ou transformações que não têm suporte nas expressões dos fluxos de trabalho e na biblioteca padrão. Um caso complicado geralmente é mais fácil de implementar no código do que usar YAML ou JSON e a sintaxe de fluxos de trabalho.

Armazene apenas o que você precisa

Controle o consumo de memória para não encontrar limites de recursos ou um erro que indique isso, como ResourceLimitError, MemoryLimitExceededError ou ResultSizeLimitExceededError.

Seja seletivo sobre o que armazenar em variáveis, filtrando e salvando apenas o que você precisa. Se um serviço retornar um payload muito grande, use uma função separada para fazer a chamada e retornar apenas o que for necessário.

É possível liberar memória limpando variáveis. Por exemplo, você pode liberar a memória necessária para as etapas seguintes. Ou você pode ter chamadas com resultados que não são importantes e pode omitir esses resultados.

É possível limpar uma variável atribuindo null. No YAML, também é possível atribuir um valor vazio ou ~ a uma variável. Isso identifica a memória que pode ser recuperada com segurança.

Exemplo

  - step:
      assign:
        - bigVar:

Usar subfluxos de trabalho e fluxos de trabalho externos

É possível usar subfluxos de trabalho para definir uma parte da lógica ou um conjunto de etapas que você quer chamar várias vezes, simplificando a definição do fluxo de trabalho. Os subfluxos de trabalho são semelhantes a uma função ou rotina em uma linguagem de programação. Eles podem aceitar parâmetros e retornar valores, permitindo que você crie fluxos de trabalho mais complexos com uma gama mais ampla de aplicativos.

Os subfluxos de trabalho são locais para a definição do fluxo de trabalho e não podem ser reutilizados em outros fluxos de trabalho. No entanto, é possível chamar fluxos de trabalho de outros fluxos de trabalho. Os conectores de fluxos de trabalho podem ajudar com isso. Para mais informações, consulte as informações gerais do conector para a API Workflow Executions e a API Workflows.

Usar conectores do Workflows

O Workflows oferece vários conectores que facilitam o acesso a outros produtos Google Cloud em um fluxo de trabalho. Os conectores simplificam os serviços de chamada porque processam a formatação das solicitações, fornecendo métodos e argumentos para que você não precise conhecer os detalhes de uma API Google Cloud . Os conectores também têm um comportamento integrado para processar novas tentativas e operações de longa duração. Assim, você pode evitar iterar e aguardar a conclusão das chamadas. Os conectores cuidam disso para você.

Se você precisar chamar uma API Google Cloud , primeiro verifique se existe um conector do Workflows para ela. Se você não encontrar um conector para um produto Google Cloud , solicite.

Saiba como usar um conector e, para uma referência detalhada dos conectores disponíveis, consulte a Referência de conectores.

Executar etapas do fluxo de trabalho em paralelo

Embora os fluxos de trabalho possam executar etapas sequencialmente, também é possível executar etapas independentes em paralelo. Em alguns casos, isso pode acelerar significativamente a execução do fluxo de trabalho. Para mais informações, consulte Executar etapas do fluxo de trabalho em paralelo.

Aplicar novas tentativas e o padrão de saga

Projete fluxos de trabalho resilientes que possam lidar com falhas temporárias e permanentes de serviço. Erros nos fluxos de trabalho podem ser gerados, por exemplo, por solicitações HTTP, funções, conectores com falha ou gerados pelo seu próprio código de fluxo de trabalho. Adicione o tratamento de erros e as novas tentativas para que uma falha em uma etapa não cause a falha de todo o fluxo de trabalho.

Algumas transações de negócios abrangem vários serviços. Por isso, você precisa de um mecanismo para implementar transações que abrangem serviços. O padrão de design de saga é uma maneira de gerenciar a consistência de dados entre microsserviços em cenários de transações distribuídas. Uma saga é uma sequência de transações que publica um evento para cada transação e aciona a próxima. Se uma transação falhar, a saga vai executar transações de compensação que neutralizam as falhas anteriores na sequência. Teste o tutorial de repetições e padrão de saga em fluxos de trabalho no GitHub.

Usar callbacks para aguardar

Os callbacks permitem que as execuções de fluxo de trabalho aguardem outro serviço para fazer uma solicitação ao endpoint de callback. Essa solicitação retoma a execução do fluxo de trabalho.

Com os callbacks, é possível sinalizar para o fluxo de trabalho que um evento especificado ocorreu e aguardar esse evento sem a necessidade de pesquisa. Por exemplo, é possível criar um fluxo de trabalho que notifique quando um produto está de volta ao estoque ou quando um item é enviado, ou que espera para permitir a interação humana, como revisar um pedido ou validar uma tradução. Também é possível aguardar eventos usando callbacks e gatilhos do Eventarc.

Orquestrar jobs de longa duração

Se você precisar executar cargas de trabalho de processamento em lote de longa duração, use o Batch ou os jobs do Cloud Run. Também é possível usar fluxos de trabalho para gerenciar os serviços. Isso permite combinar vantagens e provisionar e orquestrar todo o processo de maneira eficiente.

O Batch é um serviço totalmente gerenciado que permite programar, enfileirar e executar cargas de trabalho em lote em instâncias de máquina virtual (VM) do Compute Engine. É possível usar o conector de fluxos de trabalho para o Batch (em inglês) para programar e executar um job em lote. Para mais detalhes, consulte o tutorial.

Os jobs do Cloud Run são usados para executar o código que realiza o trabalho (um job) e é encerrado quando o trabalho é concluído. Os fluxos de trabalho permitem executar jobs do Cloud Run como parte de um fluxo de trabalho para realizar processamentos de dados mais complexos ou orquestrar um sistema de jobs atuais. Confira o tutorial que demonstra como usar fluxos de trabalho para executar um job do Cloud Run.

Conteinerizar tarefas de longa duração

É possível automatizar a execução de um contêiner de longa duração usando workflows e o Compute Engine. Por exemplo, é possível criar contêineres de uma tarefa de execução longa para que ela possa ser executada em qualquer lugar e, em seguida, executar o contêiner em uma VM do Compute Engine para a duração máxima de uma execução de fluxo de trabalho (um ano).

Com os fluxos de trabalho, é possível automatizar a criação da VM, a execução do contêiner na VM e a exclusão da VM. Isso permite que você use um servidor e execute um contêiner, mas abstrai a complexidade do gerenciamento de ambos e pode ser útil se você encontrar limitações de tempo ao usar um serviço, como funções do Cloud Run ou o Cloud Run. Teste o tutorial Contêineres de execução longa com fluxos de trabalho e o Compute Engine no GitHub.

Executar ferramentas de linha de comando no Workflows

O Cloud Build é um serviço que executa seus builds no Google Cloud como uma série de etapas de build, em que cada uma delas é executada em um contêiner do Docker. A execução dessas etapas é análoga à execução de comandos em um script.

A Google Cloud CLI inclui as ferramentas de linha de comando gcloud, bq e kubectl, mas não há uma maneira direta de executar comandos da CLI gcloud nos fluxos de trabalho. No entanto, o Cloud Build oferece imagens de contêiner que incluem a CLI gcloud. É possível executar comandos da CLI do gcloud nesses contêineres em uma etapa do Cloud Build e criar essa etapa nos fluxos de trabalho usando o conector do Cloud Build.

Exemplo

Execute gcloud em um fluxo de trabalho:

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

Usar o Terraform para criar seu fluxo de trabalho

O Terraform é uma ferramenta de infraestrutura como código que permite criar, alterar e melhorar de maneira previsível sua infraestrutura em nuvem usando código.

É possível definir e implantar um fluxo de trabalho usando o recurso google_workflows_workflow do Terraform. Para mais informações, consulte Criar um fluxo de trabalho usando o Terraform.

Para ajudar a gerenciar e manter fluxos de trabalho grandes, crie seu fluxo de trabalho em um arquivo YAML separado e importe esse arquivo para o Terraform usando a função templatefile, que lê um arquivo em um determinado caminho e renderiza o conteúdo como um modelo.

Exemplo

  # 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",{})
  }

Da mesma forma, se você tiver um fluxo de trabalho principal que chama vários subfluxos de trabalho, poderá definir o fluxo de trabalho principal e os subfluxos de trabalho em arquivos separados e usar a função templatefile para importá-los.

Exemplo

  # 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",{}
      )])
  }

Se você estiver se referindo a números de linha ao depurar um fluxo de trabalho, todos os arquivos YAML importados pelo arquivo de configuração do Terraform serão mesclados e implantados como um único fluxo de trabalho.

Implantar um fluxo de trabalho de um repositório Git

O Cloud Build usa gatilhos de build para ativar a automação de CI/CD. Você pode configurar acionadores para detectar eventos de entrada, como quando uma nova confirmação é enviada para um repositório ou quando uma solicitação de envio é iniciada, e, em seguida, executar automaticamente um build quando novos eventos chegarem.

É possível usar um acionador do Cloud Build para iniciar automaticamente um build e implantar um fluxo de trabalho de um repositório Git. É possível configurar o gatilho para implantar o fluxo de trabalho em qualquer alteração no repositório de origem ou somente quando a mudança corresponder a critérios específicos.

Essa abordagem pode ajudar a gerenciar o ciclo de vida da implantação. Por exemplo, é possível implantar mudanças em um fluxo de trabalho em um ambiente de preparo, executar testes nesse ambiente e, em seguida, lançar essas mudanças de forma incremental no ambiente de produção. Para mais informações, consulte Implantar um fluxo de trabalho de um repositório do Git usando o Cloud Build.

Otimizar o uso

O custo de execução de um fluxo de trabalho é mínimo. No entanto, para uso de alto volume, aplique as seguintes diretrizes para otimizar o uso e diminuir o custo:

  • Em vez de usar domínios personalizados, verifique se todas as chamadas para serviços Google Cloudusam *.appspot.com, *.cloud.goog, *.cloudfunctions.net ou *.run.app para que você seja cobrado por etapas internas, e não externas.

  • Aplique uma política de nova tentativa personalizada que equilibre suas necessidades de latência e confiabilidade com os custos. As novas tentativas mais frequentes reduzem a latência e aumentam a confiabilidade, mas também podem aumentar os custos.

  • Ao usar conectores que aguardam operações de longa duração, defina uma política de pesquisa personalizada que otimize a latência para custo. Por exemplo, se você espera que uma operação leve mais de uma hora, talvez seja melhor usar uma política que inicialmente pesquisa após um minuto em caso de falha imediata e, em seguida, a cada 15 minutos.

  • Combine atribuições em uma etapa.

  • Evite o uso excessivo de etapas sys.log. Use o registro de chamadas.

Resumo das práticas recomendadas

A tabela a seguir resume as dicas gerais e as práticas recomendadas neste documento.

Dicas gerais
Práticas recomendadas

A seguir