🏗️

GoogleCloudでTerraformに入門する - CLIセットアップから基本的な使い方まで

に公開

本記事のサマリ

この記事では、TerraformをGoogle Cloudで使い始めるために必要な基礎知識から実際の環境構築まで、ひと通りカバーしています。CLIのインストール方法から実際のリソース作成まで、手を動かしながら学べる内容になっているので、まずは小さく始めてみたい方におすすめです。

はじめに

私自身、AWSではCDKを使ってIaCを実践していたので、コードでインフラを管理する良さは知っていました。ただ、Google Cloudは初めてで、Terraformも触ったことがなかったんです。

Google Cloudのコンソール画面も直感的で使いやすいんですが、やっぱりコードで管理した方が人的ミスがなくていいですよね。設定の履歴も残るし、レビューもできるし。

そんなわけでTerraformに挑戦してみることにしたんですが、公式ドキュメントを開いた瞬間に「うっ」となりました。英語だし、CDKとは書き方が全然違うし、どこから読めばいいのかわからない。でも、一つずつドキュメントを読んで試していくうちに少しずつ分かるようになってきました。

この記事では、私が実際にTerraformのドキュメントを読みながら理解していった順序で、Google Cloudでの使い方を説明していきます。つまずいたところや「ここ分かりにくいな」と思った部分も含めて、できるだけ正直に書いていこうと思います。

今回コード部分は少ないですが、下記のリポジトリにあります。(gistでも良かったかも..)

https://github.com/toto-inu/lab-202511-terraform

まずは公式サイトを開いてみた

最初に訪れたのは、もちろんTerraformの公式サイトです。

https://www.terraform.io/

「Write infrastructure as code using declarative configuration files」と書かれています。ふむふむ、declarative(宣言的)というのがキーワードらしい。

ドキュメントを読み進めていくと、Terraformは「何を作りたいか」を書けば、「どうやって作るか」は自動でやってくれるツールだと分かってきました。例えば、「GCSバケットをこういう設定で作りたい」とコードに書けば、必要なAPI呼び出しを全部Terraformが組み立てて実行してくれるということです。

これって、シェルスクリプトとかで「まずgcloudコマンドでバケット作って、次に権限設定して…」と手順を書いていくのとは全然違うアプローチですね。

状態管理という考え方

ドキュメントを読んでいて、もう一つ重要だと感じたのが「state(状態)」という概念です。Terraformは、今のインフラがどういう状態なのかを記録しておいて、設定ファイルと実際の環境を比較して差分を出してくれるらしい。

正直、最初は「状態ファイル?何それ?」という感じでしたが、これがあることで「既に作ったリソースは触らずに、新しいリソースだけ追加する」みたいな操作が安全にできるんだと理解しました。この辺は実際に動かしてみないとピンとこないですよね。

HCLという言語について調べる

さて、Terraformは独自の設定言語を使うらしい、ということがドキュメントから分かりました。HCL(HashiCorp Configuration Language)というものです。

最初に見たときは「また新しい言語覚えないといけないのか…」とため息が出ましたが、サンプルコードを見てみると、思ったより読みやすい。JSONほどカッチリしてないし、YAMLほど自由すぎない、ちょうどいい感じ。

プロバイダーって何?

ドキュメントを読んでいくと、まず「provider(プロバイダー)」というものを設定する必要があるとわかります。これは要するに「どのクラウドサービスを使うか」の宣言ですね。

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.0"
    }
  }
}

provider "google" {
  project = "my-project-id"
  region  = "us-central1"
}

バージョンを ~> 4.0と書くことで「4.x系の最新版を使う」という意味になるらしい。
なるほど、package.json的なノリかな。

resourceブロックでリソースを定義

そして本題、実際のクラウドリソースは resourceブロックで書くようです。

resource "google_storage_bucket" "example" {
  name     = "my-terraform-bucket-12345"
  location = "US"
  
  uniform_bucket_level_access = true
  
  versioning {
    enabled = true
  }
}

最初ちょっと混乱したんですが、resourceの後の "google_storage_bucket"がリソースタイプで、その次の "example"がTerraform内での識別名なんですね。つまり、この設定ファイルの中で「exampleという名前のGCSバケット」として参照できる、ということです。

variableで値を外出し

プロジェクトIDとかバケット名をハードコードするのは嫌だなと思っていたら、ちゃんと変数の仕組みがありました。

variable "project_id" {
  description = "Google Cloud Project ID"
  type        = string
}

