バイセル Tech Blog

バイセル Tech Blogは株式会社BuySell Technologiesのエンジニア達が知見・発見を共有する技術ブログです。

バイセル Tech Blog

Cloud Runジョブのオーバーライド機能で実現した単発処理群管理のための快適な環境

はじめに

こちらは バイセルテクノロジーズ Advent Calendar 2023 の5日目の記事です。 前日の記事は野口さんの「手軽にElasticsearchとRDBの型不整合を防ごう! ~ dynamic templateを使った実装例 ~」でした。

こんにちは、開発2部の馬場です。

アプリケーションの開発や運用において、例えばSQLを流してのデータ修正など何かしらスポットで単発処理を行いたいことがあると思います。

今回私たちは、新規開発中のプロダクトへのデータ連携処理を実行していく必要がありました。 そこで、データ連携をはじめ任意の処理を自由なタイミングで実行できる基盤を用意することにしました。

この記事ではGoogle Cloudの「Cloud Runジョブのオーバーライド機能」を活用し、単発処理群を快適に管理する方法についてご紹介します。

目次

背景

現在私たちは、GoとGoogle Cloudを用いて新規プロダクトの開発を行なっています。 その中で今後のリリースをみこして、旧システムから新規開発中のプロダクトに順次データを移行していく必要がでてきました。

しかし、私たちは今までアプリケーションの機能開発に注力してきたため、アプリケーションの機能ではない処理を実行する基盤がありませんでした。

そこで、アプリケーションの機能ではない単発処理を自由なタイミングで実行できる環境を用意することにしました。

技術選定

私たちの目的を達成するために、技術選定に臨みました。私たちのシステムはGoogle Cloud上に構築されているため、今回の処理基盤もGoogle Cloudで実現することを前提に選定を進めました。

理想の環境

今回の単発処理群を管理する環境において、以下の3つを満たすことが理想でした。

任意のタイミングで任意の処理を実行できる

単発処理で実施したい処理は様々なものが想定されます。それぞれの処理を簡単に出し分けられることや、処理によって実行のタイミングを制御できることが理想でした。

開発〜デプロイ〜実行を簡単に素早くできる

単発処理の中には、こまめに動作確認しながら開発をしないといけない処理もあれば、開発〜実行までをスピーディーに行わなければいけない処理もありました。そのため、開発 〜 デプロイ 〜 実行をスムーズに行える環境を目指しました。

低コスト

今回の単発処理群の実行環境は、それぞれの処理で内容や実行頻度も様々です。そのため、実行時のみ課金などコストパフォーマンスが高いものが理想でした。

そして今回私たちは、Cloud Runジョブと、Cloud Runジョブがもつオーバーライド機能を用いてシステムを構築することにしました。

Cloud Run ジョブとオーバーライド機能とは

Cloud RunとはGoogle Cloudが提供するサーバレスなコンテナ実行環境で、Cloud RunサービスとCloud Runジョブに大別されます。

Cloud Runジョブはバッチ処理に適しており、処理の実行トリガーがHTTPリクエストに限らず、より長時間の実行や明示的な並列処理を行うことが可能です。

また、Cloud Runジョブではジョブの登録時に実行コマンドや環境変数、タイムアウトなどの指定をできます。そして、Cloud Runジョブのオーバーライド機能を使用することでジョブの実行時にそれらを上書きできます。オーバーライド機能によって指定したパラメータの値はこの実行にのみ適用されます。

詳しくは以下ドキュメントをご覧ください。 https://cloud.google.com/run/docs/overview/what-is-cloud-run?hl=ja

Cloud Run ジョブを選んだ理由

今回、アプリケーションの機能ではない単発処理群を管理する環境において、私たちは以下の理由からCloud Runジョブを採用しました。

コンテナを実行できるので環境構築が容易

Cloud Runジョブはコンテナイメージをそのままコンテナとして実行できるため、開発 〜 デプロイ 〜 実行までをスムーズに行うことができると考えました。コンテナイメージにジョブの実行に必要なものをまとめ、全ての環境で同一のイメージを使用すれば、環境構築や動作確認、デプロイなどを容易にできると考えました。

実行時のみの課金である

Google Compute Engineなどとは異なり、Cloud Runジョブは実行時にのみ料金が発生します。単発処理の実行を想定しているため、コスト面で魅力に感じました。

