y-ohgi's blog

TODO: ここになにかかく

Terraform のディレクトリ設計案

f:id:y-ohgi:20210907205337p:plain

TL;DR

  • Terraform のディレクトリ設計の個人の意見です。
  • マサカリはむしろください。より良い構成のために。
  • Terraform でVPC EC2 を作る例を元にディレクトリ設計の案を出します。

About

Terraform のディレクトリ設計が溢れているので、俺の考えた最強のディレクトリ構成を提案します。
各所でディレクトリ構成について聞かれることが多いのと、ついにTerraform がGAしたのものあり、形に残しておきたかったこともあります。

Version

  • terraform
    • 1.0.6

Repository

y-ohgi/blog-terraform-dir

ディレクトリ構成

$ tree
.
|-- Makefile
|-- README.md
|-- docker-compose.yaml
|-- ec2
|   |-- main.tf
|   |-- provider.tf
|   |-- variables.tf
|   |-- variables_dev.tf
|   `-- variables_prd.tf
|-- network
|   |-- outputs.tf
|   |-- provider.tf
|   |-- routetable.tf
|   |-- subnet.tf
|   |-- variables.tf
|   |-- variables_dev.tf
|   |-- variables_prd.tf
|   `-- vpc.tf
`-- security-group
    |-- main.tf
    |-- modules
    |   `-- security-group
    |       |-- README.md
    |       |-- main.tf
    |       |-- outputs.tf
    |       |-- provider.tf
    |       `-- variables.tf
    |-- outputs.tf
    |-- provider.tf
    |-- variables.tf
    |-- variables_dev.tf
    `-- variables_prd.tf

5 directories, 27 files

タスクランナーとしてMakefile を使用する

Makefile をタスクランナーとして使用しています。
volume mount をしたいため、docker-compose も併用しています、が、Makefile からしか叩きません。

ディレクトリの粒度

大まかに、 ec2 network security-group のような単位でディレクトリを切って、それぞれ別々にapply します。
粒度の目安としては変更頻度・関連性を考えてディレクトリを切ります。
例えば network ディレクトリではVPC・Subnet・Route Table・IGW/NGW を作っていますが。セキュリティグループは含めていません。セキュリティグループの変更頻度は各ディレクトリが切られる毎にだいたい関連して変更されるためです。

環境分け

dev やprd のような環境分けにはworkspace を用います。
network ディレクトリを例に説明します。

最初にvariables.tf ファイルで workspace 変数に後述の variables_dev.tf, variables_prd.tf の変数を展開します。

variables.tf

variable "project_name" {
  description = "プロジェクト名(prefix で使用する)"
  default     = "myprj"
}

locals {
  workspaces = {
    dev = local.dev
    prd = local.prd
  }

  workspace = local.workspaces[terraform.workspace]

  name = "${var.project_name}-${basename(abspath(path.module))}"

  tags = {
    Name        = local.name
    Service     = local.name
    Environment = terraform.workspace
    Terraform   = "true"
  }
}

workspace がdev の場合、以下のような変数が展開されます

variables_dev.tf

locals {
  dev = {
    cidr = "172.16.0.0/16"

    azs             = ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
    private_subnets = ["172.16.1.0/24", "172.16.2.0/24", "172.16.3.0/24"]
    public_subnets  = ["172.16.101.0/24", "172.16.102.0/24", "172.16.103.0/24"]
  }
}

展開された変数は local.workspace["public_subnets"]) のように引っ張ってこれます。

subnet.tf

  :

resource "aws_subnet" "public" {
  count = length(local.workspace["public_subnets"])

  vpc_id = aws_vpc.this.id

  cidr_block        = element(local.workspace["public_subnets"], count.index)
  availability_zone = element(local.workspace["azs"], count.index)

  tags = merge(
    local.tags,
    { "Name" = format("%s-public-%d", local.name, count.index) }
  )
}

tfvars を指定しても良い、というかそちらのほうが厳密性が高いのですが、Makefile に頼らなくても書けるという点がメリットです。
なんらかのタスクランナーで環境毎にtfvar を指定できる場合はそちらの検討もしましょう。
workspace を使用する理由としt、S3/GCS のパスを自動で作成してくれる点が最も強い理由です。

