📝

TerraformとGoogle Cloudの私的ベストプラクティスとかなんとか

2024/12/06に公開

この記事はGoogle Cloud Champion Innovators Advent Calendar 2024 の 6日目の記事です。

おはこんにちばんわ。サントリー(soundTricker)こと大橋です。
Google Cloud & Terraformをいくつかの会社 & プロジェクトで構築、運用してみて、知見や思うことが出てきたのでちょっとまとめてみたいと思います。

なお久しぶりに記事を書くのでダラダラ書きます。

対象者

  • Google CloudでTerraformを始めたい人/導入を検討している人
    • ただしTerraformの使い方とかは書いていません。
  • Google CloudでTerraformを書いていて悩んでいる人
  • 暇な人
  • Google Cloud老人会の方々

この記事の目的

この記事はこれからGoogle CloudへTerraformを導入するか悩んでいる方や、今Google CloudでTerraformを利用して管理を行っている方に向けて、ベストプラクティスの共有や、運用する上での注意点をまとめた記事です。

ただし、Terraformの基本的な操作方法や、Google CloudでのTerraformの始め方については、他の記事がたくさんありますので、そちらを参照してください。

最初にまとめ

  • Terraform化する範囲は好きでいい 既存プロジェクトは無理する必要はない ただ財産にはなる
  • ベストプラクティスをちゃんと読もう
  • CIはやっておいた方が良い CDは要検討
  • どこまでやるかは「開発者がさわらないところまで」がおすすめ
  • sandboxプロジェクトはあるといいよ

お前誰よ

知ってる人興味ない人は飛ばしておk

私はsoundTricker(通称 サントリー)というSNS名で活動している大橋というものです。

普段は 株式会社カブク(製造業向けサービス業)のCTOをやっており、また似た名前ですが、株式会社Kabuk Style(旅行業)などいくつかの会社を副業でお手伝いしています。[1]

Google Cloud歴はApp Engineが出たぐらいからで、もう何年か覚えてません。
その他コミュニティ活動で、GCPUG東京などのオーガナイザーの一人をやっています。最近は何もやってません。

また Google より Google Developer Experts 2024(GDE)(Workspace)と Google Cloud Champion Innovators(Google Workspace) というのをやらせていただいています。
どちらもGoogle Workspaceを担当していて、GDEとしてはGoogle Workspaceの動向を追い続けて、登壇したり、Apps Script周りの話をしたりしています。
仕事や個人としてはGoogle Cloud周りの構築・運用・雑用、バックエンド開発やフロントエンド開発、CI/CD、DevOps、開発プロジェクトの立ち上げ、雑用、ツールづくりとかを得意としています。フルスタックエンジニアではありません。

本記事を読む上の注意点

Google CloudのProjectと案件などを指すプロジェクト、
Google CloudのCloud Runの中に定義したServiceや、VPCなどGoogle Cloud上のオブジェクトを表すリソースとTerraformのResourceなど言葉が同じものがあります。
本記事では

  • Google CloudのProjectは英字で「Project」それ以外の案件などを指す言葉はカタカナで「プロジェクト」
  • TerraformのResourceやModuleは英字で「Resource」「Module」、それ以外のGoogle Cloud上のオブジェクトなどはカタカナで「リソース」

と書くようにしています。

普通に会話しててもこのあたりの言葉ってわかりにくいですよね。すみません。注意して読んでください。

昨今のTerraformとGoogle Cloud

昨今では、Infrastructure as Codeの流行りもあり、インフラ構成管理をコードで行うことが多くなってきてます。
また改訂版のISMS(JIS Q 27001:2023)でも構成管理の項が追加され、大企業の方も管理部門に何かしらの構成管理ツールを導入しろと言われている人も多いのではないでしょうか?

Google Cloudにおいても公式ドキュメント中に各種リソースの定義方法として、Cloud Console、Cloud SDKと並びTerraformのコードが紹介されており、Google Cloud 公式に構成管理はTerraformでという雰囲気を感じます。

最近ではInfrastructure Manager(Infra Manager)というサービスも提供されており、Terraformの実行や履歴、Stateファイル[2]の管理もGoogle Cloud上でできるようになり、今後は更にTerraformを利用したGoogle Cloudの構成管理が一般的になると思います。

https://cloud.google.com/infrastructure-manager/docs/overview

Deployment Manager ? あの子は影が薄くてイマイチ使われてるのかわかりません。(多分Cloud Marcketplaceとかにリリースしている企業とかは使っていたと思います)
Deployment Managerは事例も少ないので、現状Google Cloudで構成管理ツールを使うのであればTerraformが一般的で、情報も多いです。

私は今Terraformを導入するべきか

初めてTerraformの導入を考える上で大事なのは「「新規プロジェクト」「既存プロジェクト」のどちらであるか?」です。

新規プロジェクトであれば、Projectもっと言えば、Google CloudのProject Folderなどを含めてTerraform化するのは比較的簡単です。ぜひトライしてください。