variable "bucket_name" {
  description = "Name of the GCS bucket"
  type        = string
  default     = "my-default-bucket"
}

使うときは var.project_idという形で参照できます。default値も設定できるのが便利ですね。

outputで結果を取り出す

作成したリソースの情報を後で見たいときは、outputを使うらしい。

output "bucket_url" {
  description = "URL of the created bucket"
  value       = google_storage_bucket.example.url
}

google_storage_bucket.example.urlという書き方で、さっき定義したリソースの属性にアクセスできるんですね。この辺は実際に動かしてみないと実感湧かないですが。

とりあえずインストールしてみる

概念は何となく分かったので、実際に手を動かしてみようと思います。まずはTerraform CLIのインストールから。

公式のインストールガイドを見ると、OSごとに方法が書いてあります。

https://developer.hashicorp.com/terraform/install

私の環境はmacOSなので、Homebrewでサクッと入れられそうです。

brew tap hashicorp/tap
brew install hashicorp/tap/terraform

Linuxの場合は公式リポジトリを追加する方法が推奨されていました。

# Ubuntu/Debianの場合
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform

Windowsの人はChocolateyが使えます。

choco install terraform

インストールできたら、ちゃんと動くか確認してみます。

terraform version

バージョン番号が表示されれば成功です!意外とすんなりインストールできて安心しました。

Google Cloudの認証で少しハマる

さて、Terraformは入ったけど、Google Cloudに接続するための認証はどうするんだろう?ドキュメントを探していくと、Google Cloud CLIを使うのが一般的みたいです。

https://cloud.google.com/sdk/docs/install

私はすでにgcloudコマンドは入れていたので、認証だけやり直します。

gcloud auth login
gcloud config set project YOUR_PROJECT_ID

ここまでは良かったんですが、ドキュメントをさらに読んでいくと「Application Default Credentials」というものが必要だと書いてあって。「え、さっきログインしたのとは違うの?」と混乱しました。

調べてみると、gcloud auth loginは人間がgcloudコマンドを使うための認証で、TerraformみたいなツールがGoogle Cloud APIを呼ぶには別の認証情報が必要らしい。なるほど、そういうことか。

gcloud auth application-default login

このコマンドを実行すると、ブラウザが開いて認証が求められて、完了すると ~/.config/gcloud/application_default_credentials.jsonというファイルができます。これでTerraformがGoogle Cloud APIを呼べるようになるわけですね。

APIの有効化も必要だった

もう一つ忘れがちなのが、使いたいAPIの有効化です。Google Cloudのプロジェクトでは、デフォルトで全部のAPIが有効になっているわけではないんですよね。

gcloud services enable compute.googleapis.com
gcloud services enable storage-api.googleapis.com

この辺は、実際に使いたいサービスに応じて追加していく感じです。最初は「あれ、エラーが出る」と思ったら、API有効化忘れてた、みたいなことがよくあります。

いよいよ実際にリソースを作ってみる

準備が整ったので、実際にGoogle Cloudのリソースを作ってみます。最初は簡単なところから、ということでGCSバケットにしました。

まず作業ディレクトリを作ります。

mkdir terraform-gcp-demo
cd terraform-gcp-demo

main.tfを書いてみる

Terraformの設定ファイルは .tfという拡張子で、慣例的に main.tfというファイル名にすることが多いようです。エディタで開いて、ドキュメントを見ながら書いていきます。

Google Cloud Providerのドキュメントを見ると、GCSバケットの作成例が載っています。

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

terraform {
  required_providers {
    google = {
      source  = "hashicorp/google"
      version = "~> 4.0"
    }
  }
}

provider "google" {
  project = var.project_id
  region  = var.region
}

variable "project_id" {
  description = "Google Cloud Project ID"
  type        = string
}

variable "region" {
  description = "Google Cloud region"
  type        = string
  default     = "us-central1"
}

variable "bucket_name" {
  description = "Name of the GCS bucket"
  type        = string
}

resource "google_storage_bucket" "demo_bucket" {
  name     = var.bucket_name
  location = "US"
  
  uniform_bucket_level_access = true
  
  versioning {
    enabled = true
  }
  
  lifecycle_rule {
    condition {
      age = 30
    }
    action {
      type = "Delete"
    }
  }
}

output "bucket_name" {
  description = "Name of the created bucket"
  value       = google_storage_bucket.demo_bucket.name
}

output "bucket_url" {
  description = "URL of the created bucket"
  value       = google_storage_bucket.demo_bucket.url
}

terraform.tfvarsで変数に値を入れる

