terraform-cloud functions
![Yuichi](https://res.cloudinary.com/zenn/image/fetch/s----bz66US--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/6b2b4da125.jpeg)
公式サイトでは Homebrew を利用したインストールが案内されている。
$ brew tap hashicorp/tap
$ brew install hashicorp/tap/terraform
tfenv
tfenv というバージョンマネージャを利用してインストールすることもできる。
$ brew install tfenv
$ tfenv --version
=> tfenvのバージョン番号出力
$ tfenv list-remote
=> インストール可能なterraformのバージョン番号が一覧表示
$ tfenv install バージョン番号
=> バージョン番号記載しなかった最新の安定版が入る
# どのバージョンのterraformを利用するか指定する
$ tfenv use バージョン番号
$ terraform --vesion
=> terraformのバージョン番号出力
ディレクトリ配下に置いた.terrafrom-versionにバージョン番号を書いておけば、tfenv でインストール済みのバージョンに切り替わる。
- GCSを手動作成する
terraform {
required_version = "1.4.6"
backend "s3" {
bucket = "1で作ったバケット名"
key = "terraform.state"
region = "ap-northeast-1"
}
}
provider "aws" {
region = "ap-northeast-1"
}
- bucketを作成したストレージを指定、コードの例ではS3
![Yuichi](https://res.cloudinary.com/zenn/image/fetch/s----bz66US--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/6b2b4da125.jpeg)
tfファイルは名前はなんでもいいらいしい
$ cd 'terraformファイルがあるフォルダ'
# terraformの初期化を行うコマンド
# 必要なproviderやmoduleのダウンロードが実行される。
# terraform使うよって宣言
# この段階では"terraform.state"はストレレージに作成されない。リソースを作成した段階で作成される
$ terraform init
# 構文チェックができる。間違っている箇所を指摘してくれる
$ terraform validate
# 実行計画を表示するコマンド。dry run
# 追加や変更は実際には実行されない。実際に何を消すか作るかを表示してくれる
# 未設定はデフォルト値になる。たまにクラウドもしくはterrformのデフォルト値があわることがあるので影響調査が必要
$ terraform plan
# main.tfファイルで定義した内容を反映するコマンド
$ terraform apply
resource "aws_sns_topic" "terraform_test" { # terrformで管理している名前
name = "terraform-test" # クラウドで管理している名前
}
![Yuichi](https://res.cloudinary.com/zenn/image/fetch/s----bz66US--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/6b2b4da125.jpeg)
gcloud config configurations list
- gcloud config configurations listでどの configuration が有効になっているかを確認できます
$ gcloud config configurations list
NAME IS_ACTIVE ACCOUNT PROJECT COMPUTE_DEFAULT_ZONE COMPUTE_DEFAULT_REGION
default True xxx@gmail.com xxx
xxx False yyy@gmail.com yyy
yyy False xxx@gmail.com xxx
gcloud config configurations activate [NAME]
gcloud config configurations activate [NAME]
指定したアカウント名の gcloud CLI 構成をアクティブにします。
今回はすでにアクティブ済み
gcloud config set
gcloud config set
当該の configuration の project フィールドが更新
今回はすでに設定済み
gcloud auth login
gcloud auth login
コマンドはユーザーアカウントを認証し、その結果を gcloud CLI の設定に保存します
gcloud auth application-default login
gcloud auth application-default login
gcloud auth application-default login コマンドは、アプリケーションがGoogleCloudAPIを使用するための認証情報を取得します。
![Yuichi](https://res.cloudinary.com/zenn/image/fetch/s----bz66US--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/6b2b4da125.jpeg)
既存のリソースを読み込みます。
tags にzukkie-terraform-stateバケットの情報を定義することが出来ます。
data "aws_s3_bucket" "zukkie_terraform_state" {
bucket = "zukkie-terraform-state"
}
resource "aws_sns_topic" "terraform_test2" {
name = "terraform-test2"
tags = data.aws_s3_bucket.zukkie_terraform_state
}
![Yuichi](https://res.cloudinary.com/zenn/image/fetch/s----bz66US--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/6b2b4da125.jpeg)
module
- terraformコードを分割する事が出来ます。
- 開発環境と本番環境で設定値が違うリソースを作りたい時など、resourceを1つだけ書いて異なる設定値を変数で渡すだけで済むので視認性がよくなります。
terraform import - 手動で作ったリソースをterraformで管理したい場合などに使います。
試しに 手動で作ったtfstateファイルを保存しているS3をimportします。
![Yuichi](https://res.cloudinary.com/zenn/image/fetch/s----bz66US--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/6b2b4da125.jpeg)
ブロックタイプの解説
![Yuichi](https://res.cloudinary.com/zenn/image/fetch/s----bz66US--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/6b2b4da125.jpeg)
terraform destroy
terraform destroyコマンドというリソースを削除するコマンドがありますが
削除したいリソースがある時は対象のリソースをコメントアウトしてterraform applyをすればOK
理由は terraform は tfstateファイルとtfファイルを比較してtfファイルの方に合わせる様にリソースを作成・削除するためです。
![Yuichi](https://res.cloudinary.com/zenn/image/fetch/s----bz66US--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/6b2b4da125.jpeg)
のPub/Subなしバージョン
moduleの中のフォルダでコネクタを分けていく
saas-a/variables.tf に該当のテーブルリストを記載しておくと、テーブルリストごとにCloud FunctionsやCloud Schedulerなどのセット作成
.
├── README.md # プロジェクト全体の説明や使用方法
├── projects
│ └── SaaS-A
│ └── README.md # SaaS-A プロジェクトの詳細説明
└── tf
├── backup.tf # バックアップ設定用 Terraform ファイル
├── saas-a.tf # SaaS-A 固有のリソース定義
├── module
│ └── saas-a
│ ├── README.md # SaaS-A モジュールの説明
│ ├── cloudfunctions.tf # Cloud Functions リソース定義
│ ├── http_body
│ │ └── body.json # HTTP リクエストボディのテンプレート(必要な場合)
│ ├── iam.tf # IAM 設定用 Terraform ファイル
│ ├── monitoring.tf # モニタリング設定用 Terraform ファイル
│ ├── scheduler.tf # Cloud Scheduler 設定用 Terraform ファイル
│ ├── service_account.tf # サービスアカウント設定用 Terraform ファイル
│ ├── source
│ │ ├── conf
│ │ │ ├── schema.json # データスキーマ設定(必要な場合)
│ │ ├── main.py # Cloud Functions のメインコード
│ │ ├── requirements.txt # Python 依存関係リスト
│ │ └── util
│ │ ├── bq.py # BigQuery 関連ユーティリティ
│ │ ├── cmc_api_client.py # CMC API クライアント
│ │ └── log.py # ロギングユーティリティ
│ ├── storage.tf # Cloud Storage 設定用 Terraform ファイル
│ └── variables.tf # モジュール変数定義
├── provider.tf # Terraform プロバイダー設定
├── variables.tf # ルートモジュール変数定義
└── workload_identity.tf # Workload Identity 設定用 Terraform ファイル
![Yuichi](https://res.cloudinary.com/zenn/image/fetch/s----bz66US--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/6b2b4da125.jpeg)
スモールで
事前に、"test-data-terraform-001"を作成しておく
.
├── backup.tf
├── module
│ └── saas-a
│ ├── cloudfunctions.tf
│ ├── source
│ │ ├── main.py
│ │ └── requirements.txt
│ ├── storage.tf
│ └── variables.tf
├── provider.tf
├── saas-a.tf
└── variables.tf
-
backup.tf
:
# ローカル変数の定義
# (この部分は元のコードには含まれていませんでしたが、通常はここにlocals{}ブロックが来ます)
# プロバイダーの設定
terraform {
# 必要なプロバイダーの指定
required_providers {
google = {
source = "hashicorp/google" # Google Cloud Platformのプロバイダー
version = ">= 5.23.0" # バージョン5.23.0以上を使用
}
}
# Terraformのバージョン指定
required_version = ">= 1.7.0" # Terraform 1.7.0以上が必要
# バックエンドの設定(状態ファイルの保存先)
backend "gcs" {
bucket = "test-data-terraform-001" # Google Cloud Storageのバケット名
prefix = "terraform/state" # バケット内のプレフィックス(パス)
}
}
-
module/saas-a/cloudfunctions.tf
:
resource "google_cloudfunctions_function" "function" {
for_each = toset(var.target_apis) # toset()関数は、入力をセットに変換します。これにより、各要素に対して1つのCloud Functionが作成されます。
name = each.key # each.keyはfor_eachループの現在の要素を参照
# name = "hello-world-00001"
description = "My Cloud Function"
runtime = "python312"
available_memory_mb = 128 # メモリの割り当て(MB)
timeout = 540 # タイムアウト(秒)
source_archive_bucket = google_storage_bucket.cf-source.name # ソースコードが格納されているバケット
source_archive_object = google_storage_bucket_object.archive.name # ソースコードのアーカイブオブジェクト
trigger_http = true # HTTPトリガーを有効にする
entry_point = "hello_world"
}
-
module/saas-a/source/main.py
:
# cloudfunctions/main.py
def hello_world(request):
return "Hello, World!"
-
module/saas-a/source/requirements.txt
:
# 必要なPythonパッケージを列挙
requests
-
module/saas-a/storage.tf
:
# Cloud Functionsのソースファイル格納用バケット
resource "google_storage_bucket" "cf-source" {
name = "${var.project_id}-cf-saas-a"
location = "asia-northeast1"
}
# Cloud Functionsのソースファイルをzip化
data "archive_file" "function_archive" {
type = "zip"
source_dir = "${path.module}/source"
output_path = "${path.module}/source/cloud_function.zip"
}
# Cloud Functionsのソースファイルをバケットにアップロード
resource "google_storage_bucket_object" "archive" {
name = "functions/src_${data.archive_file.function_archive.output_md5}.zip"
bucket = google_storage_bucket.cf-source.name
source = data.archive_file.function_archive.output_path
}
-
module/saas-a/variables.tf
:
variable "project_id" {}
variable "target_apis" {
type = list(string)
default = ["func-table-001"] # テーブルに応じて追加していく
}
-
provider.tf
:
# Google Cloud Platformのプロバイダー設定
provider "google" {
project = var.project_id # プロジェクトIDを変数から取得
region = "asia-northeast1" # デフォルトのリージョンを東京に設定
}
-
saas-a.tf
:
# saas-a連携用のモジュールを呼び出す
module "saas-a" {
# モジュールのソースパスを指定
# 現在のディレクトリの下の"module/saas-a"ディレクトリにあるモジュールを使用
source = "./module/saas-a"
# プロジェクトIDを変数から渡す
# この変数は親モジュールで定義されていると想定
project_id = var.project_id
}
-
variables.tf
(ルートモジュール):
# プロジェクトIDを定義する変数
variable "project_id" {
type = string # 変数の型を文字列に指定
default = "cookbiz-data" # デフォルト値として "cookbiz-data" を設定
}
![Yuichi](https://res.cloudinary.com/zenn/image/fetch/s----bz66US--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/6b2b4da125.jpeg)
scheduler.tf
resource "google_cloud_scheduler_job" "~~~~" {
schedule = "30 0 * * *"
time_zone = "Asia/Tokyo" # 今度からこれいれとこ、毎回cronの時間の脳内変換がめんどくさいので
# HTTP呼び出しの設定
http_target {
http_method = "POST"
uri = google_cloudfunctions_function.transfer_porters_to_gcs[each.key].https_trigger_url
# ボディーの設定
body = base64encode(file("${path.module}/http_body/body.json")) # bodyなかったらエラーになった無知でした
# リクエストヘッダーの設定
headers = {
"Content-Type" = "application/json"
}
# OIDCトークンを使用した認証
oidc_token {
service_account_email = google_service_account.data-transfer-porters.email
}
}
# /http_body/body.json
{
}
![Yuichi](https://res.cloudinary.com/zenn/image/fetch/s----bz66US--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/6b2b4da125.jpeg)
iam.tf
# ローカル変数の定義
locals {
# ローカルからCloud Functionsを呼び出すユーザを定義。
invoke_users = toset([
"~@~"
])
}
# Cloud Functions呼び出し権限(ユーザ)
resource "google_cloudfunctions_function_iam_member" "invoker" {
# 各ユーザとAPIの組み合わせで権限を付与、一つのユーザーに必要な権限つけるfor文
for_each = {
for item in setproduct(
[for invork_user in local.invoke_users : invork_user],
[for target_api in var.target_apis : target_api]
) : join("-", item) => item
}
~
![Yuichi](https://res.cloudinary.com/zenn/image/fetch/s----bz66US--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/6b2b4da125.jpeg)
workload_identity.tf
Workload Identity 連携とは
外部のワークロードに対して Google Cloud のサービスアカウントの権限を利用させることができる機能です。
サービスアカウントキーを利用する方法よりセキュアな認証方式となっておりますので、GitHub Actions や AWS をはじめ、他の外部サービスから Google Cloud を操作させたい場合に
Workload Identity 連携を設定する際の注意点について解説します。
Workload Identity プロバイダ設定時に属性条件を指定しない場合
Workload Identity プロバイダを設定する際に属性条件を指定しないと、任意の外部IDがそのプロバイダを通じてGoogle Cloudのサービスアカウントを利用できるリスクがあります。これは、サービスアカウントとプロバイダ名を知っているだけで、誰でもそのサービスアカウントの権限を利用できる状態を招く可能性があるためです。このため、特定の属性条件を設定し、特定のユーザーやリソースのみがアクセスできるように制限をかけることが推奨されます。
IAM 設定時に Workload Identity プール配下の外部ID全てに対して権限を付与する場合
Workload Identity プール配下のすべての外部IDに対して権限を付与すると、プール内の任意の外部IDがその権限を利用できる状態になります。これにより、意図しないユーザーやリソースがサービスアカウントの権限を行使できるリスクが生じます。特にGitHubのようなマルチテナントのIDプロバイダと連携する場合、特定の組織やリポジトリに限定して権限を付与することが重要です。
推奨される対策
- 属性条件の設定:Workload Identity プロバイダ設定時に属性条件を指定し、特定のユーザーやリソースのみがアクセスできるようにします。例えば、GitHubの特定の組織やリポジトリに限定することで、セキュリティを強化できます。
- IAM設定の細分化:IAM設定時に、Workload Identity プール配下のすべての外部IDに対して権限を付与するのではなく、特定の属性に基づいて権限を付与します。これにより、特定のリポジトリやチームに対してのみサービスアカウントの権限を付与することが可能になります。
- ベストプラクティスの遵守:Workload Identity プールとプロバイダを専用のプロジェクトで管理し、組織ポリシーの制約を使用して他のプロジェクトでのプロバイダの作成を無効にするなどのベストプラクティスを遵守します。
これらの対策を講じることで、Workload Identity 連携のセキュリティを強化し、不正なアクセスや権限の濫用を防ぐことができます。
![Yuichi](https://res.cloudinary.com/zenn/image/fetch/s----bz66US--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_70/https://storage.googleapis.com/zenn-user-upload/avatar/6b2b4da125.jpeg)
API データ連携(テンプレート:README.md)
仕様
処理概要
API を実行し、Storage へ連携する。
- [処理ステップを記述]
システム構成
- Runtime: Python 3.12
- Cloud Scheduler → Cloud Functions → Cloud Storage[追加の構成情報]
環境変数
変数名 | 設定値 | 備考 |
---|---|---|
API_KEY |
API キー | Secret Manager に登録された値を取得する。 |
TZ |
Asia/Tokyo |
API キーとデータベースURLはSecret Managerへ手動で登録する。
input
起動時の HTTP パラメータ
キー | 値 | 必須 | 備考 |
---|---|---|---|
target_date |
データ取得基準日(default: 起動日前日) | ◯ | リカバリ実行時などに指定 |
output
各テーブルはTARGET_DATE
でパーティション化されている(BigQuery 上では_PARTITIONTIME
パーティショニング列が設定されている)
{project}.{dataset}.{table}
カラム名 | カラム型 | モード | 説明 |
---|---|---|---|
user_id |
STRING |
REQUIRED |
ユーザー識別子 |
event_type |
STRING |
REQUIRED |
イベントタイプ |
timestamp |
TIMESTAMP |
REQUIRED |
イベント発生時刻 |
metadata |
JSON |
NULLABLE |
イベント関連メタデータ |
運用
- ジョブの失敗時・過去データのリカバリ時には、Cloud Functions 起動用の HTTP パラメータに
target_date
を指定して実行する -
TARGET_DATE
を同じ日付で繰り返し実行した場合、BigQuery 上のデータは重複することなく、パーティションごとに洗い替えられる
アラート
monitoring.tfで定義。ALERT
レベルのログが出力された際にSlackへ通知する。
通知先: #{slack_channel}
ローカルからの Cloud Functions 呼び出し
roles/cloudfunctions.invoker
を持つユーザーで実行する。
iam.tfにて権限を設定する。
日付指定実行
text
TARGET_API={function_name}
curl -X POST \
$(gcloud --project={project_id} functions describe ${TARGET_API} --region {region} --format="value(httpsTrigger.url)") \
-H "Authorization: Bearer $(gcloud auth print-identity-token)" \
-H "Content-Type: application/json" \
-d '{"target_date": "2024-03-31"}'
全件取得
text
TARGET_API={function_name}
curl -X POST \
$(gcloud --project={project_id} functions describe ${TARGET_API} --region {region} --format="value(httpsTrigger.url)") \
-H "Authorization: Bearer $(gcloud auth print-identity-token)" \
-H "Content-Type: application/json" \
-d '{"mode": "full_refresh"}'
参考
[参考リンクや文書]