Terraformのトップレベルブロックについての備忘録
Terraformで設定ファイル(.tf)を書く際、トップレベル(インデントなしの最上位階層)に定義できるブロックについて、調べた内容を整理したメモ。
いつもどのブロックが何をするのか、関連するものが混ざってしまっていたので、分類してまとめた。
トップレベルブロックの一覧
.tf ファイルの最上位階層に記述できるブロックは、大きく分けると以下の通り。
ツールや接続先の定義
| ブロック名 | 用途・メモ |
|---|---|
terraform |
必要なTerraformやプロバイダーのバージョン(required_providers)、Stateファイルの保存先(Backend)を定義する。 |
provider |
AWSやGoogle Cloudなど、接続先クラウドの認証情報やリージョンなどを設定する。 |
変数とデータの管理
| ブロック名 | 用途・メモ |
|---|---|
variable |
外部から値を受け取るための変数(入力引数)を定義する。 |
locals |
コード内で再利用する共通の値や、動的な計算結果(定数)を定義する。 |
data |
クラウド上に既に存在しているリソースの情報を参照して、値を取得する。 |
output |
リソース作成後に、他のモジュールやCLIに値を引き渡すための出力値を定義する。 |
リソースの作成
| ブロック名 | 用途・メモ |
|---|---|
resource |
クラウド上に新規リソースを作成・管理するための主要なブロック。 |
module |
外部や別ディレクトリにある設定をグループ化した「モジュール」を呼び出して再利用する。 |
状態管理やリファクタリング用のブロック
| ブロック名 | 用途・メモ |
|---|---|
import |
手動で作成済みのリソースをTerraformの管理下(State)に取り込む(v1.5〜)。 |
moved |
リソース名(State上のアドレス)を変更する際に、リソースを再作成せずに移動させる(v1.1〜)。 |
removed |
リソースの実体は削除せず、Terraformの管理(State)からのみ除外する(v1.7〜)。 |
check |
デプロイ完了後に、インフラの状態が正常であるかを継続的にテスト・検証する(v1.3〜)。 |
メモ: import, moved, removed などのリファクタリング用ブロックは、従来 terraform state mv などのCLIコマンドを実行して直接State(terraform.tfstate)を操作していた処理を、設定ファイル上にコード(宣言的)として記述できるようにしたもの。コマンドでの手作業を減らし、コードとして変更履歴を残すことで安全にリファクタリングを行えるメリットがある。
main.tf での記述例
調べたブロックをまとめた設定ファイルのサンプル。
main.tf
# ツール設定
terraform {
required_version = ">= 1.7.0"
# プロバイダーのソース(配布元)やバージョンを定義
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# 接続先の設定
provider "aws" {
region = "us-east-1"
}
# 入力変数
variable "env" {
default = "prod"
}
# ローカル変数(定数)
locals {
tag = "managed-by-tf"
}
# 既存リソースの参照
data "aws_ami" "latest_linux" {
most_recent = true
owners = ["amazon"]
}
# リソースの新規作成(移行先の新リソース名で定義)
resource "aws_instance" "web" {
ami = data.aws_ami.latest_linux.id # data.aws_ami.latest_linux を参照
instance_type = "t3.micro"
tags = {
Name = local.tag # local.tag を参照
}
}
# 外部モジュールの呼び出し
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
name = "${var.env}-vpc" # var.env を参照
cidr = "10.0.0.0/16"
}
# 既存リソースのインポート(v1.5以降)
# plan時に -generate-config-out を指定すると以下のようなコードが自動生成される
# 具体的な操作方法などは補足に記載。
# resource "aws_s3_bucket" "manual_bucket" {
# bucket = "my-hand-made-bucket-2026"
# }
import {
to = aws_s3_bucket.manual_bucket
id = "my-hand-made-bucket-2026"
}
# リソース名の変更に伴うStateの移動(v1.1以降)
# 移行前のリソースはコードから削除(またはリネーム)しておく。(ここではわかりやすさのためにコメントアウト)
# 具体的な操作方法などは補足に記載。
moved {
from = aws_instance.old_web # 移行前の旧リソース名
to = aws_instance.new_web # 移行後の新リソース名
}
#resource "aws_instance" "old_web" {
# ami = data.aws_ami.latest_linux.id
# instance_type = "t3.micro"
#}
resource "aws_instance" "new_web" {
ami = data.aws_ami.latest_linux.id
instance_type = "t3.micro"
}
# Terraform管理からの除外(v1.7以降)
# 元々は以下のような定義があったものを削除したうえでremovedブロックを追記
# 具体的な操作方法などは補足に記載。
# resource "aws_s3_bucket" "old_archive" {
# bucket = "my-old-archive-bucket"
# }
removed {
from = aws_s3_bucket.old_archive
lifecycle {
destroy = false
}
}
# デプロイ後の状態検証(v1.3以降)
# アサーションが失敗しても、インフラ構築はエラー終了せず警告(Warning)のみ出力される
# 具体的な操作方法などは補足に記載。
check "ec2_security_check" {
assert {
# 設定ファイルの引数(設定値)を確認するより、デプロイ結果の実際の属性値を検証する方が確実
condition = aws_instance.new_web.public_ip == ""
error_message = "警告: EC2インスタンス ${aws_instance.new_web.id} にパブリックIPアドレスが割り当てられています!"
}
}
# 出力値の定義
output "server_ip" {
value = aws_instance.new_web.public_ip # 移行後の aws_instance.new_web の値を参照
}
ファイルの分割:役割ごとのファイル分割
Terraformは同じディレクトリ内にある .tf ファイルをすべて読み込んで1つの設定としてマージする。そのため、可読性を上げるために、main.tf にすべてを記載するのではなく、以下のようにファイルを分けて整理することが多い。
| 対象となるブロック | よくあるファイル名称 | 内容 |
|---|---|---|
terraform, provider
|
providers.tf / terraform.tf
|
バージョン指定やState保存先(Backend)を terraform.tf に、接続先設定を providers.tf に分けるパターンも多い(大規模プロジェクトなどでよく使われる慣習)。 |
variable |
variables.tf |
外部から値を受け取るための入力変数の定義。 |
locals |
locals.tf |
コード内で再利用する共通の値や定数の定義。 |
output |
outputs.tf |
リソース作成後に出力する値の定義。 |
resource, module
|
main.tf(または vpc.tf, iam.tf など用途に応じた名称) |
メインとなるインフラリソースの作成や、外部モジュールの呼び出し。 |
複数環境(dev, stg, prd)の管理
実際に利用する場合、環境を分けて管理したくなることが多い。
これまで整理したトップレベルブロックを「共通化してDRYにできる部分」と「環境ごとに分けるためDRYにできない部分」に整理すると、再利用しやすくなる。
-
resourceやmoduleによる具体的なインフラの構成。これらはmodulesディレクトリ配下に「テンプレート」として記述し、各環境から呼び出して使い回す。 -
providerやterraform { backend }などの接続先・State保存先設定。環境ごとに別々のAWSアカウントやStateの保存場所を指定する必要があるため、環境ごとのフォルダに直接記述する。 -
variableを介して流し込む、環境ごとの具体的なパラメータ値(環境名、インスタンスのサイズなど)。
ディレクトリで環境を分ける方法
環境ごとの管理方法はいろいろあるが、ここではディレクトリで分ける方法を紹介する。他の方法(例:terragrunt の利用など)は機会があれば紹介したい。
環境ごとにフォルダを完全に分け、その中でそれぞれ terraform コマンドを実行する。
共通するインフラ(VPCやEC2の構成など)は「モジュール(module)」として切り出して再利用し、各環境のディレクトリから呼び出す。
フォルダ構成の例:
.
├── modules/ # 共通テンプレートを置く場所(DRYにできる部分)
│ └── network/
│ ├── main.tf # resource ブロック(リソースの具体的な構成)を記述
│ └── variables.tf # variable ブロック(このモジュールで必要な変数の宣言)
└── environments/ # 各環境ごとのディレクトリ(DRYにできない部分)
├── dev/
│ ├── main.tf # module ブロック(modules/networkモジュールを呼び出す)
│ ├── providers.tf # terraform (backend) や provider ブロック(接続先・State保存先)を記述
│ ├── variables.tf # variable ブロック(この環境固有の変数の宣言)
│ └── terraform.tfvars # dev環境用の具体的なパラメータ値(変数の中身)を指定
├── stg/
└── prd/
-
補足:
-
実際に利用する場合は、各フォルダ内(例:dev)に移動して、
terraform init、terraform plan、terraform applyを実行する。 -
moduleを呼び出すときは、以下のように予約語であるsourceに相対パスを指定する。environments/dev/main.tf
module "network" { source = "../../modules/network" } -
ローカルで実行する場合、Stateファイル(
terraform.tfstate)はコマンドを実行したフォルダの直下(例:environments/dev/terraform.tfstate)にそれぞれ作成される。どの環境のStateファイルかがフォルダ構造そのままで整理されるため、見失うことはない。
-
警告:Stateファイル(
terraform.tfstate)の取り扱いについて
Stateファイルには、パスワードやアクセスキーなどの機密情報がプレーンテキストで含まれることがあります。
必ず Git の管理から外すよう.gitignoreに指定してください。また、複数人で共同開発する場合はローカルに置かず、S3やGoogle Cloud StorageなどのリモートBackendに保存するのが一般的です。
補足:状態管理・リファクタリングブロックの実行手順
リファクタリング用の各ブロック(import, moved, removed, check)を適用する際の手順メモ。
1. import ブロック(既存リソースの取り込み)
- 設定ファイル(例:
main.tf)にimportブロックを記述する。 - コマンドで
-generate-config-outオプションをつけてplanを実行し、コードを自動生成させる。terraform plan -generate-config-out=generated.tf-
注意: WindowsのPowerShellでは、等号(
=)の解釈によりエラーになることがありました。その場合は以下のように引数をダブルクォーテーションで囲んで実行してください。terraform plan "-generate-config-out=generated.tf"
-
注意: WindowsのPowerShellでは、等号(
- 生成された
generated.tfの内容を確認・調整する。-
注意: 自動生成された
generated.tfはそのまま残して実行しても問題ないが、構成整理のために中身をmain.tfなどの任意のファイルにコピー(移動)し、不要になったgenerated.tf自体は削除することが多い。
-
注意: 自動生成された
- 定義されたコードが存在する状態で
terraform applyを実行し、Stateに状態を反映させる(この段階ではクラウド上の実物には変更は入らない)。 - 反映完了後、用済みとなった
importブロック自体はコードから削除する(残しておいても問題はないが、不要なため消すのが一般的)。
2. moved ブロック(リソース名の変更・移動)
- コード内の対象リソースのアドレス名(定義名)を変更する。
- 設定ファイルに
movedブロックを記述し、from(変更前)とto(変更後)のアドレスを指定する。-
注意:
movedの適用時点で、移行先(to)のnew_webのリソース定義はコード上に存在している必要がありますが、移行元(from)のold_webのリソース定義はコード上から消えている(名前を書き換えているため)必要があります。両方がコードに残っていると、Terraformが正しく移行を認識できず、エラーや二重作成の原因になります。
-
注意:
-
terraform planを実行し、リソースの再作成(破壊と作成)ではなく「名前のみの変更(Stateの更新)」として認識されていることを確認する。 -
terraform applyを実行してStateを更新する。-
注意:
movedブロックはState移行完了後もコードから削除しないのが基本。削除してしまうと、他の開発者の環境や、本番環境などの別環境でapplyを実行する際に移動が自動適用されず、再作成が発生してしまうため。
-
注意:
3. removed ブロック(管理対象からの除外)
- 設定ファイルに
removedブロックを記述し、対象のアドレスとlifecycle { destroy = false }を指定する。-
注意: もし
destroy = falseを指定しない場合(またはdestroy = trueにした場合)、Terraform管理から外れると同時に、クラウド上のインフラ実物も削除(破壊)されてしまう。実物を残して管理から外したい場合は、必ずdestroy = falseの明記必要。
-
注意: もし
- これまで定義していた対象の
resourceブロックをコードから完全に削除する。 -
terraform planを実行し、クラウドの実物が削除対象にならず、Stateから除外されることだけが計画されているのを確認する。- 注意:「Stateから除外(removed)されますが、破壊(destroyed)はされません」というような表示がでます。
-
terraform applyを実行する。 - 反映完了後、用済みとなった
removedブロックをコードから削除する。
4. check ブロック(デプロイ後の状態検証)
- 設定ファイルに
checkブロックを記述し、検証したいリソースのパラメータと失敗時のerror_messageを指定する。 -
terraform planまたはterraform applyを実行する。-
ポイント: デプロイ実行時だけでなく、
plan(Refreshing state)の段階でも毎回チェックが行われ、稼働中のインフラにドリフト(設定のズレ)が発生していないかを監視できます。
-
ポイント: デプロイ実行時だけでなく、
- (検証に成功した場合)通常通りエラーや警告なしで終了する。
-
注意:
checkブロックはリソースが稼働している間、継続的にそのポリシーを検証し続けるために記述するものなので、moved同様に適用完了後もコードから削除しないのが基本。
-
注意:
- (検証に失敗した場合)全体の処理自体は正常に完了(またはNo changes)するが、ログの最後に警告(Warning)として
error_messageに書いたカスタムメッセージが表示される。-
新規作成時の注意: まだリソースが存在しない最初の計画(
plan)段階では、チェック対象の値がknown after applyになるため検証がスキップされ、apply完了直後にチェックが実行される。
-
新規作成時の注意: まだリソースが存在しない最初の計画(