Terraformのディレクトリ構造と設計指針の一例を紹介します

エキサイト株式会社の武藤です。

エキサイトではAWSの構成管理にTerraformを採用しているサービスがいくつかあります。 Terraformのディレクトリ構造について検索してみると、様々なパターンが出てきます。 どのパターンが最適なのか、選択に迷ってしまう方も多いと思います。 今回は私が担当しているE・レシピについて、ディレクトリ構造と設計指針を紹介します。参考になれば幸いです。

全体構成

まずはディレクトリの全体構成についてです。

├── application
│   ├── web_server   // EC2作成
│   │   ├── dev
│   │   │    └── v1
│   │   └── prod
│   │      └── v1
│   └── modules   
├── gateway
│   ├── web_alb     // ALB作成
│   │  ├── dev
│   │  │    └── v1
│   │  └── prod
│   │     └── v1
│   └── modules 
└── infra
   └── vpc           // VPC作成
        ├── dev
        ├── modules
        └── prod

一例として、インフラリソースの作成単位にVPC、EC2、ALBがあるとします。 それぞれに対して、infra, application, gatewayというレイヤーを設けており、ディレクトリもそのように階層を分けています。 VPCは、最初に作られるインフラリソースです。その後、EC2、ALBの作成が行われます。 作成するリソースに依存関係があるように、レイヤーにも依存関係を考えます。

  • infra : VPC作成
  • application : EC2作成
  • gateway : ALB作成

このように依存関係があることで、作成するインフラリソースの順番、破棄する順番を示して、運用ルールとしています。 新しくインフラリソースを追加する際は、どこのレイヤーに依存しているかを考えてディレクトリを追加します。

moduleディレクトリは、基本的に各レイヤーごとにmoduleを作るようにします。 moduleが使われる範囲を狭めて、管理しやすくしています。

workspace 機能を使わない

Terraformには環境ごとの構成差分を管理するためにworkspace機能が用意されています。 しかし、弊チームでは可読性やわかりやすさを重視するため、workspace機能を使わずにディレクトリ階層によってdev, prodを分けています。 ディレクトリで示すことで、ひと目で環境差異が把握できる点、map関数、lookup関数を使わずにシンプルに書ける点があります。 (メンバーのTerraform習熟度が高ければ、workspaceを使う方が効率的なケースもあると思います。)

ディレクトリの環境分けの方法では重複コードも出てきますが、そこは諦めています。。

module化するタイミング

公式ドキュメントの考え方に則って考えています。 www.terraform.io

過度にmodule化すると全体構成が複雑になってしまい、メンテナンスしにくくなってしまいます。

We do not recommend writing modules that are just thin wrappers around single other resource types. If you have trouble finding a name for your module that isn't the same as the main resource type inside it, that may be a sign that your module is not creating any new abstraction and so the module is adding unnecessary complexity. Just use the resource type directly in the calling module instead.

上記では、リソースの薄いラッパーになるようなmodule化は推奨しないと説明しています。 基本的には複数のリソースを意味のある単位でmodule化しています。

moduleを定数として扱う

過度なmodule化に注意しつつ、極力重複する記述は避けたいところです。 AWSの場合は、セキュリティグループの設定が様々なリソース作成のたびに出てきますので、共通化したい設定の一つです。 セキュリティグループのインバウンド, アウトバウンドルールは、社内オンプレからのアクセス、AWSネットワークからのアクセスなど、同じIPレンジを使うことが多いので共通の定数として扱います。

下記は、定数を出力するだけのmoduleを作成し、リソースで読み込むサンプルコードです。

├── constants
│   ├── output.tf
│   └── security_group_rules.tf     // セキュリティグループに関する定数
# security_group_rules.tf
locals {
  # エキサイトAWSネットワーク
  security_group_excite_aws_network = {
    from_port = ****
    to_port   = ****
    protocol  = "tcp"
    cidr_blocks = [
       ***.***.***.***/**,
       ***.***.***.***/**,
       ***.***.***.***/**
    ]
    description = "hogehoge"
  },
  # ICMP
  security_group_icmp = {
    from_port = -1
    to_port   = -1
    protocol  = "icmp"
    cidr_blocks = [
      "0.0.0.0/0"
    ]
    description = "ICMP Rule"

  }

...
}
# 定数を読み込むリソースファイル
module "constants" {
  source = "../constants"
}

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

...

  security_group_rule_ingress = [
    module.constants.security_group_excite_aws_network,
    module.constants.security_group_icmp,

  ]

...
}

Terraformのlocal valuesの場合、外部からの入力できなくなるため、定数の用途で扱えます。

下記の記事を参考にさせていただきました。 qiita.com

最後に

Terraformのディレクトリ構造と設計指針について、一例を紹介しました。

今回は、可読性やわかりやすさを重視したディレクトリ構造と設計指針で実装を進めました。 何を重視するかはチームメンバーやサービスによって変わると思います。 メンバーと話し合いながら、運用しやすいTerraformを書きましょう。

参考記事

www.terraform.io

qiita.com