Terraform で Cloud Workflows をデプロイする

Cloud Workflows を Terraform で扱います。以下はリファレンスのページ。

Cloud Workflows リファレンス

以下は公式チートシート。ぼーと眺めている。

Cloud Workflows - Syntax cheat sheet

以下のリポジトリは、Google の中の人が push してくれている Cloud Workflows のサンプルリポジトリ。マニュアル読んでも分からず困ったことがある場合、ここを見に行くとおおよそ解決します。

GitHub - Workflows Samples

環境

  • Terraform v1.3.5
  • registry.terraform.io/hashicorp/google v4.43.1

ファイル一覧

Terraform のテンプレートファイルと Workflows 向けの YAML ファイルたちです。指定した URL に HTTP アクセスして、Response の Body を表示するだけの処理を作ります。

ファイル構造

.
├── main.tf
└── yaml
    ├── custom_predicate.yaml
    ├── subwf_http_get.yaml
    └── wf_main.yaml

./main.tf

Terraform のテンプレートファイル。

variable "project_id" {}
variable "region" {}

terraform {
  required_version = "1.3.5"

  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "4.43.1"
    }
  }
}

provider "google" {
  project = var.project_id
  region  = var.region
}

# Create a service account for Workflows
# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_service_account
resource "google_service_account" "workflows_service_account" {
  account_id   = "workflows-service-account"
  display_name = "Workflows Service Account"
  project      = var.project_id
}

# Grant privileges to a service account
# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_service_account_iam#google_service_account_iam_member
resource "google_project_iam_member" "workflows_service_account" {
  role    = "roles/logging.logWriter"
  member  = "serviceAccount:${google_service_account.workflows_service_account.email}"
  project = var.project_id
}

# Define and deploy a workflow
# https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/workflows_workflow
resource "google_workflows_workflow" "workflows_example" {
  name        = "sample-workflow"
  project     = var.project_id
  region      = var.region
  description = "A sample workflow"
  labels = {
    "key1" = "value1"
  }
  service_account = google_service_account.workflows_service_account.id
  source_contents = join("", [
    templatefile("${path.module}/yaml/wf_main.yaml", {}),
    templatefile("${path.module}/yaml/subwf_http_get.yaml", {}),
    templatefile("${path.module}/yaml/custom_predicate.yaml", {})
  ])
}

wf_main.yaml

main workflow となる YAML ファイル。実際の処理は sub workflow 側で記述しています。

main:
  params: [args]
  steps:
    - assignment:
        assign:
          - host: $${args.host}
          - path: $${args.path}
    - call_subworkflow:
        call: subwf_http_get
        args:
          host: $${host}
          path: $${path}
        result: output
    - return_message:
        return: $${output}

subwf_http_get.yaml

main workflow から呼び出される sub workflow となる YAML ファイル。http.get 関数の処理を実行するものです。

subwf_http_get:
  params: [host, path]
  steps:
    - assignment:
        assign:
          - URL: '$${"https://" + host + "/" + path}'
    - get_message:
        try:
          call: http.get
          args:
            url: $${URL}
            timeout: 60.0
            headers:
              Content-Type: "text/plain"
          result: response
        retry:
          predicate: $${custom_predicate}
          max_retries: 5
          backoff:
            initial_delay: 1.0
            max_delay: 60
            multiplier: 1.25
        except:
          as: e
          steps:
            - logging_error:
                call: sys.log
                args:
                  data: '$${"Could not get from " + URL + "."}'
                  severity: "ERROR"
            - raise_exception:
                raise: $${e}
    - logging_info:
        call: sys.log
        args:
          data: '$${"Could get from" + URL + "."}'
          severity: "INFO"
    - return_value:
        return: $${response.body}

custom_predicate.yaml

http.get 関数のリトライ制御で利用される predication の YAML ファイル。

custom_predicate:
  params: [e]
  steps:
    - repeat:
        switch:
          - condition: $${"ConnectionError" in e.tags}
            return: true
          - condition: $${e.code == 429}
            return: true
          - condition: $${e.code == 502}
            return: true
          - condition: $${e.code == 503}
            return: true
          - condition: $${e.code == 504}
            return: true
    - otherwise:
        return: false

デプロイ・実行

デプロイして、実行します。

Workflows のデプロイ

デプロイ。

$ terraform apply -auto-approve \
-var="project_id=123456789012" \
-var="region=us-central1"

terraform apply

Workflows の実行

デプロイした Workflows の実行。https://ipinfo.io/ip という、アクセス元 IP アドレスを返してくれる Public API にアクセスしています。

$ gcloud workflows run sample-workflow --location=us-central1 --data='{"host":"ipinfo.io", "path":"ip"}'
Waiting for execution [dfe385de-1a0d-411d-8e1a-b4751776a984] to complete...done.
argument: '{"host":"ipinfo.io","path":"ip"}'
endTime: '2022-11-21T05:35:42.099667275Z'
name: projects/123456789012/locations/us-central1/workflows/sample-workflow/executions/dfe385de-1a0d-411d-8e1a-b4751776a984
result: '"64.233.172.168"'
startTime: '2022-11-21T05:35:41.581584119Z'
state: SUCCEEDED
status:
  currentSteps:
  - routine: main
    step: return_message
workflowRevisionId: 000005-60a

gcloud workflows run

メモたち

Workflow 定義ファイルを分ける

main workflow は処理のフローを記述するレベルに抑え、実際の処理は個別の sub workflow で記述します。sub workflow で利用する引数を管理することで、sub workflow の処理をある種の関数やメソッドのように利用できるようなります。以下の公式の紹介記事が詳しいです。

Terraform で複数の YAML ワークフロー定義をデプロイする

変数の設定

Cloud Workflows の YAML ファイルで利用する変数は、$${variable_name} と記述します。Cloud Workflows 本来の変数の記法は ${variable_name}ドルマーク 1 つですが、そのままでは Terraform の変数として扱われてしまうため、ドルマークを 2 つ付けてエスケープしてあげます。

エラーハンドリングとリトライの制御

try-except 構文と try-retry 構文を利用して、エラーハンドリングします。

Catch errors

Retry steps

各関数で発生する例外は、関数のマニュアルページに記載あるので、そこで確認します。

http.get - raised_exceptions

拾われなかった例外は発生した場合、ワークフロー全体の処理が終了するため、処理を停止したいステップを作る時には明示的に raise してあげます。

Raise errors