この記事を一言でいうと
Terraformは「インフラをコードで管理するためのツール(IaC: Infrastructure as Code)」。宣言的にインフラのあるべき状態を書き、差分だけを反映する仕組みと、コードを読み解くための3つの層(variable / module / resource)を押さえた上で、個人利用からチーム・組織で運用するときに出てくる課題、コストとガバナンスを仕組みで担保する方法までを整理する。
Terraformとは
手動でクラウドの管理画面をポチポチ操作しなくても、コードで環境構築を自動化できるツール。インフラの構成管理を機械処理可能なコードとして定義し、プロビジョニング(インフラ構築作業)を自動化するためのもの、と言い換えられる。従来のGUI操作や手順書メンテナンスに頼る運用は、対応する仕様が増えるほど作業時間と人的ミスが増えやすいが、コード化によってその負担を減らせる。
コードとして管理することで得られるメリットは主に次の5つ。
- 自動化による作業効率化: 手動オペレーションを減らせる
- 冪等性: 同じコードを何度適用しても結果が同じになる
- 再利用性: モジュール化した設定をプロジェクト間で使い回せる
-
検証可能性:
planで「何が変わるか」を適用前に確認できる - バージョニング: インフラの変更履歴をコードの変更履歴として追える
特徴
| 特徴 | 説明 |
|---|---|
| マルチクラウド対応 | AWS、Azure、GCP、S3、CloudFrontなど幅広いクラウドリソースを一括管理できる |
| 宣言的な構成 | 「どう作るか」ではなく「どうなっていてほしいか(理想の状態)」をコードで宣言する |
| 差分管理 | 現在のインフラ状態とコードの理想状態を比較して、必要な変更だけを実行する |
| ステート管理 | 「今のインフラがどうなっているか」を記録する仕組み |
| モジュール化 | よく使う設定をモジュールとして再利用できる |
Terraformは HCL(HashiCorp Configuration Language) という専用の記法でリソースを記述する。実行時は Terraform Core がコードを解釈し、AWSやAzureなど対象サービスごとの Provider を通じてクラウドAPIを呼び出す、という役割分担になっている。同じHCLの文法で書けるので、プロバイダーが変わっても学び直しのコストが小さいのが強み。研修資料で見た数字では、公開されているProviderが4,000以上・Moduleが15,500以上とのことで、ゼロから書かなくても既存の部品を組み合わせやすい環境が育っている。
主要コマンド
Terraformの基本ワークフローは Write(コードを書く)→ Plan(差分確認)→ Apply(反映) の3ステップ。コードをGitHubなどのVCSで管理すれば、Planの結果をチームでレビューしてからApplyする、というコードレビューの延長線上でインフラ変更を扱えるようになる。
| コマンド | 説明 | 目的 |
|---|---|---|
terraform init |
初期化。必要なプロバイダー(例:AWS)をダウンロードする | プロジェクト開始時に一度だけ行う |
terraform plan |
変更内容の確認。何が追加・削除・変更されるかを事前に確認する | 実行前のシミュレーション |
terraform apply |
実際にクラウドへ反映する | 本番適用 |
terraform destroy |
作成したリソースをすべて削除する | 環境リセットや検証終了時に使用 |
terraform fmt |
コードのフォーマットを整える | チーム内で見やすく統一 |
基本のファイル構成
プロジェクト直下は役割ごとにファイルを分けるのが基本。
| ファイル | 役割 |
|---|---|
versions.tf |
ツールの前提条件(Terraform / プロバイダーのバージョン指定) |
provider.tf |
接続先(AWSなど)の設定 |
backend.tf |
state の保存先の設定 |
main.tf |
リソース定義 |
さらに、1つのモジュール内で見ると「入力・実処理・出力」の3つに分けて考えると理解しやすい。
| ファイル | 役割 | たとえるなら |
|---|---|---|
variables.tf |
入力 | 関数の引数 |
main.tf |
実処理 | 関数の中身 |
outputs.tf |
出力 |
return 値 |
ステート(State)とは
結論: Terraformが管理している「インフラの最新状態を記録するファイル」。
目的
- 現在のインフラ構成(何を作ったか・どんな設定か)を保存する
- 次回の変更時に「何が変わるか」を判断する基準になる
- ステートがないとTerraformはインフラの差分を判断できない
管理方法の種類(代表3つ+α)
| 管理場所 | 特徴 |
|---|---|
| ローカル | デフォルト。terraform.tfstate がプロジェクト内に作成される。1人作業向け |
| S3バケット | AWS上に保存。複数人で共有できる。安全でスケーラブル |
| HCP Terraform | HashiCorp公式のクラウド管理サービス。アクセス制御や履歴管理が容易 |
| 他クラウド(GCS, Azure Storageなど) | 各クラウドプロバイダ向けのリモートバックエンドとしても利用可 |
Terraformの3つの層(variable / module / resource)
コードを読むときは、この3つがどう繋がっているかを追うと理解しやすい。
1. variable(変数定義)
- 役割: 変数の型と説明を定義するだけ
- リソース作成: しない
- 例えるなら: 関数の引数定義
variable "environment" {
type = string
description = "Deployment environment"
}
2. module(モジュール呼び出し)
- 役割:
terraform/modules/dynamodb/内の定義を「呼び出す」 - リソース作成: 直接は作らない。モジュール内の
resourceを実行させる - 例えるなら: 関数呼び出し
module "dynamodb" {
source = "../modules/dynamodb"
app_name = var.app_name
environment = var.environment
}
3. resource(リソース定義)
- 役割: 実際にAWSリソースを作成する指示
- リソース作成: する
- 例えるなら: 関数の実装部分
resource "aws_dynamodb_table" "work_progress_table" {
name = "${var.environment}-${var.app_name}-work-progress"
hash_key = "pk"
# ...
}
実行フロー
-
terraform applyを実行 -
module "dynamodb" { ... }を読む -
source = "../modules/dynamodb"に移動 - その中の
resource "aws_dynamodb_table"を見つける - 実際にAWSでDynamoDBテーブルを作成する
呼び出す側(main.tf):
module "dynamodb" {
source = "../modules/dynamodb" # どのモジュールを使うか
app_name = var.app_name # 変数を渡す
environment = var.environment
}
呼び出される側(モジュール内):
resource "aws_dynamodb_table" "work_progress_table" {
name = "${var.environment}-${var.app_name}-work-progress"
# 実際にテーブルが作成される
}
localsで重複をなくす
locals は、Terraform内部で一度だけ計算した値を何度も再利用するための仕組み。
localsなし(同じ式を複数箇所に重複して書いてしまう):
module "route53" {
project_domain_name = var.environment == "prod" ? "${var.app_name}" : "${var.environment}.${var.app_name}"
}
module "acm" {
project_domain_name = var.environment == "prod" ? "${var.app_name}" : "${var.environment}.${var.app_name}"
}
module "cloudfront" {
project_domain_name = var.environment == "prod" ? "${var.app_name}" : "${var.environment}.${var.app_name}"
}
localsあり(計算を1箇所にまとめて使い回す):
locals {
hosted_zone_name = var.environment == "prod" ? "${var.app_name}" : "${var.environment}.${var.app_name}"
}
module "route53" {
project_domain_name = local.hosted_zone_name
}
module "acm" {
project_domain_name = local.hosted_zone_name
}
module "cloudfront" {
project_domain_name = local.hosted_zone_name
}
変数の優先順位
同じ変数が複数箇所で指定された場合、Terraformは以下の優先順位(数字が小さいほど強い)で値を決定する。
| 強さ | 入力元 | 例 |
|---|---|---|
| 1 | コマンドラインの -var / -var-file(指定した順) |
terraform plan -var="env=prod" -var-file=prod.tfvars |
| 1(同格) | HCP Terraform (Cloud) の変数(UI) | Workspace Variables / Variable Sets |
| 2 |
*.auto.tfvars / *.auto.tfvars.json(辞書順) |
dev.auto.tfvars |
| 3 | terraform.tfvars.json |
- |
| 4 | terraform.tfvars |
- |
| 5 | 環境変数 | TF_VAR_env=prod |
| 6 |
variable の default
|
variable "env" { default = "dev" } |
チームでTerraformを運用するときの壁
個人のPCから直接 terraform apply する運用(DIY運用)は、チームやプロジェクトが増えるほど次のような課題にぶつかりやすい。
- stateやvarsの置き場所がチームごとにバラバラになりがち(ローカル管理、独自のTerraform用サーバー、CI経由のapplyなど、やり方が統一されない)
- 誰がいつ何を
applyしたかの履歴が追いにくい - チーム横断でのアクセス制御やレビューの仕組みを自前で作り込む必要がある
これを解決するのが HCP Terraform(HashiCorp公式のマネージドSaaS)や Terraform Enterprise(自社環境にセルフホストする版)。どちらも考え方は同じで、Organization → Project → Workspace という階層でチーム・環境(開発/ステージング/本番)ごとにstateと変数を分離し、内部的にはHashiCorp Vaultで暗号化した状態で一元管理してくれる。VCS(GitHubなど)と連携させれば、コードの変更を起点に plan → apply を回すワークフローも組める。自社データセンターなど閉域網にあるリソースを管理したい場合は、Agentを経由してHCP Terraform/Terraform Enterpriseと接続する構成も用意されている。
導入のメリットは大きく3つの軸で説明されていた。
- コスト: 利用費用の事前チェックや、不要になったリソースの自動削除
- リスク: 変更のガードレールを仕組み化できる(詳細は後述)ことに加えて、Terraformの外側(コンソールなど)から手動で行われた変更を継続的に検知する仕組み(ドリフト検出)で、コードと実インフラの整合性を保てる
- スピード: ベストプラクティスをすぐ使える形(OOTB)で提供でき、インフラ調達のセルフサービス化を進めやすい
なお、Datadogの調査(State of DevSecOps)によれば、AWS上で使われているIaCツールの中ではTerraformの採用率が最も高いとされている(クラウドベンダー純正のCloudFormationより多い)。マルチクラウドを前提にするなら、特定ベンダー謹製のIaCより実質的な標準になっているTerraformを選ぶ理由になる。
コストとガバナンスを仕組みで担保する
手動運用(ClickOps)は「ちょっと確認するだけだから」で例外が積み重なり、一貫性のあるワークフローや監査可能なガバナンスを保ちにくい。開発チーム側の認知的負荷(このルールを毎回自分で守らないといけない、という負担)も増える。本来は全員にルールを守らせたいところだが、確認・実装・メンテナンスのコストが重く、徹底しきれないというジレンマがある。IaC化した上で、さらに次のような仕組みを併用すると運用が安定する。
- 一時環境の自動削除: 検証用など寿命が決まっているワークスペースには、Ephemeral Workspaceのような自動破棄設定を使うと消し忘れによるコスト増を防げる
- 許可リソースの制限: 使ってよいインスタンスタイプなどを変数の許可リストとしてコード側で縛っておくと、想定外の高コストリソースが作られにくくなる
-
ポリシーのコード化: Sentinelのようなポリシー機構を使うと、「CISベンチマーク準拠のS3バケットにはアクセスログを必須にする」といったルールを
apply前に自動チェックできる。組織の共通ルールとして配布・再利用もしやすい -
Run Tasksでの外部連携: セキュリティスキャンなど外部サービスのチェックを、
plan→applyの間に挟み込んで自動実行できる仕組みもある - アカウント払い出しの自動化: AWS Control Tower + Account Factory for Terraformのような仕組みと組み合わせると、新規AWSアカウントの発行から初期設定までをパイプライン化できる(大規模組織向けの応用例)
学んだこと
- Terraformは「手順」ではなく「あるべき状態」を書くツールで、実際の変更は
planで確認してからapplyする、という流れが基本 - ステートがあるからこそ差分検出ができる。ステートの保存場所(ローカル/S3/HCP Terraform)はチーム運用の前提を左右する
- コードを読むときは
variable(入力)→module(呼び出し)→resource(実処理)の順で追うと、どこで何が作られているか整理しやすい - 同じ式をあちこちに書きそうになったら
localsでまとめる、というのが重複排除の基本パターン - 個人のstate管理がそのままチーム運用に耐えるわけではない。「誰がどこでapplyするか」を統一する仕組み(HCP Terraform/Terraform Enterpriseやポリシーのコード化)は、規模が大きくなるほど効いてくる
-
applyして終わりではなく、コンソールなどTerraformの外側で加えられた変更を検知する「ドリフト検出」もセットで考える必要がある。stateとコードが一致している前提が崩れると差分管理そのものが信用できなくなる - 「冪等性」「検証可能性」はIaC全般の性質で、Terraform固有の話ではない。Terraformはそれをマルチクラウドで同じ文法(HCL)で扱えるようにした位置づけ、と理解すると他のIaCツールとの違いも整理しやすい
- 結局のところ、コマンドや概念を読んで分かった気になるのと、実際に手を動かしてコードを書いてみるのとでは理解の深さが全然違う。特に環境を分けた運用は、自分で一度事故らないと手順の大事さが実感できなかった
つまずいたところ
-
環境ごとに
terraform initをやり直さなかった: stage環境でinitしてそのまま作業していた流れで、prod環境側でも改めてinitせずにplan/applyしてしまい、自分が意図していないリソースに変更が反映されたことがあった。ディレクトリ(またはbackend設定)を環境ごとにきちんと分けた上で、環境を切り替えるたびにinitからやり直す、という手順を徹底したい -
initやplanが裏で何をしているか(プロバイダーの取得、backendへの接続、既存stateの読み込みなど)を理解していないと、上のような事故があっても原因に気づきにくい。コマンドの名前だけ覚えるのではなく、中身を理解しておく必要があると感じた - 既存のAWSリソースをTerraform管理下に持ち込む作業(import)は特につまずきが多かった。ここは内容が独立しているので、別記事としてまとめる予定
次に試したいこと
- Ephemeral Workspaceなど、検証環境を自動で片付ける仕組みの実際の設定方法
- Sentinelなど、ポリシーをコードとしてapply前チェックに組み込む方法
- S3 + DynamoDBでのstate共有と、HCP Terraformでの一元管理を実際に比較してみる
- 環境(stage/prod)ごとのディレクトリ構成と、
init/planの手順をテンプレート化して事故を防ぐ - 既存リソースのimportでつまずいた点は、別記事として改めて整理する