ただ既存プロジェクトでかつ、初めてTerraformを扱う場合は注意が必要です。
乱暴に言えば、Google Cloud向けのTerraform Resourceでは、Terraform上で設定していないリソースをすべて消してしまう場合があり、知識がない状態で安易にTerraformを使用してしまうと、大事故に繋がります。繋がりました。

ですので、有識者に基本的なコードやフローを作成してもらうことをお勧めします。
もし有識者がおらず、既存プロジェクトかつ初めてTerraformを利用する場合は以下にも記載しますが最低限IAM周りは注意してください。

Terraform中級者はまぁ頑張ってください。あなたならできます。

Google CloudでのTerraformの始め方

公式ドキュメントや、いろいろな人のやってみた、初心者向けの記事を読むのは当たり前ですが、それ以外にGoogle Cloud観点で読むべき記事や、考慮するべきことを書いていきます。

https://developer.hashicorp.com/terraform?product_intent=terraform

何を読むべきか

必ずGoogle Cloudが公開しているTerraformのベストプラクティスを一読してください。

https://cloud.google.com/docs/terraform/best-practices-for-terraform?hl=ja

この記事では以下を推奨/非推奨とともに紹介しています。

  • Terraformのスタイル・構成のガイドライン
  • 再利用可能なTerraformモジュールの作り方
  • 複数環境下への適応方法(ルートモジュールの構成方法)
  • バージョン管理
  • 運用
  • セキュリティ

コードの書き方から、セキュリティ、運用まで幅広くカバーしており、私は最初に読んでいなくて後悔しました。ものすごく後悔しました。なので、読んだことがない方はぜひ読んでください。

Terraformを利用したことがある方にも特に悩みやすい複数環境(Develop,Staging,Productionなど)でworkspace機能を利用すべきかどうか?などについても言及しており、良い指針になります。

また初めてTerraformを触るのであれば、以下も一通りやっておくと、利用感がわかると思います。

https://cloud.google.com/docs/terraform/get-started-with-terraform?hl=ja

その他 実際にコードを書き始める段階でGoogle Cloud用のTerraform Resource(以下Resource)[3]のドキュメントを見る必要があるので、以下もブックマークしておくと良いと思います。

特に後者のTerraform Google ModulesはGoogle Cloud向けのTerraform Module[4]でGoogle Clodu ProviderのTerraform Resourceだけで構築したあとに、「便利なModuleあるじゃん....」ということが多々あるので、セットで何があるかを把握しておくと良いです。

どこからはじめるか

新規のプロジェクト

新規のプロジェクトであれば、Project の作成からTerraformで管理してみると良いです。
そこからサービスアカントや、Cloud Run、VPC、LBなど必要なResourceを追加していく感じです。

初期段階であれば Project から作成しておけば、何かミスした場合や、気に入らない場合でも Project ごと削除できるので、try and errorで繰り返すことでTerraformに慣れる事も可能です。

既存のプロジェクト

Terraformではimport コマンドや、import ブロックを利用することで既存リソースをTerraform管理化に置くことができます。(stateファイルに取り込むことができる)

またimport ブロックとplan コマンド-generate-config-outによりimportしたリソースのterraformコードを出力することも可能です。

それ以外にもterraformerConfig Connector[5]で既存リソースを一括で出力することも可能です。
ただこれらのツールはベストプラクティスにあるディレクトリ構成とは異なる形で出力されるため、本当に使うために出力すると言うよりは、参考用に出力するイメージがあります。(importブロックも出力してくれるので便利ではあります)

また既存プロジェクトの場合は「IAM と Role のみ」や「新しく作成するリソースのみ」というのも比較的導入コストが低く始められる部分だと思います。

小話: 全てのIAMと紐づくロールを出力したい

既存プロジェクトでIAM や紐づくロール(IAMポリシー)を全てTerraform管理化に置きたい場合、何かしらの方法で一度すべてのIAMとIAMポリシーを出力したいと思います。 特にCloud StorageやBigqueryのDatasetsやTable、Cloud Runなどリソースに対して紐づいているIAMポリシーは画面上で一覧化しようと思うと骨が折れます。

その際に便利になるのが、Cloud Asset Inventory です。

https://cloud.google.com/asset-inventory/docs

Cloud Asset Inventory は Google Cloud 上のアセット(リソース、IAMポリシー)を管理、検索できるサービスで、Cloud Consoleからも利用可能です。

Cloud Console 上では IAMポリシー を一覧からフィルタ、csv として出力することが可能です。
また、Cloud SDK 経由でも以下のコマンドで利用できます。

gcloud asset search-all-iam-policies --scope='organizations/123456' --query='policy:amy@mycompany.com'

Refs: https://cloud.google.com/sdk/gcloud/reference/asset/search-all-iam-policies

APIや各種言語のクライアントライブラリからも利用可能なので、IAMポリシーの一覧を出力し加工、Terraform用の変数一覧として出力なども可能となります。

