素振り目的でやってみた
この記事は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
イメージのビルド
次のリポジトリでビルドした
色々雑だが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
CloudRunに認証アクセスさせるにあたり色々試行錯誤したので勉強になった