https://cloud.google.com/run/pricing?hl=ja

任意のタイミングで実行できる

Cloud Runジョブはジョブの発火をGUIを始め様々な方法で実現できます。任意のタイミングで実行できるため、今回の単発処理の実行環境に適していました。

オーバーライド機能を用いることで、コンテナ実行時に実行する命令を都度変えることができる

Cloud Runジョブのオーバーライド機能を用いることで、コンテナイメージ実行時に実行コマンドを上書きできます。開発時は1つのイメージ内でジョブのアプリケーションの全てを完結させ、ジョブの呼び出し時に実行コマンドのオーバーライドを行い、処理を出し分ければ、開発 〜 デプロイ 〜 実行をスムーズかつ柔軟に行うことができると考えました。

実行コマンドをオーバーライドする形式となるため、コンテナ引数を切り替えるだけで実行する処理を切り替えられるようにしておくと都合が良いです。そのため、私たちはアプリケーションのインタフェースをCLI形式で実装することにしました。

ここからは具体的に、Cloud Runジョブのオーバーライド機能を用いて任意のタイミングに自由に処理を出し分けられる実行環境の構成を、Goを用いて紹介していきます。

実装

1. GoのCLIアプリケーションを作成

今回、GoのCLI作成ライブラリであるcobraを使って実行したい処理のサブコマンドを作成しました。

cobraを使ってサブコマンドを実装すると、go run main.go サブコマンド名で以下のように任意の処理を実行できます。

$ go run main.go sub-command-A
-----------
サブコマンドAが実行されました
-----------
$ go run main.go sub-command-B
-----------
サブコマンドBが実行されました
-----------

例を用いて紹介します。今回簡単な異なる文字列を出力する2つのサブコマンドA・Bを実装しました。

今回、cobraアプリケーションのコードジェネレータであるcobra-cliを使用し、cobraアプリケーションの雛形を作成、またサブコマンドを追加しました。

詳しくは以下ドキュメントをご覧ください。 https://github.com/spf13/cobra-cli

cobra-cliを使用し、2つのサブコマンド、subCmdAsubCmdBを生成しました。

$ cobra-cli add subCmdA
$ cobra-cli add subCmdB
$ tree
.
├── LICENSE
├── cmd
│   ├── root.go
│   ├── subCmdA.go
│   └── subCmdB.go
├── go.mod
├── go.sum
└── main.go

subCmdA.goとsubCmdB.goに簡単な文字列出力のコードを実装し、UseフィールドにUse:"サブコマンド名"のように実行時に指定するサブコマンド名を指定しました。

./cmd/subCmdA.go

package cmd

import (
    "fmt"

    "github.com/spf13/cobra"
)

var subCmdA = &cobra.Command{
    Use:   "sub-command-A", // サブコマンド名
    Short: "sub-command-A",
    Long:  `sub-command-A`,
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("-----------")
        fmt.Println("サブコマンドAが実行されました")
        fmt.Println("-----------")
    },
}

func init() {
    rootCmd.AddCommand(subCmdA)
}

./cmd/subCmdB.go

package cmd

import (
    "fmt"

    "github.com/spf13/cobra"
)

var subCmdB = &cobra.Command{
    Use:   "sub-command-B",  // サブコマンド名
    Short: "sub-command-B",
    Long:  `sub-command-B`,
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Println("-----------")
        fmt.Println("サブコマンドBが実行されました")
        fmt.Println("-----------")
    },
}

func init() {
    rootCmd.AddCommand(subCmdB)
}

以下のように実行したいサブコマンドを指定することで、任意の処理を出し分けることができました。

$ go run main.go sub-command-A
-----------
サブコマンドAが実行されました
-----------
$ go run main.go sub-command-B
-----------
サブコマンドBが実行されました
-----------

2. GoのCLIアプリケーションをコンテナ化する

続いて、1.で作成したcobraアプリケーションをコンテナとして実行できるようにします。

今回以下のような簡単なDockerfileを用意しました。

./Dockerfile

FROM --platform=linux/amd64 golang:1.21.3-bullseye

WORKDIR /app

COPY ./go.mod ./go.sum ./

RUN go mod download

COPY . .

RUN go build -o /app/command main.go

CMD ["/app/command"]