どこまでやるべきか

なんとなく考えるとTerraformのGoogle Cloud Providerで定義されているResourceがあるならば、全てをTerraformで表現したほうが良さそうに感じますよね?
ただ実際に運用していくと全てをTerraformで管理してしまうと面倒な部分が出てきます。
なので全てをTerraform下で管理するのはお勧めできません。

ではどこまで管理することを目指すといいのでしょうか?
私の感覚で言えば、「(複数の)開発者が開発中に触りたい部分はTerraformで管理しない(別の手段で管理する)」が今のところ良い指針です。

Terraformは1つの状態管理ファイル(stateファイル)を元にインフラ側の状態と、Terraformのコード(tfファイル)の状態の差分を取ってインフラ側リソースの追加/変更/削除を決定します。
このため1人がtfファイルを操作している場合は問題が発生しないのですが、複数人がtfファイルを並行開発で操作していると問題が発生しやすいです。

例えばAがfeature/aブランチで開発中にstaging環境へCloud Scheduler のスケジュールaを追加したいと思い、tfファイルに追記、terraform applyを実行 その後Aがstagingにてテストを実行中に、別の案件でBがfeature/bブランチでstaging環境へ別のCloud Schedulerのスケジュールbをしようとtfファイルへ追記、terraform applyを実行した場合、feature/bブランチにはfeature/aブランチの変更が無いため、スケジュールaは削除されます。

例えば、以下のような物は開発者が自身のブランチ中で追加/修正/削除することが多く上記の例のようなことが発生しやすいため、CIによるデプロイなどでterraformでは管理しないようにしています。

  • Cloud Runのリビジョン(デプロイされるイメージバージョン)、環境変数
    • Cloud Runのサービス自体はterraformで管理 Cloud Runサービスに設定するネットワーク関連もterraformで管理
    • 環境変数は開発者側で追加修正したいことが多い
  • 開発者がコントロールしたい秘匿データ(APIキーとか)を入れるSecret Manager
    • ただしDBのパスワードやIPアドレスなど、terraformによって管理されるリソースにより作成されるシークレット値はterraformで管理する
  • Cloud TasksのQueue
  • Cloud Schedulerのスケジュール
  • BigQueryのテーブル
    • Datasetsは権限の付与を含めてTerraformで管理したほうが無難

小話: じゃあお前はどうやってterraform管理外のリソースを管理してるのか

Terraformの話とズレますが、私がやっているプロジェクトではCloud TasksのQueueやCloud Scheculerをyamlで管理していて、CD時にrakeタスクやpythonのfabなどを利用してGoogle Cloud側との差分を見てデプロイしています。(会社によって利用している言語が違うのでそれに合わせて軽めのtaskを作成する感じ)

昔のAppEngineのqueue.yamlcron.yamlの様な感じですね

例: queue.yaml

queue.yaml
default: &default
  rateLimits:
    maxConcurrentDispatches: 10
  retryConfig:
    maxAttempts: 8
    minBackoff: 5s
default_queues: &default_queues
  default:
    name: default
    <<: *default
  scheduled:
    <<: *default
    name: scheduled

例: scheduler.yaml

scheduler.yaml
health_check:
  cron: "0 * * * *"
  class: "HealthCheckJob"
  queue: scheduled

