Terraformで複数環境の共通部分をモジュール化する


developmentproduction の2環境のため、VPCを構築する例を考えてみます。

$ tree
.
├── development
│   └── main.tf
└── production
    └── main.tf

2 directories, 2 files

普通に書くと、 local.environment 以外、同内容の main.tf が2つ作成されます。

development/main.tf
locals {
  environment = "development"
}

terraform {
  required_version = "~> 1.1"

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

provider "aws" {
  profile = "example"
  region  = "ap-northeast-1"
}

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = local.environment
  }
}

output "vpc_id" {
  value = aws_vpc.main.id
}
production/main.tf
locals {
  environment = "production"
}

terraform {
  required_version = "~> 1.1"

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

provider "aws" {
  profile = "example"
  region  = "ap-northeast-1"
}

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = local.environment
  }
}

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

この程度なら何とかなりますが、環境やリソースや変更が増えていった場合、手に負えなくなっていきます。

上記の例をモジュール化した場合、次のようなファイル構成になると思います。

$ tree
.
├── development
│   └── main.tf
├── module
│   └── main.tf
└── production
    └── main.tf

3 directories, 3 files

モジュール化した共通部分です。

module/main.tf
variable "environment" {
  type = string
}

variable "profile" {
  type    = string
  default = "default"
}

variable "region" {
  type    = string
  default = "ap-northeast-1"
}

terraform {
  required_version = "~> 1.1"

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

provider "aws" {
  profile = var.profile
  region  = var.region
}

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"

  tags = {
    Name = var.environment
  }
}

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

各環境の定義ファイルです。

development/main.tf
locals {
  environment = "development"
  profile     = "example"
}

module "main" {
  source      = "../module"
  profile     = local.profile
  environment = local.environment
}

output "vpc_id" {
  value = module.main.vpc_id
}
production/main.tf
locals {
  environment = "production"
  profile     = "example"
}

module "main" {
  source      = "../module"
  profile     = local.profile
  environment = local.environment
}

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

上記の例では元々のリソース定義が短いため、一見したところ記述量が減っていないように見えます。

各環境の main.tf に注目すると、半分以下に減っていることがわかります。また、モジュール部分が長くなっています。

通常、環境ごとの差分より共通部分の方がかなり大きくなりますので、簡潔性と保守性は増していくと思います。