notebook

都内でWEB系エンジニアやってます。

TerraformでCloudRun+CloudSchedulerを構築する

素振り目的でやってみた

この記事はterraform Advent Calendar 2020の12日目の記事です

前提

  • 用途はバッチ
  • CloudRunは認証済みアクセスのみ許可する
  • TerraformでCloudRunの設定周りを行う
  • イメージのビルドは別途行いCloudRunのtfファイルで参照させる
  • Cloud Schedulerは複数用意する

実行環境

$ terraform version
Terraform v0.14.2
+ provider registry.terraform.io/hashicorp/google v3.50.0

イメージのビルド

次のリポジトリでビルドした

swfz/cloud-run-ruby-sample

github.com

色々雑だがprint部分を削ってソースを貼っておく

  • app.rb
require 'sinatra'
require 'json'
require 'base64'
require 'net/http'
require 'google/cloud/storage'
require 'google/cloud/secret_manager'

set :bind, "0.0.0.0"
port = ENV["PORT"] || "8080"
set :port, port

post "/" do
  "Hello World!"
end

post "/storage" do
  storage = Google::Cloud::Storage.new
  bucket = storage.bucket ENV["BUCKET"]

  local_file_path = "./Gemfile"
  storage_file_path = "Gemfile"

  file = bucket.create_file local_file_path, storage_file_path
  status 200
end

post "/secret_manager" do
  project_id = ENV["PROJECT_ID"]

  client = Google::Cloud::SecretManager.secret_manager_service
  key = client.secret_version_path project: project_id, secret: 'sample-secret', secret_version: 'latest'
  res = client.access_secret_version name: key

  status 200
end

post "/fixed_ip" do
  params = JSON.parse request.body.read

  uri = URI.parse('https://ifconfig.me')
  res = Net::HTTP.get_response(uri)

  uri
end

イメージのビルド

上記のリポジトリをcloneしてビルドする

gcloud builds submit --tag gcr.io/$GCP_PROJECT_ID/sample-run

サービスアカウント

CloudRunを実行するサービスアカウントの作成から

  • service_account.tf
# CloudRunを実行するサービスアカウント
resource google_service_account run_invoker {
  project      = local.project
  account_id   = "cloud-run-invoker-sa"
  display_name = "Cloud Run Invoker Service Account"
}

# Cloud Storageを操作するための権限
resource google_storage_bucket_iam_binding run_invoker {
  bucket = google_storage_bucket.bucket.name
  role   = "roles/storage.admin"

  members = [
    "serviceAccount:${google_service_account.run_invoker.email}"
  ]
}

# シークレットマネージャーの情報を読むための権限
resource google_secret_manager_secret_iam_binding run_invoker {
  project   = local.project
  secret_id = "sample-secret"
  role      = "roles/secretmanager.secretAccessor"
  members = [
    "serviceAccount:${google_service_account.run_invoker.email}"
  ]
}

# CloudRunを実行するためのポリシー
data google_iam_policy invoker {
  binding {
    role = "roles/run.invoker"
    members = [
      "serviceAccount:${google_service_account.run_invoker.email}"
    ]
  }
}

resource google_cloud_run_service_iam_policy run_policy {
  location    = google_cloud_run_service.default.location
  project     = local.project
  service     = google_cloud_run_service.default.name
  policy_data = data.google_iam_policy.invoker.policy_data
}

サービスアカウントで実行させるためサービスアカウントの作成とrun.invokerの権限を付与する処理を書いている

また、今回は実行中にGCSのへの処理とシークレットマネージャーの読み込みも行うのでそのあたりの権限も追加している

CloudRun

GCRへのpushは別途でやっておくのでここではイメージの名前を固定してdataで参照できるようにしている

タイムアウトは15分を設定(2020-12-11時点での最大値)

  • cloud_run.tf
# 別途作成したイメージ名を指定
data google_container_registry_image app {
  name = "sample-run"
}

