はじめに
クラウドインフラを構築するとき、コンソールをポチポチ操作していくのは再現性がなく、レビューもしづらく、後から「なぜこの設定にしたのか」が分からなくなりがちです。Terraform はインフラを宣言的なコードとして管理できる Infrastructure as Code(IaC)ツールで、AWS / Google Cloud / Azure をはじめとする数百のプロバイダに対応しています。
本記事では、Terraform を業務で使い始める前に知っておくと迷子にならない、基礎知識と実践的なTipsをまとめました。「なんとなく terraform apply は打てるけど、State って何?」というあたりの疑問を解消できる内容にしています。
対象読者
- クラウドの基本操作はできるが、IaC ツールはこれから触る方
- Terraform を雰囲気で使っているが、概念を整理したい方
- チームで Terraform を導入しようとしている方
動作環境
| 項目 | バージョン |
|---|---|
| Terraform | 1.9 系 |
| 対象プロバイダ例 | AWS Provider 5.x |
TL;DR
- Terraform は「あるべき状態」を HCL で宣言し、差分を計算して適用するツール
-
init→plan→applyの3コマンドが基本ワークフロー -
State ファイル(
terraform.tfstate)が要。ローカル管理ではなくリモートバックエンドに置く - 変数は
variableで宣言し、terraform.tfvarsや環境変数で値を渡す - 同じ構成を再利用するなら Module にまとめる
- 環境分離は
workspaceよりもディレクトリ分割が無難 -
terraform fmtとterraform validateは CI に組み込む -
.terraform.lock.hclは Git で管理する - State にはシークレットが平文で入る。State 自体を機密情報として扱う
- 既存リソースは
importブロックで Terraform 管理下に取り込める
Tip 1: Terraform の基本ワークフローを理解する
Terraform の操作はざっくり以下の流れで進みます。
| コマンド | 役割 |
|---|---|
terraform init |
プロバイダのダウンロードとバックエンド初期化 |
terraform plan |
現状とコードの差分を計算(実環境は変更しない) |
terraform apply |
plan で出た差分を実環境に適用 |
terraform destroy |
コードで管理している全リソースを削除 |
terraform apply は対話プロンプトで yes を求められますが、CI などでは -auto-approve を付けます。本番環境ではこのフラグを安易に使わず、必ず plan の結果をレビューしてから適用しましょう。
Tip 2: Provider と Resource の最小構成
Terraform のコードは .tf ファイルに HCL(HashiCorp Configuration Language)で書きます。最小構成は次のとおりです。
terraform {
required_version = ">= 1.9.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
resource "aws_s3_bucket" "example" {
bucket = "my-terraform-example-bucket-20260501"
}
-
terraformブロック: Terraform 本体・プロバイダのバージョン制約 -
providerブロック: プロバイダの設定(リージョンや認証情報) -
resourceブロック: 作成・管理するリソース
resource "aws_s3_bucket" "example" の最後の example は Terraform 内部で参照するための名前で、AWS 側のバケット名とは別物です。
Tip 3: 変数は variable で宣言してから使う
ハードコードした値はすぐに散らかります。variable ブロックで宣言し、利用側で var.変数名 として参照します。
variable "region" {
type = string
description = "AWS リージョン"
default = "ap-northeast-1"
}
variable "environment" {
type = string
description = "環境名 (dev / stg / prd)"
validation {
condition = contains(["dev", "stg", "prd"], var.environment)
error_message = "environment は dev / stg / prd のいずれかにしてください。"
}
}
値の渡し方は複数あり、優先順位が決まっています。
| 渡し方 | 例 | 優先度 |
|---|---|---|
| コマンドラインフラグ | -var "environment=dev" |
高 |
*.auto.tfvars ファイル |
terraform.tfvars |
中 |
| 環境変数 | TF_VAR_environment=dev |
低 |
default 値 |
default = "dev" |
最低 |
シークレットを tfvars に書いて Git にコミットしないこと。環境変数(TF_VAR_*)か、AWS Secrets Manager / Parameter Store などからの取得を推奨します。
Tip 4: State ファイルが Terraform の心臓部
Terraform は「コード」と「実環境」を直接比較しているわけではありません。State ファイル(terraform.tfstate)に記録されたリソースの状態 を介して差分を計算しています。
このため、State が壊れたり、複数人で別々の State を持ったりすると整合性が崩れます。
State 管理のポイント
- ローカルの
terraform.tfstateは本番運用に使わない - リモートバックエンド(S3 + DynamoDB ロック、Terraform Cloud など)に置く
- バージョニングを有効にして、誤操作からのロールバックに備える
terraform {
backend "s3" {
bucket = "my-terraform-state"
key = "prd/network.tfstate"
region = "ap-northeast-1"
dynamodb_table = "terraform-state-lock"
encrypt = true
}
}
State ファイルにはリソースの全属性が平文で含まれます。RDS のパスワードや API キーなどシークレットも生の値が入ることがあるため、State 自体を機密情報として扱い、アクセス権限を厳格に絞ってください。
Tip 5: 同じ構成は Module にまとめる
「VPC + サブネット + ルートテーブル」のような繰り返し使う構成は Module にまとめると見通しが良くなります。
variable "cidr_block" {
type = string
}
resource "aws_vpc" "this" {
cidr_block = var.cidr_block
}
output "vpc_id" {
value = aws_vpc.this.id
}
呼び出し側はこう書きます。
module "network" {
source = "../../modules/network"
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "app" {
vpc_id = module.network.vpc_id
cidr_block = "10.0.1.0/24"
}
最初から無理に Module 化する必要はありません。2回以上同じ構成を書きそうになったら切り出すくらいの温度感が、過剰な抽象化を避けられて良いです。
Tip 6: 環境分離は workspace よりディレクトリ分割
terraform workspace は同じコードで複数の State を持てる機能ですが、環境(dev/stg/prd)の分離には不向きです。
| 観点 | workspace | ディレクトリ分割 |
|---|---|---|
| 環境ごとの設定差分 | 表現しづらい | 自然に書ける |
| State の保存先 | 同じバックエンド | 環境ごとに分けられる |
| 誤適用のリスク | 高(workspace の切り替え忘れ) |
低 |
| コードレビューしやすさ | 低 | 高 |
推奨ディレクトリ構成
.
├── modules/
│ ├── network/
│ └── compute/
└── envs/
├── dev/
│ ├── main.tf
│ └── backend.tf
├── stg/
└── prd/
workspace は「短期的な検証用 State を増やしたい」「機能ブランチごとに環境を立てたい」といった用途に絞るのが安全です。
Tip 7: 既存リソースは import ブロックで取り込む
コンソールで作ってしまった既存リソースも Terraform 管理下に取り込めます。Terraform 1.5 以降は import ブロック が使えて、コードでインポートを宣言できます。
import {
to = aws_s3_bucket.legacy
id = "existing-bucket-name"
}
resource "aws_s3_bucket" "legacy" {
bucket = "existing-bucket-name"
}
terraform plan -generate-config-out=generated.tf を使うと、既存リソースの設定を .tf として書き出してくれます。
インポート後の最初の plan で大量の差分が出ることがよくあります。慌てて apply せず、差分が「コードの不足」なのか「実環境の意図しない設定」なのかを確認してから進めましょう。
Tip 8: data ブロックで既存リソースを参照する
「他チームが管理している VPC の ID を取得したい」など、Terraform 管理外のリソースを読み取り専用で参照したいときは data ブロックを使います。
data "aws_vpc" "shared" {
tags = {
Name = "shared-vpc"
}
}
resource "aws_subnet" "app" {
vpc_id = data.aws_vpc.shared.id
cidr_block = "10.0.10.0/24"
}
resource は「作成・管理する」、data は「読み取るだけ」と覚えておくと混乱しません。
Tip 9: fmt と validate を CI に組み込む
Terraform には標準でフォーマッタと構文チェッカが付属しています。
terraform fmt -recursive
terraform validate
CI では次のように差分検出までやると、プルリクで「なぜか動かない」を防げます。
name: Terraform CI
on: [pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.0
- run: terraform fmt -check -recursive
- run: terraform init -backend=false
- run: terraform validate
加えて、セキュリティチェックには tfsec や Checkov を入れておくと、典型的な設定ミス(公開バケット、暗号化忘れなど)を早期に検出できます。
Tip 10: .terraform.lock.hcl を Git で管理する
terraform init 実行時に生成される .terraform.lock.hcl は、プロバイダのバージョンとハッシュを固定するファイルです。
-
node_modulesにおけるpackage-lock.jsonのような立ち位置 - 必ず Git にコミットする
- プロバイダのバージョンを上げたいときは
terraform init -upgradeで更新
.terraform/
- .terraform.lock.hcl
*.tfstate
*.tfstate.backup
.gitignore でコミット対象から外すと、開発者ごとにプロバイダのバージョンが変わってしまい再現性が崩れます。
逆に、.terraform/ ディレクトリ(プロバイダのバイナリが入る場所)と *.tfstate は Git にコミットしない のが鉄則です。
まとめ
本記事では、Terraform を本格的に使い始める前に押さえておきたい10個のポイントを紹介しました。
特に重要なのは:
- State ファイルの取り扱い: リモートバックエンド + ロック + 機密情報として保護
- 環境分離は workspace ではなくディレクトリ分割
-
.terraform.lock.hclのコミット で再現性を担保
Terraform は「宣言的に書ける」というシンプルな魅力の裏に、State 管理という独特の概念があります。最初にここを理解しておけば、複雑な構成にスケールしても破綻しにくくなります。
次のステップとしては、実際に小さなリソース(S3 バケットや Lambda 関数など)を Terraform で作って壊してみるのがおすすめです。手を動かすほど plan の差分の読み方が分かるようになります。
参考資料