最近の記事

再利用可能なモジュールは ./modules/ 配下に配置する

ルール

Reusable modules are placed under ./modules/ (e.g., ./modules/networking)

(再利用可能なモジュールは ./modules/ 配下に配置する)

解説

複数の環境から参照されるサブモジュールはすべてmodules/に集めます。こうすることでモジュールの性格を推測しやすくなり、迷うことが減ります。

サンプルコード

# ディレクトリ構造の例
# ./modules/networking/main.tf - ネットワーク関連の再利用可能モジュール
# ./modules/compute/main.tf    - コンピュート関連の再利用可能モジュール
# ./modules/database/main.tf   - データベース関連の再利用可能モジュール

参考リンク

Infrastructure 開発ガイドライン Repository Structure

全てのモジュールにREADME.mdを配置する

ルール

Always place README.md in every module. README.md should have module information auto-inserted by terraform-docs

(全てのモジュールにREADME.mdを配置する)

解説

README.mdには本来目的や概要を記すのですが、それらをSpec-Drivenではspec.mdに記します。 そのためterraform-docsによって自動生成されたドキュメントがREADME.mdのメインコンテンツとなります。

サンプルコード

# Networking Module

<!-- BEGIN_TF_DOCS -->
<!-- END_TF_DOCS -->

参考リンク

Infrastructure 開発ガイドライン Repository Structure

すべてのディレクトリ名とファイル名にはsnake_caseを使用する

ルール

Use snake_case for all directory and file names (e.g., awesome_module/awesome_specifications.md)

(すべてのディレクトリ名とファイル名にはsnake_caseを使用する)

解説

スネークケースは、Terraformのリソース名やモジュール名でよく用いられる命名規則です。 ファイル名についてはケバブケースが使われることも多いのですが、逐一スネークケースにすべきかケバブケースにすべきかを考えないといけなくなっていると混乱のもとのため、なるべくスネークケースによせています。

サンプルコード

# ディレクトリ構造の例:
# modules/
#   awesome_module/              # snake_caseのディレクトリ名
#     main.tf
#     variables.tf
#     awesome_specifications.md  # snake_caseのファイル名

参考リンク

Infrastructure 開発ガイドライン Repository Structure

再利用可能モジュール間の相互参照を避ける

ルール

Avoid cross-referencing between reusable modules. Control references from the caller as much as possible

(再利用可能モジュール間の相互参照を避ける。参照は可能な限り呼び出し側で制御する)

解説

モジュール間の相互参照は取り回しを大きく損なうためできる限り避けます。

よく問題を起こすのがセキュリティグループルールで、呼び出し元と呼び出し先が別のモジュールでかつoutboundとinboundをきちんと指定するだけで相互参照になってしまいます。このようなケースではエッジモジュール(配線専用モジュール)を別に用意し、そのモジュール内でルールを設定します。カプセル化の観点からは好ましくないのですが、制約上いたしかたのない妥協と言えます。

サンプルコード

# main.tf (ルートモジュール)
module "networking" {
  source = "./modules/networking"

  vpc_cidr = var.vpc_cidr
}

module "database" {
  source = "./modules/database"

  # 呼び出し側で依存関係を明示的に制御
  vpc_id           = module.networking.vpc_id
  subnet_ids       = module.networking.private_subnet_ids
  security_group_id = module.networking.db_security_group_id
}

# modules/database内で直接 module.networking を参照しない

参考リンク

Infrastructure 開発ガイドライン Repository Structure

locals.tfは必要な場合のみ使用する

ルール

Use locals.tf only when necessary

(locals.tfは必要な場合のみ使用する)

解説

locals ブロックは複雑な式の計算結果を一時的に保持したり、繰り返し使われる値に名前を付けるために使用しますが、過度に使用するとコードの可読性が低下します。そのためなるべく使わないようにします。

どうしても必要な場合は locals.tf に書きます。

サンプルコード

# locals.tf
locals {
  # 複数箇所で使用される複雑な式
  common_tags = merge(
    var.default_tags,
    {
      Environment = var.environment
      ManagedBy   = "Terraform"
    }
  )

  # 計算結果を保持
  instance_count = var.environment == "production" ? 3 : 1
}

参考リンク

Infrastructure 開発ガイドライン Repository Structure

ヘルパースクリプトはhelpers/ディレクトリに配置する

ルール

Save helper scripts in helpers/ directory. Basically place directly under repository root directory, but can be placed under specific module if limited to that module

(ヘルパースクリプトはhelpers/ディレクトリに保存する。基本的にリポジトリルート直下に配置するが、特定モジュール専用の場合はそのモジュール配下に配置できる)

解説