以下のようにコンテナ引数に実行したいサブコマンドを指定することで、1つのイメージでコンテナ実行時に任意の処理を出し分けることができました。

$ docker run 0f38fa4c9cd0 /app/command sub-command-A
-----------
サブコマンドAが実行されました
-----------
$ docker run 0f38fa4c9cd0 /app/command sub-command-B
-----------
サブコマンドBが実行されました
-----------

以後は、Cloud Runジョブのオーバーライド機能を使用し、コンテナ引数を指定することによって任意の処理を出し分けられる環境を作成していきます。

3. Cloud Run ジョブを作成

Cloud Runジョブは、ジョブの作成元イメージを指定する必要があります。イメージをArtifact Registryという、Google Cloudのコンテナレジストリに保存し、ジョブ作成時にそのイメージを指定することで、任意のジョブの実行できるようにします。

そこでまずは、2.で作成したイメージを以下のようにArtifact Registryにアップロードしておきます。

詳しくは以下ドキュメントをご覧ください。 https://cloud.google.com/artifact-registry/docs/docker/pushing-and-pulling?hl=ja#linux

そして、Cloud RunのGoogle Cloudコンソール画面から「ジョブを作成」を押下します。

次に、コンテナイメージのURLの指定欄で右にある「選択」を押下します。先ほどArtifact Registryにアップロードしたイメージを選択しジョブを作成します。

4. 作成したジョブをオーバーライド機能を用いて呼び出してみる

実際にオーバーライド機能を用い、コンテナ引数を指定すること任意の処理を実行していきます。

3.で作成したジョブを選択し、「オーバーライドを使用して実行」を押下します。

オーバーライド機能を用いて、実行時のコンテナ引数を指定しサブコマンドAを呼び出すことができました。

同様に、サブコマンドBも呼び出すことができました。

Cloud Runジョブのオーバーライド機能を使用し、コンテナ引数を指定することによって任意の処理を出し分けることができました。

活用例

実際に私たちのチームでの活用例をご紹介します。

今回私たちは、旧システムからデータ移行の作業をする必要がありました。大量の過去のデータや毎日追加されるデータを連携していきたいため、毎日の定期実行でデータ連携処理を実行していく必要がありました。

Cloud RunジョブはREST APIを提供しており、HTTPリクエスト経由で実行できます。そして、今回紹介したオーバーライド機能もリクエストボディにパラメータを含ませることで利用が可能です。

Cloud SchedulerからHTTPリクエストを実行する際のリクエストボディのOverridesパラメータ指定時に、コンテナ引数を指定することによって任意の処理を出し分けることができるのではと考えました。

上記を踏まえ、私たちはCloud Schedulerをトリガーにオーバーライド機能を使用してジョブを実行することで、毎日の定期実行でデータ連携処理の実行を可能にしました。

例を用いて紹介します。Cloud SchedulerからCloud Runジョブを実行するために、Cloud Runジョブのスケジューラトリガーを作成します。

Cloud RunのGoogle Cloudコンソール画面からジョブを選択し、「スケジューラトリガーを追加」を押下します。

頻度やタイムゾーンなどを設定して作成します。

作成されたスケジューラトリガーを選択し、「詳細を表示」を押下します。

実行内容を構成する配下の本文でリクエストボディを指定できます。この時以下のようなリクエストボディを指定することで、オーバーライド機能を用いてコンテナ引数を上書きして実行できます。

このようにCloud Schedulerをトリガーにオーバーライド機能を使用してジョブを実行することで、任意の処理の定期実行を可能にしています。

今後はHTTPリクエスト経由で、実行時間を指定した予約実行なども実装していきたいと考えています。

終わりに

今回、Cloud Runジョブのオーバーライド機能を活用し、単発処理群を管理する環境を構築していきました。HTTPリクエスト経由での実行も可能なため、幅広く応用が効きます。

開発〜実行を簡単に素早くでき、任意のタイミングで任意の処理を実行できる理想の環境を構築できました。是非参考にしていただきたいです。

明日のバイセルテクノロジーズAdvent Calendar 2023は小松山さんの「費用削減を目指し検討したVPCコネクタからDirect VPC Egressへの移行とその見送りの理由」です、そちらもぜひ併せて読んでみてください!

最後に、バイセルではエンジニアを随時募集しております。興味のある方はぜひ以下の採用サイトをご覧ください。

herp.careers