Workflows のベスト プラクティス

Workflows を使用してサービスをオーケストレートする場合は、ここに記載されているベスト プラクティスを参照できます。

これは、推奨事項をすべて網羅したものではありません。また、Workflows 使用方法の基本についての説明もありません。このドキュメントは、Google Cloud のランドスケープと Workflows に関する全般的な知識があることを前提としています。詳細については、Google Cloud アーキテクチャ フレームワークWorkflows の概要をご覧ください。

最適な通信パターンを選択する

複数のサービスをデプロイするためのマイクロサービス アーキテクチャを設計する場合は、次の通信パターンから選択できます。

  • サービス間の直接通信

  • 間接的なイベント ドリブン通信(コレオグラフィとも言う

  • 自動化された構成、調整、管理(オーケストレーションとも言う)

上記の各オプションのメリットとデメリットを考慮して、ユースケースに最適なパターンを選択してください。たとえば、サービス間の直接通信は他のオプションよりも実装が簡単ですが、サービスが密結合されます。一方、イベント ドリブン アーキテクチャでは、サービスが疎結合されます。ただし、モニタリングとデバッグはより複雑になることがあります。最後に、Workflows のような中央のオーケストレーターは、柔軟性は低いものの、サービス間の直接通信の密結合や、複雑なイベントの調整を必要とせずに、サービス間の通信を調整できます。

通信パターンを組み合わせることもできます。たとえば、イベント ドリブン オーケストレーションでは、密接に関連するサービスはイベントによってトリガーされるオーケストレーションで管理されます。同様に、あるオーケストレーションの結果として、別のオーケストレーション システムに Pub/Sub メッセージが送信されるシステムを設計することもできます。

全般的なヒント

サービス オーケストレーターとして Workflows を使用する場合は、次のヒントを参考にしてください。

URL をハードコードしない

ハードコードされた URL を避けることで、複数の環境間で移植可能で、保守しやすいワークフローをサポートできます。これは次の方法で実現できます。

  • URL をランタイム引数として定義します。

    これは、クライアント ライブラリまたは API からワークフローを呼び出す場合に便利です。(ただし、ワークフローが Eventarc のイベントによってトリガーされ、渡すことができる引数がイベント ペイロードである場合、これは機能しません)。

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

    ワークフローを実行するときに、URL を指定できます。次に例を示します。

    gcloud workflows run multi-env --data='{"urls":{"url1": "URL_ONE", "url2": "URL_TWO"}}'
  • 環境変数を使用して、デプロイ先の環境に応じて動的に構成されるワークフローを作成します。または、テンプレートとして再利用できるワークフローを作成し、個別に管理されている環境変数に応じて構成します。

  • 単一のワークフロー定義ファイルを作成する置換手法を使用しますが、ワークフローのプレースホルダを置き換えるツールを使用してバリアントをデプロイします。たとえば、Cloud Build を使用してワークフローをデプロイし、Cloud Build 構成ファイルに、ワークフローのプレースホルダ URL を置き換えるステップを追加します。

    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 を使用してビルドを送信するをご覧ください。

    または、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 コネクタを使用して、URL を Secret Manager に安全に保存し、取得します。

ネストされたステップを使用する

すべてのワークフローには、少なくとも 1 つのステップが必要です。 デフォルトで、Workflows は、ステップが順序付きのリストにあるかのように扱い、すべてのステップが実行されるまで、ステップを 1 つずつ実行します。論理的に、一部のステップはグループ化する必要があります。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 呼び出しなどの単純なタスクを実行します。Workflows は、サービスを呼び出して、レスポンスを解析し、他の接続されたサービスに対する入力を構築できます。サービスを呼び出すことで、追加の呼び出し、追加の依存関係、サービスを呼び出すサービスによる複雑性を回避できます。ビジネス ロジックのないサービスを宣言型 API 呼び出しに置き換え、Workflows を使用して複雑さを抽象化することを検討してください。

ただし、Workflows では複雑すぎる作業を行うサービスを作成する必要があります。たとえば、ワークフロー式とその標準ライブラリでサポートされていない、再利用可能なビジネス ロジック、複雑な計算、変換の実装が挙げられます。複雑なケースは通常、YAML や JSON、Workflows 構文を使用するよりもコード内で実装する方が簡単です。

必要なものだけを保存する

メモリ使用量を制御して、リソース上限または、ResourceLimitErrorMemoryLimitExceededErrorResultSizeLimitExceededError などを示すエラーが発生しないようにします。

変数に保存する内容を慎重に選択し、必要なものだけをフィルタして保存します。サービスがペイロードが大きすぎる値を返す場合は、別の関数を使用して呼び出しを行い、必要なものだけを返します。

変数をクリアすると、メモリを解放できます。たとえば、後続のステップに必要なメモリを解放することがあります。また、懸念する点がない結果で呼び出しを行い、それらの結果をすべて省略することもできます。

null を割り当てることで変数をクリアできます。YAML では、変数に空の値または ~ を代入することもできます。これにより、安全に再利用できるメモリが特定されます。

  - step:
      assign:
        - bigVar:

サブワークフローおよび外部ワークフローの使用

subworkflowsを使用して、複数回呼び出すロジックやステップのセットを定義し、ワークフロー定義を簡素化できます。サブワークフローは、プログラミング言語の関数やルーティンに似ています。パラメータと戻り値を受け取ることができるため、より幅広いアプリケーションでより複雑なワークフローを作成できます。

サブワークフローはワークフロー定義に対してローカルであり、他のワークフローで再利用できないことに注意してください。ただし、他のワークフローからワークフローを呼び出すことはできます。 これには Workflows コネクタが役立ちます。詳細については、Workflow Executions APIWorkflows API のコネクタの概要をご覧ください。

Workflows コネクタを使用する

Workflows には、ワークフロー内の他の Google Cloud プロダクトに簡単にアクセスできるようにする多数のコネクタが用意されています。コネクタは、リクエストのフォーマットを処理するため、呼び出し側のサービスが簡素化されます。コネクタは、Google Cloud API の詳細を認識する必要がないメソッドと引数を提供します。コネクタには、再試行長時間実行オペレーションを処理する組み込みの動作があるため、反復処理や呼び出しの完了の待機を回避できます。これはコネクタによって処理されます。

Google Cloud API を呼び出す必要がある場合は、まず、その Workflows コネクタが存在するかどうかを確認します。Google Cloud プロダクトのコネクタが表示されない場合は、リクエストできます。

コネクタの使用方法を確認する。また、使用可能なコネクタの詳細については、コネクタのリファレンスをご覧ください。

ワークフローのステップを並列実行する

ワークフローはステップを順次実行できますが、独立したステップを並列実行することもできます。これにより、ワークフローの実行が大幅に高速化される場合があります。詳細については、ワークフロー ステップを並列実行するをご覧ください。

再試行と Saga パターンを適用する

一時的なサービス障害と永続的なサービス障害の両方に対応できる復元性のあるワークフローを設計します。Workflows のエラーは、たとえば、HTTP リクエスト、関数、コネクタの失敗による発生や、独自のワークフロー コードによって生成されたことなどで発生する場合があります。1 つのステップの失敗によってワークフロー全体が失敗することがないように、エラー処理と再試行を追加します。

一部のビジネス トランザクションは複数のサービスにまたがるため、サービスをまたがるトランザクションを実装するメカニズムが必要です。Saga 設計パターンは、分散トランザクション シナリオでマイクロサービス間のデータ整合性を管理する方法です。Saga は、すべてのトランザクションに対してイベントを発行し、次のトランザクションをトリガーするトランザクションのシーケンスです。トランザクションが失敗した場合、Saga は、シーケンス内の前の失敗を相殺する補償トランザクションを実行します。GitHub で Workflows での再試行と Saga パターンのチュートリアルを試す。

コールバックを使用して待機する

コールバックを使用すると、ワークフローの実行では、別のサービスがコールバック エンドポイントにリクエストを行うことを待ち、そのリクエストによってワークフローの実行を再開できます。

コールバックを使用すると、ポーリングせずに指定したイベントを待機して、その発生をワークフローに知らせることが可能です。 たとえば、商品の在庫が補充されたときや商品が発送されたときに通知を受け取るワークフローを作成できます。あるいは、注文の確認や翻訳の確認など、人間による操作ができるように待機することも可能です。コールバックと Eventarc トリガーを使用してイベントを待機することもできます。

長時間実行ジョブをオーケストレートする

長時間実行されるバッチ処理ワークロードを実行する必要がある場合は、Batch または Cloud Run ジョブを使用できます。また、Workflows を使用してサービスを管理できます。これにより、それぞれの利点を享受し、プロセス全体を効率的にプロビジョニングしてオーケストレートできます。

Batch は、Compute Engine 仮想マシン(VM)インスタンスでバッチ ワークロードのスケジューリング、キューイング、実行を行えるフルマネージド サービスです。Batch 用 Workflows コネクタを使用して、Batch ジョブのスケジュール設定と実行を行うことができます。詳しくは、チュートリアルをお試しください。

Cloud Run ジョブは、作業(ジョブ)を実行します。作業の完了後に終了するコードを実行する場合に使用されます。Workflows を使用すると、ワークフローの一部として Cloud Run ジョブを実行し、より複雑なデータ処理を行う、または既存のジョブのシステムをオーケストレートします。Workflows を使用して Cloud Run ジョブを実行する方法を示すチュートリアルを試します。

長時間実行タスクをコンテナ化する

Workflows と Compute Engine を使用して、長時間実行コンテナの実行を自動化できます。たとえば、長時間実行タスクをコンテナ化してどこででも実行できるようにした後、ワークフロー実行時間の最長期間(1 年間)の間、Compute Engine VM 上でコンテナを実行できます。

Workflows を使用すると、VM の作成、VM でのコンテナの実行、VM の削除を自動化できます。これにより、サーバーを使用してコンテナを実行できますが、両方を管理する複雑さが軽減されるため、Cloud Run functions や Cloud Run などのサービスを使用しているときに時間的制約が発生した場合に役立ちます。GitHub で Workflows と Compute Engine を利用したコンテナの長時間実行チュートリアルを試してください。

Workflows からコマンドライン ツールを実行する

Cloud Build は、一連のビルドステップとして Google Cloud 上でビルドを実行するサービスです。各ビルドステップは Docker コンテナで実行されます。ビルドステップの実行は、スクリプトでコマンドを実行する場合と似ています。

Google Cloud CLI には gcloudbqkubectl コマンドライン ツールが含まれていますが、Workflows から gcloud CLI を直接実行する方法はありません。ただし、Cloud Build には gcloud CLI を含むコンテナ イメージが用意されています。これらのコンテナ内の gcloud CLI コマンドは、Cloud Build ステップから実行できます。また、Cloud Build コネクタを使用して、Workflows でそのステップを作成できます。

ワークフローで 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 は、コードを使用してクラウド インフラストラクチャを予想どおりに作成、変更、改善できる Infrastructure as Code ツールです。

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 ファイルがマージされ、1 つのワークフローとしてデプロイされます。

Git リポジトリからワークフローをデプロイする

Cloud Build は、ビルドトリガーを使用して CI/CD の自動化を有効にします。新しい commit がリポジトリに push されたときや、pull リクエストが開始されたときなどでの受信イベントをリッスンし、新しいイベントが生じたときにビルドを自動的に実行するようにトリガーを構成できます。

Cloud Build トリガーを使用すると、ビルドを自動的に開始し、Git リポジトリからワークフローをデプロイできます。ソース リポジトリが変更された場合や、特定の条件に一致する変更があった場合にのみワークフローをデプロイするようにトリガーを構成できます。

このアプローチは、デプロイ ライフサイクルを管理する際に活用できます。たとえば、ステージング環境でワークフローの変更をデプロイし、その環境に対してテストを実行して、これらの変更を本番環境に段階的にロールアウトできます。詳細については、Cloud Build を使用して Git リポジトリからワークフローをデプロイするをご覧ください。

使用を最適化する

ワークフローの実行費用は最小限になります。ただし、使用量が大量の場合は、次のガイドラインを適用して使用量を最適化し、コストを削減します。

  • カスタム ドメインを使用する代わりに、Google Cloud サービスへの呼び出しには *.appspot.com*.cloud.goog*.cloudfunctions.net、または *.run.app を使用します。これにより、外部のステップではなく、内部で課金されるようになります。

  • レイテンシと信頼性のニーズとコストのバランスを取るカスタム再試行ポリシーを適用します。再試行の頻度が上がると、レイテンシは短くなり、信頼性が向上しますが、コストも増加する可能性があります。

  • 長時間実行オペレーションを待機するコネクタを使用する場合は、コストのレイテンシを最適化するカスタム ポーリング ポリシーを設定します。たとえば、オペレーションに 1 時間以上かかると予想される場合、即時に失敗した場合はまず 1 分後にポーリングし、その後 15 分ごとにポーリングするポリシーを作成できます。

  • 割り当てを 1 つのステップにまとめます。

  • sys.log ステップを過度に使用しないでください。代わりにコール ロギングの使用を検討してください。

ベスト プラクティスの概要

次の表に、このドキュメントで推奨する一般的なヒントとベスト プラクティスをまとめます。

全般的なヒント
ベスト プラクティス

次のステップ