またCloud Runに設定する環境変数も以下のようなyamlを作成してデプロイ時に展開&Cloud Runへ設定するようにしています。(yamlからコマンドライン引数に変換するパーサを書いてる 値がsm://で始まっていたらそれをsecret keyにsecret managerから環境変数へ設定するようなオプション設定をする)

例:env.yaml

env.yaml
env_variables:
  APP_ENV: production
  RAILS_LOG_TO_STDOUT: 1
  HOGE_API_KEY: sm://hoge_api_key   

env_variablesとなっているのはもともとApp Engineで使っていたyamlをできる限りそのまま使えるようにしている名残ですね

なおSecret Managerへのデータ追加は開発者にやってもらっています。このあたりは組織ポリシーとの兼ね合いもあるので、要検討です。

既存プロジェクトのTerraform化

既存プロジェクトのTerraform化についてはベストプラクティス中でも「既存のリソースのインポートを回避する」と書いてあり、それやるなら作り直せと書いてあります。

https://cloud.google.com/docs/terraform/best-practices-for-terraform?hl=ja#importing

個人的には「作り直せるならやっとるわ」と心の底から思いますが、無理してTerraform化する必要はないと思います。

なお私は「小さなところからコツコツとTerraform化」「新しくやらなきゃいけないところはTerraform化」というのをやっていましたが、このパターンはストレスが溜まって最終的には全部movedブロックとimportブロックによる大規模リファクタリングをやる羽目になるので、それが未来に待っているのだなというお気持ちを強く持って実施すると良いと思います。

上に書いたような「IAMのみ」のように特定範囲に絞ったTerraform化については上記のようなことが発生しないため、ストレスは少ないと思います。

その他最初の段階で検討するべきこと

Google Cloudプロジェクトの構成

案件規模にもよりますが、以下のような構成にすることが多いです。

(組織)
 - (Project用のFolder=大体Project名)
  - (Project名)-admin (とか-root) // ルート(管理用)Project
  - (Project名)-prod // 本番用Project
  - stagings  //staging ProjectをいれるFolder=staging環境が少なそうなら作らない場合もある 色々実験用のsandbox Projectもここにいれる
    - (Project名)-stg-000-(ランダムサフィックス)
    - (Project名)-stg-001-(ランダムサフィックス)
    - (Project名)-stg-002-(ランダムサフィックス)
    - (Project名)-sandbox-000-(ランダムサフィックス)

例:

hoge.org
 - fugafuga
   - fugafuga-prod
   - fugafuga-admin
   - stagings
     - fugafuga-stg-000-aaabbbccc
     - fugafuga-stg-001-cccaaabbb
     - fugafuga-stg-002-bbbcccaaa
     - fugafuga-sandbox-000-aaabbbcc

ルート Project

上ではルートProjectと書いていますが、複数のProjectにまたがって何かしたい時に利用するProjectです。例えば以下の目的で使います。

  • Terraform用のService Account(後述)やGithub Actions用のService Accontの作成
  • Github Actions用の Workload Identity Federation の定義
    • Projectが利用するAWSなどに対する外部認証用Workload Identity Federationの定義は各Projectで行う
  • Docker ImageなどのArtifact Registoryの作成
    • 各stagingに作っても良いが、無駄が多い

その他Projectで共通して何かしたい時にはあると便利です。

staging用FolderとProject

staging用Folderを作成するかどうかや、Projectを複数作るかは案件によって決まります。
PR毎にCloud RunやApp Engineなどでリビジョン/バージョンを分割でき、DBなどに対して複数スキーマを持てる、またはDatastoreでスキーマレスに開発が可能で、複数の開発プロジェクトを並行で1Project上で開発可能な状況であれば1つのstaging Projectで管理が可能だと思います。
様々な環境要因でそういうことが不可能な場合は開発環境が複数あったほうが開発効率が良いです。

staging用Folderについてはstaging Projectに対して一括でIAM Roleを設定したい場合などに利用します。 すべてのProjectが、共通Module化するなどTerraformで同一コードで管理されているので、あれば作らなくても問題は発生しないと思います。

またstaging Project IDにはランダムサフィックスを付けておいたほうが良いです。(productionは任意) これはProject IDが他と被らないようにするためというのもありますが、開発状況によりstagingの増減をするシーンが多々有り、作り直ししたときに同じProject IDのProjectが作れないということを避けるためです。
任意の名前が設定可能なProject名はランダムサフィックスを外したものを設定します。
ただこの運用をしていると、gcloudコマンドを実行する際に、「Project ID何だったっけ?」が発生しやすい為、以下の様にProject名->Project ID変換をするスクリプトを実行することが多くなります。

PROJECT_ID=$(gcloud projects list --filter="NAME:$PROJECT_NAME" --format="value(project_id)" --limit=1)

sandbox Project

Terraformの運用をしていると、何となくtfファイルを書いて、何となくどうなるか試したいということがよく発生します。実際にこの設定で導通できるのか?やこの設定で思った機能が実現できるかなどtfファイルを書いて、terraform applyしてみないとわからない事が多々あります。

このため、プロジェクトの予算とも相談ですが、PoCをしたり色々試すことができるProjectを1つ用意しておくと良いです。

Terraformのフォルダ構成

Terraformのフォルダ構成は上記のベストプラクティスに倣い以下の様にしています。

(プロジェクトルート)/
  infra/
    .env
    Dockerfile
    compose.yaml
    Makefile
    terraform
      resources/
        environments/
          admin/
            modules/
              <module-name>/
                main.tf
                variables.tf
                outputs.tf
                ...other…                
            backend.tf
            main.tf
            provider.tf
            variables.tf
          staging-000/
            backend.tf
            main.tf
            provider.tf
            variables.tf
          prod/
            backend.tf
            main.tf
            provider.tf
            variables.tf
        modules/
          <プロジェクト名>/
            main.tf
            variables.tf
            outputs.tf
            ...other…
          <module-name>/
            main.tf
            variables.tf
            outputs.tf
            ...other…

上記では infra ディレクトリ以下にTerraform関連のコードを置いていますが、
monorepoにするか、terraform関連のコードを別リポジトリに分けるかはプロジェクトの方針にもよると思います。個人的にはアプリケーションコードとインフラ系のコードは一緒のほうが楽です。(リポジトリいっぱいあるのが面倒)

infra ディレクトリ直下には terraform を動かすための Dockerfile compose.yaml(docker compose)、コンテナ内の環境変数設定用に .env ファイルを置いています。
environments 以下がTerraformプロジェクトルート、modules以下が共通で利用するTerraform Moduleの置き場です。 environments以下にも modulesがあるのは特定のProjectでしか利用しないModuleを置くためです。

modules以下にはプロジェクト名と同じ名前のModuleをおいています。
これはenvironmentsから呼ばれるエントリポイント的なModuleにしていて、
ここから個別のModuleを呼び出しています。
同じstagingを複数作る場合は、この方法が比較楽かつ共通化できるのでお勧めです。

environments 共通で変数を設定する場合は、Terraformでは TF_VAR_ のプレフィックスを付けた環境変数を設定すると、その変数名に環境変数の値が設定されるため、.env に値を設定します。

Terraformのバージョンを揃えるなどの意味もあり、コンテナを利用しておいたほうがいいですが、
私の場合は、compose.yaml を利用することが多いです。お好みで Devcontainer を利用してもいいと思います。

またいちいちコンテナ内に入って作業するのが面倒 & いろいろなコマンド楽に扱いたいというのもありMakefileに以下のようなタスクを書いています。(Makefileをタスクランナーとして使うの微妙じゃねというのは認めます)

Makefile
DOCKER_COMPOSE = docker compose
DOCKER_COMPOSE_EXEC_BASH = $(DOCKER_COMPOSE) exec infrastructure bash
ENV = staging-000
TF_RESOURCES_DIR = terraform/resources
TF_MODULES_DIR = $(TF_RESOURCES_DIR)/modules
TF_ENVIRONMENTS_DIR = $(TF_RESOURCES_DIR)/environments
TFDIR = $(TF_ENVIRONMENTS_DIR)/$(ENV)

tf-fmt:
	$(DOCKER_COMPOSE_EXEC_BASH) -c "terraform fmt -recursive $(ARG)"

tf-plan:
	$(DOCKER_COMPOSE_EXEC_BASH) -c "cd $(TFDIR) && terraform plan --parallelism=30 $(ARG)"

tf-apply:
	$(DOCKER_COMPOSE_EXEC_BASH) -c "cd $(TFDIR) && terraform apply --parallelism=30 $(ARG)"

以下terraformコマンドたくさん

上記から以下の様に実行することで、 environments以下に移動せずTerraformコマンドを実行します。

make tf-plan ENV=prod

このあたりは TaskJustなどの別のタスクランナーでもいいですが、何にせよ一枚間に挟んでおくと日々のストレスが軽減します。

Terraform Backend tf stateファイルの置き場

Terraformではtf stateファイルの置き場(Terraform Backend)として、 Cloud Storageが選択できるため、Google Cloudで利用する場合はCloud Storageにしておくのがいいと思います。私は上記Project構成の様にルートProjectを作成するためその中に (Project名)-terraform-stateの様な Cloud Storage Bucket を作成し、その中にtf stateファイルを配置します。

Cloud StorageをTerraform Backendにする為には以下のようなbackendブロックを設定する必要があります。
上記の様に複数のプロジェクトを同一バケットで扱う場合は、prefix を指定して Project 毎にディレクトリを指定します。
下記の場合は gs://example-terraform-state/example-staging-000/ 以下に default.tfstate というtf stateファイルが作成されるようになります。

例: backend.tf

backend.tf

terraform {
  backend "gcs" {
    bucket                      = "example-terraform-state"
    impersonate_service_account = "terraform@example-admin.iam.gserviceaccount.com"
    prefix                      = "example-staging-000"
  }
}

なお使用するバケットは オブジェクトバージョニング を有効にすることをお勧めします。純粋にバージョン管理的な意味合いもありますが、様々な事情で、terraform state rmコマンドなどで、stateファイルを操作したいことがあります。その際に操作ミスなので下手にstateの状態が正しくない状態になってしまった時にオブジェクトバージョニングを有効にしておけば、操作前の状態にstateファイルを戻すことができます。

補足となりますが、Infrastructure Managerを利用する場合は、tf stateファイルはGoogle Cloudの管理下となり基本的に参照できない場所に置かれます。(import/exportはできる)

認証まわり

Terraformをやりはじめの頃は自身のGoogleアカウントを利用して、terrformの実行していればよいですが、個人個人のGoogleアカウントを利用していると、各種権限不足などが発生しがちです。
権限周りの事を考えると、Terraform実行用のサービスアカウント作成し、Terraformの実行はそのサービスアカウント経由で実行することが望ましいです。

サービスアカウントを扱うと、ローカルからこのサービスアカウント経由で認証するためにサービスアカウントキーのJSONファイルを利用する方が多いですが、セキュリティ観点で、サービスアカウントキーファイルを利用することは非推奨です。
最近(2024年5月あたり)作成したGoogleCloudの組織では組織ポリシーによりこのサービスアカウントキーの作成がデフォルトではできなくなっています。[6]

このためTerraformでサービスアカウントを利用する場合も、サービスアカウントキーを利用するのではなく「サービスアカウントの権限借用」を利用してください。この機能はざっくり言えば特定のサービスアカウントに成り代わって、APIなどを実行できる機能です。

Terraformでこのサービスアカウントの権限借用を使う場合は以下の記事が参考になります。

https://cloud.google.com/blog/ja/topics/developers-practitioners/using-google-cloud-service-account-impersonation-your-terraform-code

他にも 「Terraform サービスアカウント 権限借用」などで検索すれば結構記事が出てくるのでそちらを参照してください。

小話: Github Actionsとサービスアカウントの権限借用

Terraformのサービスアカウントの権限借用は結構記事をよく見るのですが、「Github ActionsでTerraformのサービスアカウントの権限借用」は意外と探しても見当たりません。

Github ActionsでGoogle Cloudの認証を行う場合は google-github-actions/auth Actionsを利用します。
ここで認証したサービスアカウントから任意のサービスアカウントへ権限借用する場合は、google-github-actions/authdelegates に権限借用を行いたいサービスアカントを設定する必要があります。この設定がないと権限借用ができないというエラーになります(エラーメッセージは忘れました)

      - uses: google-github-actions/auth@v2
        name: 'Authenticate to Google Cloud for root project'
        with:
          workload_identity_provider: 'projects/${{ env.ROOT_PROJECT_ID }}/locations/global/workloadIdentityPools/github-actions-pool/providers/github-actions-provider'
          service_account: '${{ env.GHA_SERVICE_ACCOUNT_EMAIL }}'
          project_id: ${{ env.TARGET_PROJECT_ID }}
          delegates: '${{ env.TF_SERVICE_ACCOUNT_EMAIL }}'

この設定はTerraformでサービスアカウントの権限借用利用していて、CI/CDをしようと思うと必須になるのですが、現在(2024/12)時点では、「google-github-actions/auth delegates」と検索しても、公式ドキュメントのオプション引数の説明としてしか登場しておらず、使用例が見つからなかったため、ハマりやすいポイントです。

IAMの管理

「Terraform Google Cloud IAM」 などで検索すると、たくさんの被害者が出てきますが、注意が必要です。

まずTerraformのGoogle Cloud Providerでは IAM を設定するための Resource が多くあります。 これは IAM 自体が「Project」「Folder」や「Cloud Run」などどの紐づけるResourceが多くあるからで、それぞれごとに「google_project_iam_*」、「google_folder_iam_*」「google_cloud_run_v2_service_iam_」などが存在します。
またそれぞれごとに

  • iam_policy
  • iam_binding
  • iam_member

という三種類のResourceがあります。

例えばプロジェクトにIAMポリシーを設定する「google_project_iam_*」では

  • google_project_iam_policy
  • google_project_iam_binding
  • google_project_iam_member

の三種類が存在します。

それぞれ以下の様な利用用途です。

  • iam_policy: 対象リソース全体のIAMを一括で管理できる。定義設定されていない IAMポリシーは削除される。Projectであれば、記載されていないIAMポリシーは全て削除される。
  • iam_binding: 対象リソースの特定ロールに紐づくIAMを管理できる。定義設定されていない IAMポリシーは削除される。Projectであれば、例えば編集権限(roles/editor)のiam_bindingを定義した場合、そこに記載していないアカウントの編集権限は削除される。
  • iam_member: 対象リソースの特定ロールに紐づくIAMを1対1で管理できる。定義設定されていない設定は無視される。

つまり、iam_policyiam_bindingについては、Terraform上で管理してないIAMポリシーは、全て削除されるということです。(ドキュメント上では「Authoritative」と説明されています。」
それに対してiam_memberはTerraformで管理しているIAMポリシーのみ(つまりtfファイルに書いたリソースのみ)管理を行い、他は制御しません。(ドキュメント上では「non-Authoritative」や「Additive」と書かれています。)

このあたりを把握せずに、サンプルコードなどに記載されているiam_bindingをそのまま利用してしまって、ロールが吹き飛んだという事故をよく見ます。私もやりました。

なので慣れないうちは、少し面倒でも安全な「iam_member」や「IAM Module」の additiveモード を利用することをお勧めします。

なおこの件は上で紹介したベストプラクティスにも IAM の管理方法について、この件については注意が書かれています。
https://cloud.google.com/docs/terraform/best-practices-for-terraform?hl=ja#iam

またResourceの説明中にも細かく説明されています。

https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_project_iam

ただ、、、私の英語力では「Authoritative(普通に翻訳すると権威/権限/正式とか)」という単語で、この脅威を感知することは厳しかったです。私も一部のオーナー権限を吹き飛ばしました。。。。

サンプルコード等でも影響範囲が小さめな Cloud Run や Bigquery の IAM 設定では、iam_policyiam_binding を利用しているケースが良くあり、安易のそのコードをコピペ & Projectリソースに書き換えてしまうことがあるようです。(レビューで指摘したことがあります。)

本番環境で急に権限エラーが発生するなどの問題が発生するので本当に注意してください。

Terraformで運用してみて注意点や検討事項

最初からやっておいたほうがいいこと

terraform fmt

Terraformのコードは、可読性と一貫性を保つために、公式ドキュメントで推奨されているスタイルガイドに従ってフォーマットする必要があります。
Terraformではterraform fmtという公式ドキュメントで推奨されているスタイルガイドに従ってコードフォーマットを行うコマンドが用意されています。

コードフォーマットをやることは当たり前なのですが、私は最初の頃めんどくさがってこのコマンドを実行していませんでした。
というのも 私は普段 JetBrains 系(GolandとかPyCharm、RubyMine他)のIDEを利用しており、そのTerraform Pluginを利用していて、IDEのコードフォーマットを実施していたからです。

が後々気がついたのですが、terraform fmtとIDEのコードフォーマットの結果は違います。
もっと言えば、IDEのコードフォーマットは時々tfファイル(HCL)が壊れます。

「コードフォーマットなんて実行結果に関係無いし、あとからばっとやればいいでしょ」と思うでしょうが、terraform fmtはちょっと違います。
terraform fmtはResource名を規約に従った形に修正するからです。(完全にではないです。)

具体的には

resource google_project hoge-staging {...}

というリソース名を

resource "google_project" "hoge_staging" {...}

というリソース名に変更します。

ではこの変更の状態でTerraformを実行(terraform apply)していた場合、terraform fmt後の実行はどうなるでしょうか?

答えは「既存のProjectが一度削除されて、新しいProjectが作成される」です。
これはそのままではTerraformがResource名を変更したのか、とあるResourceを削除して、新しいResourceを追加したのかが判断できないためです。
この場合削除せずResource名を変更するためにはterraform state mvコマンドを実行するか、movedブロックを追記してTerraformへResourceの移動があったことを伝える必要があります。

この様に無駄な作業が増えたり、不要なリソース削除が発生しないようにするためにも、「plan/apply前にfmt」は常にクセを付けておいたほうが良いです。

Lint、ValidateとCI

こちらも当たり前といえば当たり前ですが、linterやterraform validateの定期的な実行(CI)もできる限り最初ころから実施したほうが良いです。

「github actions terraform CI」などと検索すればたくさん事例は出てきます。
Linterについても「awesome terraform」などで検索すればまとめている人がたくさん出てきます。
※私はtflintだけ使ってます。

このあたりは、良きterraformの習慣を学習する意味でも使えますし、CI中で terraform plan を実行することで、不要なリソース削除を監視できる目的もあります。

是非実施してください。

ちょっとやる前に検討したいこと

CD 継続的デリバリ

個人的にCIはやったほうがいいですが、CDはちょっと課題があると思っています。
CDを実施する場合 「どのタイミングでデプロイ(Terraformの場合はterraform apply)するのか」というのが大事です。
通常アプリケーションのデプロイですと、

  • PRの更新のタイミングでdevelopment環境へ
    • 私の場合は、featureブランチへのcommit & pushだったり、PR内へ/deployみたいなコメントを書いた時にデプロイできるようにしています。
  • PRがマージされたタイミングでstaging環境へ
  • リリース用PRがマージされたらproduction環境へ

など特定イベントごとに環境を変えてデプロイを行うと思います。
アプリケーションのデプロイはビルドはおいておいても、デプロにはそこまで長い時間がかかることはなく(1-2分とか?)、競合が起きることは少ない(または単純な後勝ちになることが多い)です。

ただTerraformの場合はterraform planコマンドやterraform applyはtf stateファイルをロックするため、同時実行ができません。また更新の種類によっては5-10分かかる場合もあり、長い時間ロックが続くケースもあります。

このため上記「PRがマージされたタイミングでstaging環境へデプロイ」の様な、タイミングが重なりやすいCDを行うと、ロックによるエラーが頻発します。
これがTerraformに慣れている方が多いといいのですが、TerraformやCD自体に慣れが少ない開発チームですと「なんかGithub Actionsが落ちた」「なんかTerraformが落ちた」というエスカレーションがよく上がるようになります。
また、どうしてもアプリケーションのデプロイとのタイミングを見たいケースもあるため、完全に自動でデプロイ(terraform apply)でいいのかと言うのが検討ポイントとして残ります。

CDはやってもいいけど、アプリケーションのデプロイとはずらせるように(マージ=デプロイとはしないとか)や、開発者の十分な理解が必要になることを覚えておく必要があります。

また、インフラ作業は緊急性が求められる作業(早急にメンテナンス画面を出す必要があるため、レビューやCIをすっ飛ばしたい)や、早くやらないと人を待たせる作業(開発環境にとりあえずPoCでパーミッション追加してほしい)があるので、大きな企業で組織ポリシー上問題がないのであれば、手元でterraform applyできる状態は残しておくほうが良いです。現実的にレビューは後からは本当によくあります。

誰もterraform applyできないという状況を避ける為など、CDなどをできるようにしておくことは大事ですが、アプリケーションのデプロイに比べてシンプルじゃないことが多い為悩みどころは多いことだけ覚えておいたください。

Test

愚痴です。

Terraformのテストについては、ベストプラクティス中でも書かれています。

https://cloud.google.com/docs/terraform/best-practices-for-terraform?hl=ja#test

外に公開するようなModulueを書く場合はTerratest各種テストフレームワークがありますし、
状態のテストについてはTerraform自体がTestの仕組みを用意しています。

https://developer.hashicorp.com/terraform/language/tests

ただいつも心の底から思いますが、ぶっちゃけ実行してみて、構成作ってみないとわからん。ってことが多々あります。ネットワークがつながるかとか、ちゃんとバージョンアップできるかとか、パーミッション足りてるかとか。 こればかりは実際terraform applyしてかつ、アプリケーションが動かないとわかないのです。
その為にはどうしても 雑にterraform applyできる環境が必要です。予算的にはいらない子になりがちですが自由にterraform applyできるsandbox的なProjectは一つ用意しておくと結構幸せです。

また、継続的にterraform applyしているからと言って、それをコピペしたTeraformのコードが別Projectを簡単にterraform apply一発でできるとは限りません。
この一からProjectを立ち上げるテストも時々避難訓練的に実施しておくことをお勧めします。
いきなり人が増えて、開発用プロジェクトがいくつか必要ということがちょこちょこ発生するかもしれません。そのときにつらい思いをしないように一から立ち上げテストは実施しておきましょう。

Infrastructure Manager

Infrastructure Manager(Infra Manager)は最近提供されたサービスで、Terraformの実行をGoogle Cloud上で管理できるサービスです。

  • stateファイルを管理する必要がなくなる
  • 実行環境をGoogle Cloud側に寄せられる
  • 実行履歴管理ができる

などのメリットがあると思います。

ただ個人的には Terraform(のコマンド) の扱いが割と隠蔽されてしまうため、初学者が使うべきサービスかどうかは少し懸念があります。

まだリリースされてから期間が短いため、事例などを見ながら様子見していきたいなと思います。

まとめ

最初のまとめにも書きましたが、まとめは

  • Terraform化する範囲は好きでいい 既存プロジェクトは無理する必要はない ただ財産にはなる
  • ベストプラクティスをちゃんと読もう
  • CIはやっておいた方が良い CDは要検討
  • どこまでやるかは「開発者がさわらないところまで」がおすすめ
  • sandboxプロジェクトはあるといいよ

です。

ほぼベストプラクティスに書いてあることの書き直しな感じになったかもしれません。

一番上の「ただ財産にはなる」については触れてきませんでしたが、私がTerraformを扱っていて一番感じている部分かもしれません。
私の経験上、Terraformを利用する上で最も大きなメリットは、インフラ構成の再現性と共有性を高められる点にあると考えています。

  • Cloud RunのDirect VPCってどうやってやったっけ?
  • Github Actions用のWorkflow Identityの設定って何が必要だっけ?
  • Cloud NATってネットワーク周りどう作ったっけ?

などなどが、Projectを立ち上げるたびに忘れていて思い出すのに時間がかかっていました。
Cloud SDKのshellを残しておくのも一つですが、こういった設定を自分個人用TerraformのModule化をしておくと割とどの仕事でも使えるようになります。

インフラ作業はアプリケーションに比べてビジネスロジックではなく似た様なアーキテクチャが多いため、コード化すると似る部分が多くなります。
ぜひIaCを進めて楽ができる未来を勝ち取ってください。

脚注
  1. 名前が似ているのはたまたまで、Kabuk Styleさんのお仕事を受けることになってからお名前を聞いて驚きました ↩︎

  2. TerraformのStateファイルとはTerraformが管理するインフラの状態を管理するファイルです。 公式ドキュメントより ↩︎

  3. TerraformのResourceとはTerraformにおける最も重要な要素の一つで、インフラ要素の1つまたは、複数を表します。 公式ドキュメントより。Google Cloudでいうと1つのIAM設定や、Project、Cloud Runサービス、VPC、Service Accountsなどを表します。 ↩︎

  4. TerraformのModuleは複数のResourceをまとめたコンテナ(一つにまとめたもの)です。公式ドキュメントより Google CloudではLBなどを設定する場合ResourceのみではURL Map、Backend、SSL、IAMなど複数のResourceを定義する必要がありますが、LB-httpモジュールを利用すると1つのモジュールで設定できます。 ↩︎

  5. Config Connector自体はk8sのOperatorで、k8sを利用してGoogle Cloudのリソースを管理するためのものです。その中にある、設定ファイルの出力機能を利用してTerraform(HCL)形式のファイルを出力しているっぽいです。 ↩︎

  6. https://cloud.google.com/resource-manager/docs/organization-policy/restricting-service-accounts?hl=ja#disable_service_account_key_creation ↩︎

Discussion