backend の設定

backend でパスやリージョンのやkey の設定をするのはディレクトリを細かい粒度で切る場合は terraform init 時のオプションで設定するという選択肢があります。

SCOPE という変数でディレクトリを指定し、WORKSPACE という変数で環境を分けます。

$ make init SCOPE=vpc WORKSPACE=dev

Makefile

# usage:
#   $ make init SCOPE=network WORKSPACE=dev
# vars:
#  SCOPE: apply を実行するディレクトリ
#  WORKSPACE: 環境(dev, prd)の指定

SCOPE     := $(SCOPE)
WORKSPACE := $(WORKSPACE)

AWS_DEFAULT_REGION := ap-northeast-1
TFSTATE_BUCKET     := blog-terraform-dir
TFSTATE_KEY        := $(SCOPE)/terraform.tfstate

TF_CMD := docker-compose run --rm \
    -e SCOPE=$(SCOPE)   \
    -e AWS_DEFAULT_REGION=$(AWS_DEFAULT_REGION) \
    -e TF_VAR_state_bucket=$(TFSTATE_BUCKET) \
    -e TF_WORKSPACE=$(WORKSPACE) \
    terraform

init:
    @ $(TF_CMD) init \
    -get=true \
    -backend=true \
    -backend-config="region=$(AWS_DEFAULT_REGION)" \
    -backend-config="bucket=$(TFSTATE_BUCKET)" \
    -backend-config="key=$(TFSTATE_KEY)"

docker-compose.yaml

version: '3.9'

services:
  terraform:
    image: hashicorp/terraform:1.0.6
    volumes:
      - ~/.aws:/root/.aws:ro
      - ./:/app/:cached
    working_dir: /app/${SCOPE}

provider.tf

terraform {
  backend "s3" {} # ここを空にできる
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "3.57.0"
    }
  }
}

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

初期とapply

S3/GCS 等のstate を管理するバケットを最初に作成します。

$ aws s3 mb s3://blog-terraform-dir --region ap-northeast-1

次に、Makefile に定義してあるworkspace コマンドで、workspace の作成( $ terraform workspace new dev )を実行します。

$ make workspace SCOPE="network" ARG="new dev"
WARNING: The WORKSPACE variable is not set. Defaulting to a blank string.
Creating blog-terraform-dir_terraform_run ... done

Initializing the backend...

Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Installing hashicorp/aws v3.57.0...
- Installed hashicorp/aws v3.57.0 (signed by HashiCorp)

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
docker-compose run --rm -e SCOPE=network -e AWS_DEFAULT_REGION=ap-northeast-1 -e TF_VAR_state_bucket=blog-terraform-dir -e TF_WORKSPACE= terraform workspace new dev
WARNING: The WORKSPACE variable is not set. Defaulting to a blank string.
Creating blog-terraform-dir_terraform_run ... done
Created and switched to workspace "dev"!

You're now on a new, empty workspace. Workspaces isolate their state,
so if you run "terraform plan" Terraform will not see any existing state
for this configuration.

これでnetwork ディレクトリをdev 環境で使う際の初期設定ができました。
prd 環境を定義する場合は以下のように ARG="new prd" を使用します。

$ make workspace SCOPE="network" ARG="new prd"

apply する際は以下のコマンドで行います

$ make apply SCOPE="network" WORKSPACE="dev"

雑記

  • Terraform のディレクトリ構成は1つのディレクトリで全てを管理するとすぐに破綻するので、分けたほうが良いと思います。
    • Makefile 等のタスクランナーを使って、責務舞にディレクトリを切ってそれぞれ実行すると良いと思います
  • 環境差分の表現にworkspace を使うなと公式からお触れが出ていますが、workspace 以外に環境差分を表現する術が貧弱すぎるので良い感じになればなぁと。個人的にworkspace で満足はしてます
  • Terraform ベストプラクティス天下一武道会の開催、お待ちしております