データエンジニアをしています。
運用しているデータパイプラインの一部にGoogle CloudのWorkflowsとCloud Functionsを用いておりますが、デプロイの俊敏性・正確性に課題があります。(コピペ・手動デプロイをやめたいです)
Terraformを使用してこれらを解消できないか、検証をしたことを共有します。
想定読者
- Terraform初心者
- Terraformを業務に導入してみたいと考えている人
- 既存のデプロイフローや手法に課題があると考えている人
試したことをかんたんに
- WorkflowsとCloud Functionsをterraformでデプロイした
- Cloud FunctionsはローカルのソースコードをZIP化してGCSにアップロードし、それをもとにCloudFunctionsをデプロイした
- 外部ファイル(ローカルのソースコードのファイル)を読み込む形でWorkflowsのソースコードを指定した
試した環境・構成
- Cloud Shell
work
  └ archive
      └ cloudfuncitons
           └ test_function1
                └ main.zip
  └ cloudfunctions
       └ test_function1
            └ main.py 
  └ workflows
       └ test_workflows
             └ test_workflow.yml
  └ main.tf
archive
Cloud FunctionsをデプロイするためにZIP化したソースコードを格納するディレクトリ。
実運用ではCloud FunctionsだけでなくLambdaも使用予定なので、サービス名別・関数名別にディレクトリを作成している。
cloudfunctions
デプロイするCloud Functionsのソースコードを格納するディレクトリ。
workflows
デプロイするWorkflowsのソースコードを格納するディレクトリ。
main.tf
Cloud FunctionsやWorkflowsとそれに関連するリソースの構成情報を記載したtfファイル。
Cloud Functionsのソースコード
Hello Worldを出力するシンプルなコードです。
def hello_world(request):
    print('Hello World')
    return 'Hello from Cloud Functions!'
Workflowsのソースコード
Cloud Functionsを呼び出すシンプルなコードです。
- invoke_test_function1:
      call: http.get
      args:
          url: "Cloud FuncitonsのURL"
          auth:
            type: OIDC
      result: response
- logResponse:
      call: sys.log
      args:
        text: ${response.body}
        severity: "INFO"
Terraformのソースコード
プロバイダの設定
provider "google" {
  project = "プロジェクト名"
  region  = "asia-northeast1"
}
Cloud Functionsの構成情報
# CloudFunctionsのソースコードを格納するGoogle Cloud Storageバケット
resource "google_storage_bucket" "cloud_functions_bucket" {
  name     = "バケット名"
  location = "asia-northeast1"
  # アクセスレベルを均一化
  uniform_bucket_level_access = true
  # 公開アクセスを「非公開」に
  public_access_prevention = "enforced"
}
# CloudFunctionsのソースコードをGCSのオブジェクトとしてアップロードする
resource "google_storage_bucket_object" "test_function1" {
  name   = data.archive_file.function_archive.output_path
  bucket = google_storage_bucket.cloud_functions_bucket.name
  source = data.archive_file.function_archive.output_path
}
# CloudFunctionsのローカルのソースコードをZIP化して、ローカルのarchiveディレクトリに格納する(このZIPファイルをGCSにアップロードする)
data "archive_file" "function_archive" {
  type        = "zip"
  source_dir  = "cloudfunctions/test_function1"
  output_path = "archive/cloudfunctions/test_function1/main.zip"
}
# CloudFunctionsの構成情報
resource "google_cloudfunctions_function" "test_function1" {
  name        = "test_function1"
  description = "An example Cloud Function"
  runtime     = "python310"
  # GCSにアップロードしたソースコードをもとにデプロイする
  source_archive_bucket        = google_storage_bucket_object.test_function1.bucket
  source_archive_object        = google_storage_bucket_object.test_function1.source
  available_memory_mb          = 128
  entry_point                  = "hello_world"
  trigger_http                 = true
  https_trigger_security_level = "SECURE_ALWAYS"
  timeout                      = 60
}
Workflowsの構成情報
# Workflowsの構成情報
resource "google_workflows_workflow" "test_workflow" {
  name        = "test_workflow"
  description = "An example workflow"
  # ローカルのymlファイルをソースコードとしてデプロイする
  # ${path.module}はTerraformファイルがあるディレクトリを表します
  source_contents = file("${path.module}/workflows/test_workflows/test_workflow.yml")
  # TODO: 既存サービスアカウントもTerraformで扱えるようにimportしたい
  service_account = "既存のサービスアカウント"
}
ハマったポイント
結果としてterraform applyでのデプロイは成功したのですが、それまでにいくつかハマったポイントがあったので、記録しておきます。
Workflowsのソースコードを外部から読み込む
TerraformやGoogle Cloudのドキュメントを見ていると、Workflowsのソースコードはtfファイルに直書きしている例が多かったです。
しかし、これだと保守性も悪くなりそうですし、テキスト容量の制限(32KB)もあったので、外部ファイルを読み込むことができないか模索しました。
結果、Terraformのfile関数とpath.module変数を用い、下記のように外部ファイルを読み込むことができました。
file("${path.module}/workflows/test_workflows/test_workflow.yml")
参考1:google_workflows_workflow
参考2:Terraform を使用してワークフローを作成する
WorkflowsからCloud Functionsを呼び出すとき
これはTerraform関連の話ではないのですが、WorkflowsからCloud Functionsを呼び出すとき、403エラーが出でしまいました。
Workflowsには十分な権限を付与していたのですが、エラーとなってしまいました。
ドキュメントをみるとauthセクションを加えることで認証情報を付与してCloud Functionsを呼び出すことができるようでした。
Cloud Functions や Cloud Run にリクエストを送信するときは、OIDC を使用して認証します。
OIDC を使用して HTTP リクエストを行うには、URL を指定した後に、ワークフローの定義の args セクションに auth セクションを追加します。この例では、Cloud Functions を呼び出すために、リクエストが送信されます。
今後の展望
- 既存のリソースをTerraformで管理できるようにする
- すでに運用しているデータパイプラインをTerraformで扱えるようにする
 
- Step FunctionsとLambdaもTerraformで管理できるようにする
- すでに運用しているデータパイプラインはStep FuncitonsとLambdaも使用しているため
 
- Githubとクラウド環境のソースコードおよび構成情報を一致させるために、CI/CDパイプラインとTerraformを連携させる
- Github ActionsとTerraformを組み合わせてデプロイの俊敏性・正確性を上げる
 
