Workflows 最佳实践

在使用 Workflows 编排服务时,您可以参考此处列出的最佳实践。

此处列出的建议并非详尽无遗,也不会向您介绍如何使用工作流的基本知识。本文档假定您已经大致了解了整体 Google Cloud 环境和 Workflows。如需了解详情,请参阅 Google Cloud 架构框架工作流概览

选择最优的通信模式

在设计用于部署多项服务的微服务架构时,您可以从以下通信模式中进行选择:

  • 直接服务到服务通信

  • 间接事件驱动型通信(也称为编排

  • 自动配置、协调和管理(也称为编排

请务必考虑上述每种方案的优点和缺点,并为您的应用场景选择最佳模式。例如,与其他选项相比,直接服务到服务通信的实现可能更简单,但会使服务之间紧密耦合。相比之下,事件驱动型架构可让您松散耦合服务;不过,监控和调试可能更为复杂。最后,虽然工作流等集中协调程序的灵活性较低,但可让您协调服务之间的通信,而无需直接服务间通信的紧密耦合或复杂的编排事件。

您还可以组合使用通信模式。例如,在事件驱动型编排中,密切相关的服务在由事件触发的编排中进行管理。同样,您可以设计一个系统,其中一个协调会导致向另一个协调系统发送 Pub/Sub 消息

常规提示

决定使用 Workflows 作为服务编排器后,请牢记以下实用提示。

避免对网址进行硬编码

通过避免使用硬编码的网址,您可以支持可在多个环境中移植且更易于维护的工作流。您可以通过以下方式实现这一点:

  • 将网址定义为运行时参数

    当通过客户端库或 API 调用工作流时,这可能会很有用。(不过,如果您的工作流由 Eventarc 中的事件触发,并且唯一可传递的参数是事件载荷,则此方法不适用。)

    示例

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

    运行工作流时,您可以指定网址。例如:

    gcloud workflows run multi-env --data='{"urls":{"url1": "URL_ONE", "url2": "URL_TWO"}}'
  • 使用环境变量,创建一个根据部署环境动态配置的工作流。或者,创建一个可作为模板重复使用的工作流,并根据单独维护的环境变量进行配置。

  • 使用替换技术,您可以创建单个工作流定义文件,但通过用于替换工作流中的占位符的工具部署变体。例如,您可以使用 Cloud Build 部署工作流,并在 Cloud Build 配置文件中添加一个步骤来替换工作流中的占位符网址。

    示例

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

    然后,您可以在构建时 替换变量值。例如:

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

    如需了解详情,请参阅通过 CLI 和 API 提交 build

    或者,您也可以使用 Terraform 预配基础架构,并定义一个配置文件,以便使用输入变量为每个环境创建工作流。

    示例

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

    在配置的根模块中声明变量后,可以通过多种方式为其分配值。例如

    terraform apply -var="project_id=PROJECT_ID" -var="url1=URL_ONE" -var="url2=URL_TWO"
  • 使用 Secret Manager 连接器在 Secret Manager 中安全地存储和检索网址。

使用嵌套步骤

每个工作流都必须至少有一个步骤。默认情况下,Workflows 会将步骤视为有序列表并逐个执行,直到所有步骤都运行完毕。从逻辑上讲,某些步骤应归为一组,您可以使用 steps 块嵌套一系列步骤。这样做很方便,因为您可以指向正确的原子步骤来处理一组步骤。

示例

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

换行表达式

所有表达式都必须以 $ 开头,并用大括号括起来:

${EXPRESSION}

为避免 YAML 解析问题,您可以使用引号将表达式括起来。例如,当英文冒号被解读为定义某个映射时,包含英文冒号的表达式可能会导致意外行为。要解决此问题,您可以使用英文单引号将 YAML 表达式括起来:

'${"Name: " + myVar}'

您还可以使用跨多行的表达式。例如,使用 Workflows BigQuery 连接器时,您可能需要将 SQL 查询括在引号中。

示例

- 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

如需查看完整的工作流定义,请参阅并行运行多个 BigQuery 作业

使用声明式调用

使用 Workflows 从工作流本身调用服务并处理结果,以及执行发出 HTTP 调用等简单任务。工作流可以调用服务、解析响应以及为其他连接的服务构建输入。调用服务可以避免产生额外调用、额外依赖项和调用服务的服务复杂性。考虑将不含业务逻辑的服务替换为声明式 API 调用,并使用 Workflows 来抽象复杂性。

不过,您应创建服务来完成任何对 Workflows 来说过于复杂的工作;例如,实现 Workflows 表达式及其标准库不支持的可重用业务逻辑、复杂计算或转换。与使用 YAML 或 JSON 和 Workflows 语法相比,在代码中实现复杂情况通常更容易。

仅存储所需内容

控制内存用量,以免遇到资源限制或指示此类问题的错误(例如 ResourceLimitErrorMemoryLimitExceededErrorResultSizeLimitExceededError)。

谨慎选择要存储在变量中的内容,仅过滤出并存储所需的内容。如果服务返回的载荷过大,请使用单独的函数为您进行调用,并仅返回所需内容。

您可以通过清除变量来释放内存。例如,您可能需要释放后续步骤所需的内存。或者,您可能有调用包含不感兴趣的结果,您可以完全忽略这些结果。

您可以通过分配 null 来清除变量。在 YAML 中,您还可以为变量赋予空值或 ~。用于标识可以安全回收的内存。

示例

  - step:
      assign:
        - bigVar:

使用子工作流和外部工作流

您可以使用子工作流来定义要多次调用的逻辑或步骤组,从而简化工作流定义。子工作流类似于编程语言中的函数或例程。它们可以接受参数并返回值,让您能够创建更复杂的工作流,并扩大应用范围。

请注意,子工作流是工作流定义的本地工作流,无法在其他工作流中重复使用。不过,您可以从其他工作流调用工作流。Workflows 连接器可以帮助您实现这一点。如需了解详情,请参阅 Workflow Executions APIWorkflows API 的连接器概览。

使用 Workflows 连接器

Workflows 提供了许多连接器,可让您更轻松地访问工作流中的其他 Google Cloud 产品。连接器会为您处理请求的格式,从而为您处理请求的格式设置并提供方法和参数,这样您就无需了解Google Cloud API 的详细信息。连接器还具有用于处理重试长时间运行的操作的内置行为,因此您可以避免迭代和等待调用完成;连接器会为您处理这些操作。

如果您需要调用 Google Cloud API,请先检查是否存在适用于该 API 的工作流连接器。如果您没有看到 Google Cloud 商品的连接器,可以申请

了解如何使用连接器。如需详细了解可用连接器,请参阅连接器参考文档

并行运行工作流步骤

虽然工作流可以按顺序运行步骤,但您也可以并行运行独立步骤。在某些情况下,这可以显著加快工作流执行速度。如需了解详情,请参阅并行执行工作流步骤

应用重试和 saga 模式

设计弹性工作流,能够处理暂时性和永久性服务故障。工作流的错误可能由多种原因引起,例如 HTTP 请求、函数、连接器失败,或由您自己的工作流代码生成。添加错误处理和重试,以免某个步骤失败导致整个工作流失败。

某些业务交易会跨多个服务,因此您需要一种机制来实现跨服务的事务。Saga 设计模式是一种在分布式事务场景中管理跨微服务的数据一致性的方法。saga 是一系列事务,会为每笔事务发布事件并触发下一个事务。如果事务失败,saga 会执行补偿性事务来抵消序列中的先前失败。请参阅 GitHub 上的“工作流中的重试和 Saga 模式”教程

使用回调进行等待

回调允许工作流执行操作等待其他服务向回调端点发出请求;该请求将继续执行工作流。

借助回调,您可以告知工作流指定事件已发生,然后等待该事件而不进行轮询。例如,您可以创建一个工作流,以便在产品再次有货或发货时向您发出通知;或等待人工互动,例如审核订单或验证翻译。您还可以使用回调和 Eventarc 触发器等待事件

编排长时间运行的作业

如果您需要执行长时间运行的批处理工作负载,可以使用 BatchCloud Run 作业,还可以使用工作流来管理这些服务。这样,您就可以结合优势,高效地预配和编排整个流程。

Batch 是一项全代管式服务,可让您在 Compute Engine 虚拟机 (VM) 实例上安排批处理工作负载、将批处理工作负载排入队列并执行批处理工作负载。您可以使用适用于批处理的作业流连接器来安排和运行批处理作业。如需了解详情,请试用此教程

Cloud Run 作业用于运行执行操作(作业)并在操作完成时退出的代码。借助工作流,您可以在工作流中执行 Cloud Run 作业,以执行更复杂的数据处理或编排现有作业的系统。不妨试用此教程,了解如何使用 Workflow 执行 Cloud Run 作业。

将长时间运行的任务容器化

您可以使用工作流和 Compute Engine 自动执行长时间运行的容器。例如,您可以将长时间运行的任务容器化,以便在任何位置运行,然后在 Compute Engine 虚拟机上运行该容器,直到工作流执行的最长时长(一年)。

借助工作流,您可以自动执行虚拟机的创建、在虚拟机上运行容器以及删除虚拟机。这样,您就可以使用服务器并运行容器,但它会抽象出管理这两者的复杂性,如果您在使用 Cloud Run functions 或 Cloud Run 等服务时遇到时间限制,这可能会很有帮助。在 GitHub 上试用 使用 Workflows 和 Compute Engine 实现长时间运行的容器教程。

从 Workflows 运行命令行工具

Cloud Build 是一种服务,可通过一系列构建步骤在 Google Cloud 上执行构建,其中每个构建步骤都在 Docker 容器中运行。执行构建步骤类似于在脚本中执行命令。

Google Cloud CLI 包括 gcloudbqkubectl 命令行工具,但无法直接通过工作流运行 gcloud CLI 命令。不过,Cloud Build 提供包含 gcloud CLI 的容器映像。您可以通过 Cloud Build 步骤在这些容器中运行 gcloud CLI 命令,也可以使用 Cloud Build 连接器在工作流中创建该步骤。

示例

在工作流中运行 gcloud

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

使用 Terraform 创建工作流

Terraform 是一种基础架构即代码工具,可让您使用代码以可预测的方式创建、更改和改进您的云基础架构。

您可以使用 Terraform google_workflows_workflow 资源定义和部署工作流。如需了解详情,请参阅使用 Terraform 创建工作流

为了帮助您管理和维护大型工作流,您可以在单独的 YAML 文件中创建工作流,并使用 templatefile 函数将该文件导入 Terraform。该函数会读取给定路径中的文件,并将其内容呈现为模板。

示例

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

同样,如果您有一个主工作流调用多个子工作流,则可以在单独的文件中定义主工作流和子工作流,并使用 templatefile 函数导入它们。

示例

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

请注意,如果您在调试工作流时引用行号,则通过 Terraform 配置文件导入的所有 YAML 文件都会合并并作为单个工作流部署。

从 Git 代码库部署工作流

Cloud Build 使用构建触发器来启用 CI/CD 自动化。您可以配置触发器以监听传入的事件(例如,将新提交推送到代码库或者启动拉取请求时),然后在收到新事件时自动执行构建。

您可以使用 Cloud Build 触发器自动启动构建并从 Git 代码库部署工作流。您可以将触发器配置为在源代码库发生任何更改时部署工作流,也可以仅在更改符合特定条件时部署工作流。

这种方法有助于您管理部署生命周期。例如,您可以在预演环境中部署对工作流的更改,针对该环境运行测试,然后逐步将这些更改发布到生产环境。如需了解详情,请参阅使用 Cloud Build 从 Git 代码库部署工作流

优化数据库用量

运行工作流的费用极低。不过,对于高用量,请遵循以下准则来优化用量并降低费用:

  • 请确保对 Google Cloud服务的所有调用都使用 *.appspot.com*.cloud.goog*.cloudfunctions.net*.run.app,而不是使用自定义网域,以便您只需为内部步骤付费,而无需为外部步骤付费。

  • 应用自定义重试政策,在延迟时间、可靠性需求和费用之间取得平衡。重试次数越多,延迟时间越短,可靠性越高,但费用也可能会增加。

  • 使用等待长时间运行的操作的连接器时,请设置自定义轮询政策,以便优化延迟时间以降低费用。例如,如果您预计某项操作需要超过一小时的时间,则可以设置一个政策,让其在出现立即失败时最初轮询一分钟,然后每 15 分钟轮询一次。

  • 作业合并到一个步骤中。

  • 避免过度使用 sys.log 步骤。请考虑改用调用日志记录

最佳做法摘要

下表总结了本文档中建议的常规提示和最佳实践。

常规提示
最佳做法

后续步骤