GCP WorkflowsでCloud Run Jobを依存付き並列実行してみた

こんにちは。 CTO 室の yuina です。

引き続き某 CTO からの無茶振りを捌いております。

今回 GCP の Workflows を使って、複数の Cloud Run の job に対し、依存関係を作りながら実装をしたので、備忘録として残しておきます。

GCPのWorkflowsとは

GCP の Workflows は、GCP 上でのワークフローを定義するためのサービスです。 AWSにも同様のサービスとしてAWS Step FunctionsやAmazon SWFがあります。

cloud.google.com

GCPのWorkflowsは、GCPのサービス以外にも、Googleの各種サービスをシームレスに組み合わせたりもできるので、G Suite等のサービスとも連携ができるため、エンジニアの方以外でも使う機会はあるかもしれません。

個人的にもGmail、Googleカレンダー等と組み合わせてオートメーション的なものを作っていたりしています。

どんなことをしたのか

今回はWorkflowsにてCloud Run Jobsを並列実行したり、依存を持たせることをしながら対応しました。

前提として、Cloud Run Jobsは既に作成済みで、Cloud Run Jobsを呼び出すだけの実装です。

以下はサンプルコードです。

main:
  params: [input_params]
  steps:
    - step_1:
        assign:
          - var_a: ${default(map.get(input_params, "param_a"), "")}
          - var_b: ${default(map.get(input_params, "param_b"), "")}
          - var_c: ${default(map.get(input_params, "param_c"), "")}
          - project_id: project-xyz
          - job_location: asia-northeast1
    - step_2:
        parallel:
          branches:
            - branch_a:
                steps:
                  - task_a:
                      call: googleapis.run.v1.namespaces.jobs.run
                      args:
                        name: ${"namespaces/" + project_id + "/jobs/job-abc-1"}
                        location: ${job_location}
                        body:
                          overrides:
                            containerOverrides:
                              - args: ${["script.py", "--param-a", var_a, "--param-c", var_c, "--param-b", var_b]}
                      result: result_a
            - branch_b:
                steps:
                  - task_b:
                      call: googleapis.run.v1.namespaces.jobs.run
                      args:
                        name: ${"namespaces/" + project_id + "/jobs/job-abc-2"}
                        location: ${job_location}
                        body:
                          overrides:
                            containerOverrides:
                              - args: ${["script.py", "--param-a", var_a, "--param-b", var_b]}
                      result: result_b
    - step_3:
        call: googleapis.run.v1.namespaces.jobs.run
        args:
          name: ${"namespaces/" + project_id + "/jobs/job-abc-5"}
          location: ${job_location}
          body:
            overrides:
              containerOverrides:
                - args: ${["script.py", "--param-a", var_a]}
        result: final_result
    - end_step:
        return: ${final_result}

このyamlを図にすると以下のようになります。

GCPの画面上でyamlを書いていくと、上記のような図を自動でプレビューしながら作成できるので、流れをイメージしやすいです。

また、構文に関してはドキュメントがあるため、yamlの構文を理解していれば、すぐに実装できると思います。

cloud.google.com

yamlのポイントとなりそうな部分を解説します。

1. 並列実行

- step_2:
    parallel:
      branches:
        - branch_a:
            steps:
              - task_a:
                  call: googleapis.run.v1.namespaces.jobs.run
                  args:
                    name: ${"namespaces/" + project_id + "/jobs/job-abc-1"}
                    location: ${job_location}
                    body:
                      overrides:
                        containerOverrides:
                          - args: ${["script.py", "--param-a", var_a, "--param-c", var_c, "--param-b", var_b]}
                  result: result_a
        - branch_b:
            steps:
              - task_b:
                  call: googleapis.run.v1.namespaces.jobs.run
                  args:
                    name: ${"namespaces/" + project_id + "/jobs/job-abc-2"}
                    location: ${job_location}
                    body:
                      overrides:
                        containerOverrides:
                          - args: ${["script.py", "--param-a", var_a, "--param-b", var_b]}
                  result: result_b

上記のように、parallelを使うことで、複数のjobを並列実行することができます。 並列実行がすべて完了した後に、次のステップに進むような形になります。

2. Cloud Run Jobsを呼び出す

call: googleapis.run.v1.namespaces.jobs.run
args:
  name: ${"namespaces/" + project_id + "/jobs/job-abc-1"}
  location: ${job_location}
  body:
    overrides:
      containerOverrides:
        - args: ${["script.py", "--param-a", var_a, "--param-c", var_c, "--param-b", var_b]}

上記のように、Cloud Run Jobsを呼び出すことができます。 callの部分でCloud Run Jobsの呼び出しを行い、argsの部分でjobの名前や引数を指定します。 containerOverridesの部分で、Cloud Run Jobsに渡す引数を指定する形をとります。

事前にinitで引数となる定義しておくことで、Workflows実行時にjson形式で引数の元となる値を渡すことができるようになり、同じ引数を使いまわしたり、yamlの可読性を上げることができます。

3. Cloud Run job が失敗したときの挙動

Cloud Run jobがexit code 0 以外で終了した場合、Workflowsは途中で失敗として扱うようになっています。 そのため、Cloud Run Jobsのどれかが失敗した場合は、Workflows全体が失敗として扱われます。

IaCでのデプロイ

WorkflowsはTerraformでのデプロイも可能です。 Terraformで書く場合、書き方によってはGCPの画面上で書く場合と同じ構文が使えず、手直しする必要があります。

また、実際に困った部分としては、terraform planでは特にエラーが出ないのに、applyするとエラーになることがありましたので、注意が必要です。

まとめ

Workflowsを使うことで、GCPの各種サービスを組み合わせて、シンプルに依存や並列実行を実装することができました。 GCPのサービスのみに囚われずに利用もできるため、一度触ってみると面白いと思います。