ヘルパースクリプトとは、terraformから呼び出さない、インフラ構築・運用の支援用スクリプトです。テストコマンドをまとめたスクリプトなどが該当します。

スクリプトファイルはscripts/に置きたくなりますが、Terraformでscripts/はカスタムスクリプトと呼ばれるTerraformから呼び出すためのスクリプトを置くための場所として使うことになっています。そのため混同しないよう、Terraformから呼び出さないスクリプトはhelpers/に配置します。

サンプルコード

# プロジェクト全体で使用するヘルパースクリプト
# ディレクトリ構造:
# ./helpers/
#   check_state.sh          # 全モジュールの状態確認
#   generate_docs.sh        # ドキュメント生成
#
# 特定モジュール専用のヘルパースクリプト
# ./modules/database/helpers/
#   test_connection.sh      # このモジュールのDB接続テスト
#   backup_verification.sh  # このモジュールのバックアップ検証

参考リンク

Infrastructure 開発ガイドライン Repository Structure

カスタムスクリプトは可能な限り使用しない

ルール

Avoid using custom scripts as much as possible. If necessary, create a scripts/ directory directly under the using module and save scripts there

(カスタムスクリプトは可能な限り使用しない。必要な場合は使用するモジュール直下にscripts/ディレクトリを作成し、そこにスクリプトを保存する)

解説

Terraformから呼び出すスクリプト(カスタムスクリプト)を使う場合はscripts/以下に置きます。ただしほとんど必要自体ないはずです。

むしろヘルパースクリプトと呼ばれる、Terraformから呼び出すのではない補助用のスクリプトをscripts/に置かないよう気を付けます。ヘルパースクリプトは、helpers/に保存します。こちらは通常リポジトリルート直下に保存します(e.g., <root_dir>/helpers/awesome.sh)

サンプルコード

参考リンク

Infrastructure 開発ガイドライン Repository Structure

静的ファイルはfiles/ディレクトリに配置する

ルール

Save static files referenced but not executed by Terraform (like EC2 startup scripts) in files/. Basic placement is at caller location (e.g., ./modules/api/files/userdata.sh), but project-wide files go directly under root directory

(Terraformから参照されるが実行されない静的ファイル(EC2起動スクリプトなど)はfiles/に保存する。基本的に呼び出し元の場所に配置するが、プロジェクト全体で使用するファイルはルート直下に配置する)

解説

Terraformリソースから参照される静的ファイルには、EC2のユーザーデータスクリプト、Lambda関数のダミーのソースコード、設定ファイルなどがあります。 決めの問題でしかないのですが、こういうことも決めておかないとカオスになりますので、決めておきます。

サンプルコード

# ディレクトリ構造:
# modules/
#   api/
#     main.tf
#     files/
#       userdata.sh          # EC2起動スクリプト
#       app_config.json      # アプリケーション設定ファイル
# files/
#   dummy.py             # セットアップ専用のダミーPythonコード

参考リンク

Infrastructure 開発ガイドライン Repository Structure

エントリーポイントとなるルートモジュールは環境名で表現する

ルール

Root modules serving as entry points are expressed using environment names. These are placed directly under the root directory (e.g., ./development ./production)

(エントリーポイントとなるルートモジュールは環境名で表現する)

解説

Terraformを実行する起点となるルートモジュールは環境ごとに独立したディレクトリとして表現します。 たとえばdevelopment/なら開発環境用です。これはTerraform公式が紹介している方式でもあります。

サンプルコード

# ディレクトリ構造の例
# ./development/main.tf - 開発環境用のエントリーポイント
# ./staging/main.tf     - ステージング環境用のエントリーポイント
# ./production/main.tf  - 本番環境用のエントリーポイント

# ./development/main.tf の内容例
module "networking" {
  source = "../modules/networking"

  environment = "development"
  vpc_cidr    = "10.0.0.0/16"
}

module "compute" {
  source = "../modules/compute"

  environment   = "development"
  instance_type = "t3.micro"
}

参考リンク

Infrastructure 開発ガイドライン Repository Structure

required_providersで必要なプロバイダとバージョン制約を宣言する

ルール

Declare required providers and version constraints in required_providers (e.g., = 4.0)

(required_providers で必要なプロバイダとバージョン制約を宣言する)

解説

プロバイダやTerraformのバージョンによって無視できない挙動の違いが出ることが稀にあります。 本番環境でそのようなことが起こらないよう、バージョンは「=」だけで完全に固定します。「以上(>=)」や「パッチバージョンの違いは許す"~>"」のようなあいまいな書き方はすべて禁止です。

サンプルコード

# versions.tf
terraform {
  required_version = "= 1.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "= 4.0"
    }
  }
}

参考リンク

Infrastructure 開発ガイドライン Provider Configuration