変数は定義したけど、実際の値はどこに書くの?と思って調べたら、terraform.tfvarsというファイルに書くのが一般的みたいです。

project_id  = "lab-202511-cloudrun-terraform"
bucket_name = "lab-202511-terraform-bucket-20251115"

ここで一つ注意!GCSバケット名は全世界で一意である必要があるので、適当に日付とかプロジェクト名を入れて、重複しない名前にする必要があります。今回は日付サフィックス(20251115)を付けて、確実にユニークな名前にしています。

.gitignoreも作っておく

記事には書いていませんでしたが、GitHubにpushする前に .gitignoreを作っておくのを忘れずに。特に terraform.tfstate*.tfvarsは機密情報が含まれる可能性があるので、絶対にコミットしないようにします。

# Local .terraform directories
**/.terraform/*

# .tfstate files
*.tfstate
*.tfstate.*

# .tfvars files (may contain sensitive data)
*.tfvars
*.tfvars.json

# .terraform.lock.hcl (optional, depends on your team's policy)
.terraform.lock.hcl

Terraformのワークフローを一つずつ試す

ファイルが揃ったので、いよいよ実行です。ドキュメントを見ると、Terraformには決まった実行の流れがあるようです。

まずはterraform init

最初に実行するのが terraform initコマンド。これで必要なプラグインをダウンロードしてくれるらしい。

terraform init

実行してみると、何やらダウンロードが始まって、.terraformというディレクトリができました。中を覗くとGoogle Cloudプロバイダーのバイナリが入っています。なるほど、このプラグインを使ってGoogle Cloud APIを呼んでいるわけですね。

実際の出力はこんな感じでした:

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/google versions matching "~> 4.0"...
- Installing hashicorp/google v4.85.0...
- Installed hashicorp/google v4.85.0

Terraform has been successfully initialized!

required_providers~> 4.0と指定していたので、4.x系の最新版であるv4.85.0がインストールされました。

terraform planで確認

次は terraform plan。これは「実際には何もしないけど、実行したらどうなるか見せてくれる」コマンドです。

terraform plan

出力を見ると、緑色で + google_storage_bucket.demo_bucketという表示が出ました。+マークは「新規作成される」という意味らしい。その下に、作成されるバケットの設定内容が全部書いてあります。

Terraform will perform the following actions:

  # google_storage_bucket.demo_bucket will be created
  + resource "google_storage_bucket" "demo_bucket" {
      + name                          = "lab-202511-terraform-bucket-20251115"
      + location                      = "US"
      + uniform_bucket_level_access   = true
    
      + versioning {
          + enabled = true
        }
    
      + lifecycle_rule {
          + action {
              + type = "Delete"
            }
          + condition {
              + age = 30
            }
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

正直、最初はこの出力が読みにくくて「何がどうなるの?」と混乱しましたが、よく見ると「このリソースが、こういう設定で、新規作成されますよ」ということが書いてあるだけでした。慣れれば便利かも。

最後に Plan: 1 to add, 0 to change, 0 to destroy.と出ていて、1つのリソースが追加される予定であることが一目でわかります。

いよいよterraform apply

確認が終わったら、実際に作成します。

terraform apply

実行すると、またplanと同じ内容が表示されて、最後に「本当に実行しますか?」と聞かれます。ここで yesと入力してエンターを押すと…おっ、動き始めました!

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

google_storage_bucket.demo_bucket: Creating...
google_storage_bucket.demo_bucket: Creation complete after 2s

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

bucket_name = "lab-202511-terraform-bucket-20251115"
bucket_url = "gs://lab-202511-terraform-bucket-20251115"

しばらく待つと、Apply complete!と出て、outputで指定した情報が表示されました。本当にバケットができたのか不安だったので、Google CloudコンソールのCloud Storageページを開いて確認してみると…ありました!ちゃんと作成されています👍

念のため、gcloudコマンドでも確認してみました。

gcloud storage buckets describe gs://lab-202511-terraform-bucket-20251115

バージョニングが有効になっていること、ライフサイクルルールが30日に設定されていること、uniform_bucket_level_accessがtrueになっていることも確認できました。設定ファイル通りに作られていますね!

初めて自分のコードでクラウドリソースが作れた瞬間、少し感動しました。

terraform destroyで削除

テストなので、作ったバケットは削除しておきます。ドキュメントによると terraform destroyというコマンドがあるらしい。

terraform destroy

これもまた確認メッセージが出るので yesで実行。すると、さっき作ったバケットがキレイに削除されました。コンソールで確認すると、確かに消えています。

このapply→destroyの流れ、何度でも再現できるのがIaCの良いところなんだなと実感しました。

terraform.tfstateファイルの存在に気づく

一通り動かした後、ディレクトリを見ると terraform.tfstateというファイルができていました。中を見てみると、JSONフォーマットで作成したリソースの情報が詳細に書かれています。

ドキュメントを読むと、これが「状態ファイル」というもので、Terraformが管理しているリソースの現在の状態を記録しているらしい。planやapplyのときに、この状態ファイルと設定ファイルを比較して差分を出しているわけですね。

状態ファイルは超重要

ここで注意点がありました。この状態ファイルを削除してしまうと、Terraformは「どのリソースを管理していたか」が分からなくなってしまうんです。つまり、実際にはバケットが存在しているのに、Terraformからは見えなくなってしまう。

ドキュメントには「チームで開発する場合は、状態ファイルをGCS等のリモートストレージで管理しましょう」と書いてありました。

terraform {
  backend "gcs" {
    bucket = "your-terraform-state-bucket"
    prefix = "terraform/state"
  }
}

こう設定すると、状態ファイルがGCSに保存されて、チームメンバー全員が同じ状態を共有できるようになるそうです。最初はローカルファイルで十分ですが、本格的に使うならこの設定は必須ですね。

振り返ってみて

ここまで、Terraformのドキュメントを読みながら実際に手を動かしてみた流れを書いてきました。正直、最初は「難しそう」「面倒くさそう」と思っていたんですが、一度動かしてみると案外シンプルでした。

特に良かったのは、コードで管理することで「何度でも同じ構成を再現できる」という安心感です。コンソールでポチポチやっていたときは、「さっき何やったっけ?」となることが多かったんですが、Terraformなら設定ファイルを見れば全部わかります。

つまずいたポイント

振り返ってみて、つまずいたのはこの辺でした:

  • Application Default Credentialsの設定を忘れがち: gcloud auth loginだけじゃダメで、gcloud auth application-default loginも必要でした。この違いを理解するのに少し時間がかかりました。
  • 状態ファイルの重要性を最初は理解していなかった: terraform.tfstateファイルが自動生成されますが、このファイルの重要性を最初は分かっていませんでした。実際にapplyを実行して、ファイルの中身を見て「ああ、ここに現在の状態が記録されているんだ」と理解しました。
  • Python環境の問題: これは環境固有の問題でしたが、gcloud CLIをインストールするときにPython 3.13のシンボリックリンクがないというエラーに遭遇しました。ln -sでリンクを作って解決しましたが、このあたりはOSやパッケージマネージャーのバージョンによって異なるかもしれません。

この辺は、ドキュメントに書いてあるんですが、実際にエラーに遭遇しないと頭に入ってこないですね。

実際にやってみて分かったこと

ドキュメントを読むだけでは分からなかったことが、実際に手を動かすことでいくつか理解できました:

  1. terraform planの有用性: 事前に「何が作られるか」を確認できるのは本当に便利です。Plan: 1 to add, 0 to change, 0 to destroy.という表示で、影響範囲が一目瞭然になります。
  2. outputの便利さ: terraform apply完了後に、バケット名とURLが自動表示されるのが思った以上に便利でした。コンソールで探す手間が省けます。
  3. ワークフローの明確さ: initvalidatefmtplanapplyという流れが決まっているので、迷わずに進められました。CDKと違って、ビルドのステップがないのもシンプルで良いですね。
  4. プロバイダーのバージョン管理: .terraform.lock.hclが自動生成されて、プロバイダーのバージョンが固定されます。これにより、チームメンバー全員が同じバージョンのプロバイダーを使えるようになっています。

次のステップ

今回はGCSバケットという比較的シンプルなリソースで試しましたが、次はCompute EngineのVMインスタンスとか、Cloud SQLとか、もう少し複雑なリソースに挑戦してみたいと思います。

あと、modulesという仕組みを使うと設定を再利用できるらしいので、そこも調べてみる予定です✨

公式ドキュメントは本当に充実していて、Google Cloud Providerのリソースについては全部網羅されています。

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

Google Cloud側の公式ドキュメントにもTerraformの情報があります。

https://cloud.google.com/docs/terraform

最初は英語のドキュメントを読むのが億劫でしたが、実際に手を動かしながら読むと、意外とすんなり理解できました。皆さんもぜひ、まずは小さく始めてみてください!

株式会社StellarCreate | Tech blog📚

Discussion