Scheduler + Pub/Sub + Cloud Functions を Terraform でイイ感じに管理する
はじめに
Cloud Functionsで軽量なバッチシステムを構築していたところ、システムを構成するリソースが多く、手動管理に限界を感じてTerraformを導入しました。
Google Cloud公式チュートリアルを参考に導入したものの、あくまでチュートリアルなので、実用面でそのまま使うには不便な点がいくつかありました。
そこで本記事では、公式チュートリアルをより実践的なTerraformコードに育てるポイントをいくつか紹介します。
実行環境
- macOS Ventura 13.6.2
- Terraform v1.6.4
- Cloud Functions 2nd Gen
システム構成
主なリソースおよび最終的なシステム構成は以下の通りです。
リソース名 | 用途 |
---|---|
Cloud Scheduler | 定期的なイベント実行管理 |
Pub/Sub | イベント受信・送信 |
Cloud Functions | アプリケーション実行環境 |
Secret Manager | シークレット管理 |
Cloud Storage | ソースコード置き場(Cloud Functionsデプロイ用) |
Terraform Cloud | Terraform実行環境・ステート管理 |
テンプレート
はじめに、Google Cloud公式チュートリアルで出来ることを確認しておきます。
また、以降は公式チュートリアルのTerraformコードを「テンプレート」と呼称します。
-
google_service_account
- Cloud Functions用のサービスアカウントを作成する
-
google_pubsub_topic
- Pub/Subリソースを作成する
-
google_storage_bucket
- Cloud Functionsデプロイ用のストレージを作成する
-
archive_file
- Terraform実行環境上でソースコードをzip化する
-
google_storage_bucket_object
- zip化したソースコードをCloud Storageにアップロードする
-
google_cloudfunctions_function
- Cloud Functionsリソースを作成する
前述のシステム構成にテンプレートのカバー範囲をマッピングすると以下の通りです。
ポイント
Cloud Schedulerを同時にデプロイする
テンプレートは、汎用的なPub/Subトリガーのサンプルであり、Cloud Schedulerが含まれていない(必要ないユースケースがある)ため、追加したいです。
これはgoogle_cloud_scheduler_job
リソースを追加するだけで簡単に実現できます。
+resource "google_cloud_scheduler_job" "default" {
+ name = "scheduler"
+ schedule = "0 0 * * *" # 毎日0時(JST)に実行
+ time_zone = "Asia/Tokyo"
+
+ pubsub_target {
+ topic_name = google_pubsub_topic.default.id
+ data = "{\"name\": \"Haru\"}"
+ }
+}
機密情報はSecret Managerで管理する
Cloud Functionsに限った話ではありませんが、環境変数は機密情報の格納先に適していません。[1]
そのため、機密情報はSecret Managerで管理するようテンプレートを変更したいです。
これを実現する詳細な手順は上記のドキュメントに記載されていますが、ここではTerraformを用いて実行していきます。
ただし、1.のシークレット保存については機密情報の取り扱いが厄介なので、ここでは手動で登録することとします。
- Secret Managerにシークレットを保存する。(手動)
- Cloud Functions用サービスアカウントに
secretmanager.secretAccessor
ロールを付与する。 - Cloud Functionsからシークレットを参照する。
+# Cloud Functions用サービスアカウントにシークレットアクセス権限を付与する
+resource "google_project_iam_member" "secret_accessor" {
+ project = "project_id"
+ role = "roles/secretmanager.secretAccessor"
+ member = "serviceAccount:${google_service_account.default.email}"
+}
resource "google_cloudfunctions2_function" "default" {
service_config {
+ # Secret Manager参照先を指定する
+ secret_environment_variables {
+ key = "SECRET_CONFIG_TEST" # 環境変数名
+ project_id = "project_id"
+ secret = "SECRET_NAME" # Secret Managerに登録したシークレット名
+ version = "1" # シークレットのバージョン
+ }
}
}
ソースコードの変更を検知する
テンプレートのままでは、ソースコードのみ変更した場合、Cloud Storageの再アップロードは発生しますが、Cloud Functionsの再デプロイまでは行われません。[2]
そのため、ソースコードが変わる(つまり、Cloud Storageにアップロードするzipファイルが置き換わる)と、それを検知して再デプロイするように変更したいです。
これはTerraformのlifecycle
メタ引数を用いることで簡単に実現できます。
resource "google_cloudfunctions2_function" "default" {
+ # Google Storageにアップロードしたzipファイルの変更を検知する
+ lifecycle {
+ replace_triggered_by = [
+ google_storage_bucket_object.default
+ ]
+ }
}
不要ファイルをデプロイしない
テンプレートのarchive_file
リソースは、指定したフォルダ配下のすべてのファイルをzip化します。
しかし、ソースコードの中には、.env
やテスト用コードなどが含まれるため、これらの不要ファイルはデプロイしないように変更したいです。
これは excludes
パラメータで簡単に実現できるのですが、残念ながらファイル単位かつ、ワイルドカードが使えません。
長らく要望はIssueに挙がっていますが未だに対応されておらず、ファイル数が多い場合はコメントで提案されているような対応が必要になります。[3]
ここでは、最低限の対応として.env
ファイルのみを除外しておきます。
data "archive_file" "default" {
type = "zip"
output_path = "/tmp/function-source.zip"
source_dir = "function-source/"
+ # 除外したいファイルを指定する
+ excludes = [
+ ".env",
+ ]
}
まとめ
最終的にできたTerraformコードはこちらになります。
本記事での主軸ではないので割愛していますが、同じ構成のバッチシステムが増えた時に再利用しやすいよう、変数ファイルは分離しています。
以下の手順に沿って実行すると、テンプレートを基にカスタマイズTerraformコードが実行できます。
-
terraform/main.tf
を編集する -
gcloud auth application-default login
で認証をする -
terraform
コマンド実行
ただし、現状ではTerraform実行・ステート管理がローカル依存となっているので、実際にチームで運用する場合には、以下のような対応が追加で必要になります。この辺りは長くなってきたので、また別の記事でぼちぼち書いていきます。
- Terraform Cloudを導入する
- ステートをCloud Storageに保存する + 実行はサービスアカウントを利用する
最後になりますが、Terraformコードの書き方に悩んだ際には、Google Cloud公式がガイドラインを出してくれていますので、参考にしてみてください。
-
環境変数は関数の構成に使用できますが、データベースの認証情報やAPIキーなどの機密情報の格納には適しません。 このような機密性の高い値は、ソースコードや外部の環境変数以外の場所に保存する必要があります。
(中略)
シークレットを保存するには、Secret Managerを使用することをおすすめします。
https://cloud.google.com/functions/docs/configuring/env-var#managing_secrets ↩︎ -
Updating Cloud Functions' source code requires changing zip path
https://github.com/hashicorp/terraform-provider-google/issues/1938#issuecomment-1229042663 ↩︎ -
Support glob paths in archive_file data source excludes
https://github.com/hashicorp/terraform-provider-archive/issues/62 ↩︎
Discussion