ENECHANGE Developer Blog

ENECHANGE開発者ブログ

Terraformモジュール構成のベストプラクティス

VPoTの岩本 (iwamot) です。

この記事では、Terraformモジュール構成のベストプラクティスをご紹介します。Terraformドキュメントに書かれているものですが、従わずに時間を溶かした失敗談をまじえてお伝えすることで、同じ轍を踏む方が減ることを願っています。

取り上げるのは下記のベストプラクティスです。

Module Composition(フラットなモジュールツリー)

Module Compositionは、モジュールをフラットに並べられるよう構成すべし、という話です。Terraformドキュメントでは下記の例が挙げられています。

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

  base_cidr_block = "10.0.0.0/8"
}

module "consul_cluster" {
  source = "./modules/aws-consul-cluster"

  vpc_id     = module.network.vpc_id
  subnet_ids = module.network.subnet_ids
}

これに従わず、aws-consul-clusterモジュール内でnetworkモジュールを参照し、ルートモジュールで下記のように記述する設計も可能です。

module "consul_cluster" {
  source = "./modules/aws-consul-cluster"

  base_cidr_block = "10.0.0.0/8"
}

ただ、後者の設計だと、たとえばnetworkモジュールの変数が増えたときにconsul_clusterモジュールにも追加しなければならず、保守の手間がかかります。モジュールの結合度が強すぎるわけです。

ぼくが設計を失敗したのは、モジュールAとモジュールBの共通部分をモジュールCに切り出し、内部的に参照させたケースでした。開発途中で変数の引き回しが面倒になり、結果的にModule Compositionに従うこととなりました。最初から従っていれば、無駄な工数が省けたはずです。

Dependency Inversion(依存性の逆転)

モジュールがフラットになれば、下記のような記述も可能になります。

data "aws_vpc" "main" {
  tags = {
    Environment = "production"
  }
}

data "aws_subnet_ids" "main" {
  vpc_id = data.aws_vpc.main.id
}

module "consul_cluster" {
  source = "./modules/aws-consul-cluster"

  vpc_id     = data.aws_vpc.main.id
  subnet_ids = data.aws_subnet_ids.main.ids
}

つまり、networkモジュールを使わずとも、consul_clusterモジュールが使えるわけです。このように、モジュールで必要となるリソースをルートモジュールから渡す設計が「Dependency Inversion」です。

ぼくはこの点も考慮が不足していて、前述のモジュールCにおいて、モジュールA・Bのリソース命名規則に依存した処理を記述してしまいました。Module Compositionに従って構成をフラットに変えたものの、結合度の強さは変わらぬままでした。命名規則が変わるたび、モジュールCにも手を入れなければなりません。

そこで、モジュールCで必要となるリソースを、モジュールA・Bから渡してもらう設計に見直しました。Terraformドキュメントにあるように、object型の変数を定義すると、リソースが渡しやすくなります。

variable "ami" {
  type = object({
    # Declare an object using only the subset of attributes the module
    # needs. Terraform will allow any object that has at least these
    # attributes.
    id           = string
    architecture = string
  })
}

アンチパターン:リソースがなければ作成する

また、Terraformドキュメントでは「リソースがなければ作成する」(Conditional Creation of Objects)設計も避けるよう勧めています。もし不足しているリソースがあれば、ルートモジュール側で作成するほうが、あとで読む人が理解しやすいためです。

ぼくはこの点も考慮不足で、条件式の結果を count に代入し、リソースを作ったり作らなかったりする記述をしていました。ただ、当該リソースを参照するのに条件式をいろんな場所で書かなければならず、不便でやめました。こちらも最初から従っていれば、無駄な工数が省けたはずです。

まとめ

以上、Terraformモジュール構成のベストプラクティスを、ぼくの失敗談をまじえてお伝えしました。

結論として、下記の点に気をつけるとよいでしょう。

  • 他のモジュールに依存したモジュールは作らない
  • 条件式に依存したリソース作成はなるべく避ける(作成後に参照しないならOK)

ぼくも、またTerraformモジュールを作る機会があれば気をつけます。