Terraformコーディング規約(スタイルガイド)|fmt・命名規則・ディレクトリ構成の統一ルール
Terraformはチームで書くほど、インデントや命名、ファイルの分け方が人によってばらつき、レビューの差分ノイズや属人化を招きます。これを防ぐのがコーディング規約(スタイルガイド)です。本記事はHashiCorpの公式スタイルガイドを基準に、terraform fmtで機械化できる整形と、人が決める命名・ディレクトリ構成・コメント方針を切り分け、CIで規約を強制するところまでを実務目線で整理します。
まとめ:Terraformコーディング規約の要点
- 整形は機械化する:インデントは2スペース、連続する単一行引数は等号を揃える。手で整えず
terraform fmtに任せる。 - コミット前に検証:
terraform fmtとterraform validateをバージョン管理へのコミット前に必ず実行し、TFLintで組織独自ルールを足す。 - 命名は名詞+アンダースコア:
web_apiのようにsnake_caseで、リソースタイプ名(aws_instance等)を識別子に重ねない。 - ファイルは役割で分ける:
main.tf/variables.tf/outputs.tf/providers.tf/backend.tfを用途別に置き、変数と出力はアルファベット順に並べる。 - コメントは#が正式:単一行も複数行も
#を使う。//や/* */は後方互換のためのみ。 - 変数と出力に説明を付ける:すべての変数に
typeとdescription、すべての出力にdescriptionを付ける。 - 規約はCIで強制:pre-commitフックとパイプラインで
fmt -check/validate/TFLintを自動化し、レビュー任せにしない。
Terraformのスタイルガイドとは:公式ガイドと規約を定める目的
Terraformの構成言語HCLには、HashiCorpが推奨する慣用的(idiomatic)なスタイル規約があります。HashiCorp公式のStyle Guideが正典で、「異なるチームやモジュール間でも一貫性を保つため」に用意されています。まず押さえるべきは、規約には性質の違う2種類があるという点です。
ひとつは機械で強制できる整形(インデント・空白・等号の揃え)で、これは人が議論するものではなくterraform fmtに委ねます。もうひとつは人が方針を決める設計(命名規則・ファイル分割・コメントの粒度・変数の使いどころ)で、ここをドキュメント化してレビューで守るのが規約の本体です。この2つを混ぜると「整形の指摘でレビューが埋まる」「肝心の設計判断が抜ける」という失敗に陥ります。整形はツールへ、設計は規約へ、と役割を分けるのが出発点です。
コードフォーマットの規則:インデント・空白・等号揃え・ブロック順
公式が定める整形ルールは次のとおりで、いずれもterraform fmtが自動適用します。
- インデント:ネストの各レベルにつき2スペース。タブは使わない。
- 等号の揃え:同じネストレベルで単一行の引数が連続するとき、
=の位置を揃える。 - 引数とブロックの順序:ブロック本体に引数とネストブロックが混在するときは、引数を上にまとめ、その下にネストブロックを置き、両者を1つの空行で区切る。
- メタ引数の位置:
countやfor_eachなどのメタ引数を先頭に置き、1空行で他の引数と分ける。lifecycleやdepends_onはブロックの末尾に置く。 - 空行:トップレベルのブロックどうしは必ず1空行で区切る。ネストブロックも空行で区切るが、同種の関連ブロックをまとめる場合は例外とする。
resource "aws_instance" "web_api" {
count = 2
ami = "ami-0abcd1234"
instance_type = "t3.micro"
tags = {
Name = "web_api"
}
lifecycle {
create_before_destroy = true
}
}
ポイントは、これらを手作業で守ろうとしないことです。整形は保存時フォーマットとコミット前のterraform fmtで機械化し、レビューでは整形以外の設計に集中させます。
terraform fmt・validate・TFLintによる自動整形と検証
公式は「コードをバージョン管理にコミットする前にterraform fmtとterraform validateを実行する」ことを推奨しています。整形・構文・組織ルールを3段で自動化します。
terraform fmt の使い方(-check・-recursive・-diff)
terraform fmtは設定ファイルを公式スタイルに書き換えるコマンドです。引数なしで実行するとカレントディレクトリのファイルを整形します。CIでは書き換えずに差分の有無だけを判定する使い方が定番です。
# カレントディレクトリを整形
terraform fmt
# サブディレクトリまで再帰的に整形
terraform fmt -recursive
# 整形が必要なら非ゼロ終了(CIで未整形コミットを弾く)
terraform fmt -check -recursive -diff
-checkは未整形のファイルがあると終了コードを非ゼロにするため、CIの必須チェックに向きます。-diffを付けると変更点が表示され、どこが規約違反かを開発者に返せます。
terraform validate で構文と整合性を検証する
terraform validateは設定が構文的に正しく、内部的に整合しているか(変数参照や型の不一致など)を検証します。実際のインフラやクラウドAPIへはアクセスしないため副作用がなく、保存後チェック・pre-commitフック・CIのいずれでも安全に頻繁に回せます。
# プロバイダ取得のため初回のみ init(backend無しで軽く)
terraform init -backend=false
terraform validate
validateはfmtの後段に置きます。整形(見た目)と検証(意味)は別物で、fmtが通ってもvalidateで参照ミスが見つかることは珍しくありません。
TFLint で組織独自のルールを強制する
Linter(静的解析ツール)とは、コードを実行せずにソースを解析し、規約違反や潜在的な不具合を機械的に検出する仕組みです。fmtとvalidateは公式の一般ルールまでで、「このプロジェクトではこのタグを必須にする」といった独自ルールは検出しません。公式も「TFLintのようなLinterを使って組織独自のコーディングベストプラクティスを強制する」ことを勧めています。プロバイダ別プラグイン(aws・google等)を入れると、非推奨インスタンスタイプや無効な値の指摘まで踏み込めます。fmt(整形)→validate(整合)→tflint(独自ルール)の3段で役割が重なりません。
ファイル名とディレクトリ構造の規約
ファイルは中身の役割で分けます。公式が推奨する標準構成は次のとおりです。
| ファイル | 役割 |
|---|---|
| main.tf | リソース・データソースのブロック |
| variables.tf | 変数(アルファベット順) |
| outputs.tf | 出力(アルファベット順) |
| providers.tf | プロバイダ定義・設定 |
| terraform.tf | required_version・required_providers |
| backend.tf | backend(状態保存先)設定 |
| locals.tf | ローカル値 |
| override.tf | オーバーライド定義(限定用途) |
コード量が増えたら、main.tfを機能単位に割ります。たとえばnetwork.tf・storage.tf・compute.tfのように論理グループで分けると、目的のリソースへ到達しやすくなります。共通化する塊はモジュールとしてmodules/配下のディレクトリに切り出します。
Terraform Registryで公開するモジュールのリポジトリ名はterraform-<PROVIDER>-<NAME>の3部構成が必須です。<PROVIDER>は主に使うプロバイダ、<NAME>はそのモジュールが扱うインフラの種類を表し、<NAME>にはハイフンを含められます(例:terraform-aws-vpc)。
リソース・変数・出力の命名規則
命名はfmtでは直せない、人が守る規約の中心です。公式ルールはシンプルです。
- 説明的な名詞を使い、単語はアンダースコアで区切る(snake_case)。
- 識別子にリソースタイプを含めない。リソースアドレスに型がすでに含まれるため重複になる。
- リソースタイプと名前は両方ダブルクォートで囲む。
# 悪い例:型の重複・キャメルケース・ハイフン混在
resource aws_instance webAPI-aws-instance {}
# 良い例:型は含めず、名詞をアンダースコアで
resource "aws_instance" "web_api" {}
「ハイフンとアンダースコアどちらか」で迷ったら、Terraformの識別子(リソース名・変数名・出力名)はアンダースコアが正解です。ハイフンはリソース名では使えず、命名の一貫性も崩します。ハイフンが登場するのは、前述のモジュールリポジトリ名やクラウド側のリソース名(タグやNameの値)など、Terraform識別子の外側だけです。
コメントの書き方(#・複数行・コメントアウト)
Terraformのコメントは#が正式です。公式は「単一行にも複数行にも#を使う。//と/* */は慣用的とは見なされないが、旧HCLとの後方互換のためサポートされている」と明記しています。つまり新規コードは#で統一します。
# 単一行コメント
# 複数行にわたる説明も
# 各行に # を付けるのが慣用的
resource "aws_instance" "web_api" {
# なぜ t3.micro か(コスト理由)を書く。何をしているかは書かない
instance_type = "t3.micro"
}
コメントアウト(一時的な無効化)も同じく#を行頭に付けます。複数行をまとめて無効化したいときに/* */で囲むことは文法上可能ですが、正式スタイルから外れるため、恒久的なコードには残さず、動作確認の一時措置に留めます。
コメントの粒度も規約に含めます。「何をしているか」ではなく「なぜそうしたか」を書くのが原則で、コードを読めば分かることへのコメントはノイズです。逆に、for_eachやメタ引数のように効果が一目で分からない箇所には、意図を1行添えると保守性が上がります。
変数とOutputの定義規約(type・descriptionを必須に)
公式は「すべての変数にtypeとdescriptionを付ける」「すべての出力にdescriptionを付ける」ことを求めています。説明が揃っていると、Terraform-docsによるドキュメント自動生成で入出力仕様がそのまま表になり、規約とドキュメントが二重管理になりません。
variable "instance_type" {
type = string
description = "Web APIサーバーのEC2インスタンスタイプ"
default = "t3.micro"
}
output "instance_id" {
description = "起動したWeb APIインスタンスのID"
value = aws_instance.web_api.id
}
変数ブロックの推奨順はtype→description→default(任意)→sensitive(任意)→validation。出力ブロックも同様にdescription→value→sensitive(任意)の順で揃えます。パスワードやトークンなど秘匿値はsensitive = trueを付けて出力ログへの露出を防ぎます。あわせて公式は「変数やローカル値の乱用を避ける」「countとfor_eachは控えめに使う」と釘を刺しています。何でも変数化すると、かえって値の追跡が難しくなるためです。動かせる余地は必要な箇所だけに絞るのが規約として有効です。
リソースの順序と依存関係の管理
公式は「参照するリソースより後に、それに依存するリソースを定義する」ことを推奨しています。コードが上から下へ積み上がるように読め、依存の向きがファイル上の並びと一致します。たとえばデータソースは、それを参照するリソースより前に置きます。依存関係は基本的に属性参照(例:subnet_id = aws_subnet.main.id)で自然に表現し、Terraformが順序を解決します。参照だけでは表せない暗黙の依存があるときにのみdepends_onを使います。
リソースブロック内のパラメータ順も規約化できます。推奨順は、count/for_each(あれば)→ リソース固有の引数 → ネストブロック → lifecycle(必要なら)→ depends_on(必要なら)。並びを固定すると、どのリソースを見ても同じ場所に同じ情報があり、レビューが速くなります。
規約をCI/CDで強制する:pre-commitとパイプライン
規約は文書化しただけでは守られません。競合記事の多くがルール列挙で止まる一方、実務で効くのは規約を機械が弾く仕組みです。二段構えで自動化します。
ローカル(pre-commitフック):コミット時にterraform fmt -check、terraform validate、tflintを走らせ、違反があればコミットを止めます。pre-commitフレームワークとpre-commit-terraformのフック群を使うと設定ファイルだけで導入できます。
CI(プルリクエスト):ローカルフックは各自の環境依存で抜けが出るため、最終防衛線をCIに置きます。GitHub Actionsなどでterraform fmt -check -recursiveを必須チェックにし、未整形や検証エラーのままマージできないようにします。
# GitHub Actions の一例(整形チェックを必須化)
- run: terraform fmt -check -recursive -diff
- run: terraform init -backend=false
- run: terraform validate
あわせてバージョンとシークレットも規約で固定します。terraform.tfにrequired_versionとrequired_providersを明記し、.terraform-versionで実行系のバージョンを揃えると、環境差による差分事故を防げます。どのバージョンを基準にするかはTerraformの新機能とアップグレード手順を確認して決めます。状態ファイル(tfstate)はローカルに置かず、HCP TerraformやS3などのリモートbackendで共有・ロックするのが前提です。
逆に、規約を形骸化させる失敗パターンも明確です。コメント過多(コードを読めば分かる説明で埋める)、変数の乱用(すべてを変数化して追跡不能にする)、手動整形への依存(fmtをCIで強制せずレビュー指摘に頼る)——この3つは規約があっても品質を落とすため、避けるべきアンチパターンとしてチームで共有します。
よくある質問(FAQ)
Q. terraform fmt と terraform validate の違いは?
fmtは見た目(インデント・空白・等号揃え)を公式スタイルへ整える整形コマンド、validateは構文と内部整合(参照・型)を検証するコマンドです。役割が別なので、fmt→validateの順で両方を回します。
Q. Terraformのコメントアウトはどう書く?
行頭に#を付けます。単一行も複数行も#が正式です。//や/* */も動きますが後方互換用で慣用的ではないため、恒久コードには使いません。
Q. リソース名はハイフンとアンダースコアどちらを使う?
Terraformの識別子(リソース名・変数名・出力名)はアンダースコアです(例:web_api)。ハイフンはモジュールリポジトリ名terraform-<PROVIDER>-<NAME>やクラウド側の値でのみ登場します。
Q. 変数に type と description は必須?
公式スタイルガイドはすべての変数にtypeとdescription、すべての出力にdescriptionを付けることを推奨しています。ドキュメント自動生成とも噛み合うため、規約として必須化する価値があります。
Q. 公式のスタイルガイドはどこにある?
HashiCorp DeveloperのStyle Guideが正典です。本記事のルールもこれに準拠しています。仕様は更新されるため、細部は公式で最新を確認してください。