コンテンツにスキップ

Terraform テンプレート - 大規模向け

大規模向けでは、modules/environments/ を分け、環境差分と再利用部品を切り離す前提で考える。

ディレクトリ構造

terraform/
├── modules/
│   ├── network/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   ├── compute/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   ├── security/
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   └── outputs.tf
│   └── storage/
│       ├── main.tf
│       ├── variables.tf
│       └── outputs.tf
├── environments/
│   ├── dev/
│   │   ├── backend.tf
│   │   ├── providers.tf
│   │   ├── versions.tf
│   │   ├── main.tf
│   │   ├── variables.tf
│   │   ├── terraform.tfvars
│   │   └── outputs.tf
│   ├── stg/
│   └── prod/
└── README.md

この構成の意図

  • modules/: 再利用部品を置く
  • environments/: 環境差分を閉じ込める
  • backend.tf: state 保存先を環境別に分ける
  • versions.tf: Terraform / Provider バージョンを固定する
  • terraform.tfvars: 環境固有値を入れる

このページで載せる対象

  • modules/network
  • modules/security
  • modules/compute
  • modules/storage
  • environments/dev

modules/network/main.tf

resource "aws_vpc" "main" {
  cidr_block = var.vpc_cidr_block

  tags = {
    Name = "${var.project_name}-vpc"
  }
}

resource "aws_subnet" "public_a" {
  vpc_id                  = aws_vpc.main.id
  cidr_block              = var.public_subnet_cidr
  map_public_ip_on_launch = true
  availability_zone       = "${var.aws_region}a"
}

resource "aws_subnet" "private_a" {
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.private_subnet_cidr
  availability_zone = "${var.aws_region}a"

  tags = {
    Name = "${var.project_name}-private-a"
  }
}

modules/network/variables.tf

variable "aws_region" {
  type = string
}

variable "project_name" {
  type = string
}

variable "vpc_cidr_block" {
  type = string
}

variable "public_subnet_cidr" {
  type = string
}

variable "private_subnet_cidr" {
  type = string
}

modules/network/outputs.tf

output "vpc_id" {
  value = aws_vpc.main.id
}

output "public_subnet_id" {
  value = aws_subnet.public_a.id
}

output "private_subnet_id" {
  value = aws_subnet.private_a.id
}

modules/security/main.tf

resource "aws_security_group" "web" {
  name        = "${var.project_name}-web-sg"
  description = "Allow HTTP and HTTPS"
  vpc_id      = var.vpc_id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "${var.project_name}-web-sg"
  }
}

modules/security/variables.tf

variable "project_name" {
  type = string
}

variable "vpc_id" {
  type = string
}

modules/security/outputs.tf

output "web_sg_id" {
  value = aws_security_group.web.id
}

modules/compute/main.tf

data "aws_iam_policy_document" "ec2_assume_role" {
  statement {
    effect = "Allow"

    principals {
      type        = "Service"
      identifiers = ["ec2.amazonaws.com"]
    }

    actions = ["sts:AssumeRole"]
  }
}

resource "aws_iam_role" "ec2_role" {
  name               = "${var.project_name}-ec2-role"
  assume_role_policy = data.aws_iam_policy_document.ec2_assume_role.json
}

resource "aws_instance" "web" {
  ami                    = var.ami_id
  instance_type          = var.instance_type
  subnet_id              = var.subnet_id
  vpc_security_group_ids = [var.security_group_id]

  tags = {
    Name = "${var.project_name}-web"
  }
}

modules/compute/variables.tf

variable "project_name" {
  type = string
}

variable "ami_id" {
  type = string
}

variable "instance_type" {
  type = string
}

variable "subnet_id" {
  type = string
}

variable "security_group_id" {
  type = string
}

modules/compute/outputs.tf

output "instance_id" {
  value = aws_instance.web.id
}

output "iam_role_name" {
  value = aws_iam_role.ec2_role.name
}

modules/storage/main.tf

resource "aws_s3_bucket" "logs" {
  bucket = var.bucket_name

  tags = {
    Name = "${var.project_name}-logs"
  }
}

resource "aws_s3_bucket_versioning" "logs" {
  bucket = aws_s3_bucket.logs.id

  versioning_configuration {
    status = "Enabled"
  }
}

modules/storage/variables.tf

variable "project_name" {
  type = string
}

variable "bucket_name" {
  type = string
}

modules/storage/outputs.tf

output "bucket_name" {
  value = aws_s3_bucket.logs.bucket
}

environments/dev/backend.tf

terraform {
  backend "s3" {
    bucket         = "sample-dev-terraform-state"
    key            = "network/terraform.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    use_lockfile   = true
  }
}

environments/dev/providers.tf

provider "aws" {
  region = var.aws_region
}

environments/dev/versions.tf

terraform {
  required_version = ">= 1.8.0"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
}

environments/dev/variables.tf

variable "aws_region" {
  type = string
}

variable "project_name" {
  type = string
}

variable "vpc_cidr_block" {
  type = string
}

variable "public_subnet_cidr" {
  type = string
}

variable "private_subnet_cidr" {
  type = string
}

variable "ami_id" {
  type = string
}

variable "instance_type" {
  type = string
}

variable "bucket_name" {
  type = string
}

environments/dev/main.tf

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

  aws_region          = var.aws_region
  project_name        = var.project_name
  vpc_cidr_block      = var.vpc_cidr_block
  public_subnet_cidr  = var.public_subnet_cidr
  private_subnet_cidr = var.private_subnet_cidr
}

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

  project_name = var.project_name
  vpc_id       = module.network.vpc_id
}

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

  project_name      = var.project_name
  ami_id            = var.ami_id
  instance_type     = var.instance_type
  subnet_id         = module.network.public_subnet_id
  security_group_id = module.security.web_sg_id
}

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

  project_name = var.project_name
  bucket_name  = var.bucket_name
}

environments/dev/outputs.tf

output "vpc_id" {
  value = module.network.vpc_id
}

output "public_subnet_id" {
  value = module.network.public_subnet_id
}

output "private_subnet_id" {
  value = module.network.private_subnet_id
}

output "web_sg_id" {
  value = module.security.web_sg_id
}

output "instance_id" {
  value = module.compute.instance_id
}

output "iam_role_name" {
  value = module.compute.iam_role_name
}

output "bucket_name" {
  value = module.storage.bucket_name
}

environments/dev/terraform.tfvars

aws_region          = "ap-northeast-1"
project_name        = "sample-dev"
vpc_cidr_block      = "10.0.0.0/16"
public_subnet_cidr  = "10.0.1.0/24"
private_subnet_cidr = "10.0.11.0/24"
ami_id              = "ami-xxxxxxxxxxxxxxxxx"
instance_type       = "t3.micro"
bucket_name         = "sample-dev-logs-bucket-20260802"

このテンプレートで AWS 上にできるもの

[dev environment]
  ├─ [VPC]
  │   ├─ [Public Subnet]
  │   └─ [Private Subnet]
  ├─ [Security Group]
  ├─ [EC2]
  ├─ [IAM Role]
  └─ [S3 Bucket]

補足:

  • dev 環境専用の state を S3 backend で管理する
  • module ごとに責務を分けて再利用できる
  • stg prod も同じ構造で増やしやすい

大規模向けで意識したいこと

  • state はリモート管理する
  • module の責務を広げすぎない
  • 環境差分は tfvars に寄せる
  • 命名規則、タグ、変数ルールを揃える

メモ

  • 大規模向けでは「コード断片」より「分け方」のほうが重要になる
  • module 化しすぎて読みにくくならないように注意したい