resource google_cloud_run_service default {
  name     = "cloudrun-srv"
  location = local.region

  template {
    metadata {
      labels = {
        environment = "dev"
      }
    }
    spec {
      containers {
        image = data.google_container_registry_image.app.image_url
        env {
          name  = "BUCKET"
          value = google_storage_bucket.bucket.name
        }
        env {
          name  = "SHORT_SHA"
          value = local.short_sha
        }
        env {
          name  = "PROJECT_ID"
          value = local.project
        }
      }
      service_account_name = google_service_account.run_invoker.email
      timeout_seconds      = 900
    }
  }

  traffic {
    percent         = 100
    latest_revision = true
  }
}

locals {
  url = google_cloud_run_service.default.status[0].url
}

output url {
  value = local.url
}

outputのurlは直接たたくときなどで使うかなと思い一応出すようにした

Scheduler

CloudRunでいくつかエンドポイントをもたせて色々試していた

そのため各エンドポイントに対してリクエストを送るSchedulerを構築する

今回は3つスケジューラが作成されるようにした

  • scheduler.tf
locals {
  params = {
    storage        = {
      body = "storage request body"
      cron  = "0 0 1 * *"
    }
    secret_manager = {
      body = "secret_manager request body"
      cron  = "0 0 1 * *"
    }
    fixed_ip       = {
      body = "fixed_ip request body"
      cron  = "8 * * * *"
    }
  }
}

resource google_cloud_scheduler_job job {
  name             = "test-job-${each.key}"
  description      = "test http job"
  schedule         = each.value.cron
  time_zone        = "Asia/Tokyo"
  attempt_deadline = "320s"
  project          = local.project
  region           = local.region

  # params分だけリソースを作成する(今回だと3つ)
  for_each = local.params

  retry_config {
    retry_count          = 1
    max_backoff_duration = "3600s"
    max_doublings        = 5
    max_retry_duration   = "0s"
    min_backoff_duration = "5s"
  }

  http_target {
    http_method = "POST"
    uri         = "${local.url}/${each.key}"
    body        = base64encode(jsonencode(each.value))
    headers     = {
      "Content-Type" = "application/json"
    }

    oidc_token {
      audience              = local.url
      service_account_email = google_service_account.run_invoker.email
    }
  }
}

http_targetでスケジュールが発火したらどのようなリクエストを送るのかを設定する

  • audience
    • CloudRunで発行されたURLを指定
  • service_account_email
    • 実行するサービスアカウントを指定

ここで指定したアカウントがスケジュール発火時にトークンを生成し認証に使う

GCS

  • storage.tf
resource google_storage_bucket bucket {
  name          = "sample-cloudrun-with-gcs"
  force_destroy = true
  labels = {
    environment = "development"
    managed_by  = "terraform"
  }
}

output bucket_url {
  value       = google_storage_bucket.bucket.url
  description = "base URL of the Bucket"
}

一応GCSも作成するようにしてみた

デプロイ

CloudRunのデプロイをTerraformで管理するようにしたため次のような問題が起きた

  • イメージの変更があった場合でもCloudRunの設定自体には変更がない
  • そうするとTerraform上では変更がないのでCloudRunのデプロイがたたかれない
  • 結果イメージの中身が変わらない

ワークアラウンドだがコミットハッシュをCloudRun実行時の環境変数に渡すように変更することでCloudRunのデプロイをさせるようにしてみた

terraform apply -var-file default.tfvars -auto-approve -var="short_sha=$(git rev-parse --short HEAD)"

手動実行

Schedulerからも実行できるが直接CloudRunを実行できる

CloudRun

$ curl -X POST -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://sample-run-xxxxxxxxxx-uc.a.run.app/ -d '{}'
Hello World!

Schedulerも内部的にはサービスアカウントでgcloud auth print-identity-tokenを用いてトークンを発行しているのかなーと思った

GUI

スケジューラから今すぐ実行で実行してみた

無事実行されていることを確認した

まとめ

  • Terraformで各種リソースの作成
    • GCS
    • CloudRun
    • Cloud Scheduler x 3
  • CloudRunからGCSへのアクセス確認
  • CloudRunからSecretManagerへのアクセス確認

次のリポジトリに今回素振りした内容のコードを置いた

terraform-sample/google/scheduler-run at master · swfz/terraform-sample

github.com

CloudRunに認証アクセスさせるにあたり色々試行錯誤したので勉強になった