TerragruntでDRYなTerraform

はじめに

IaC (Infrastructure as Code) に興味がある人はTerraformについて聞いたことがあるかと思います。様々なクラウドプロバイダーのインフラをコードで宣言的に管理できる優れたツールです。Terraform単体でも強力な機能を多数持っているのですが、今回はそのTerraformにさらに+αな機能を加えたTerragruntについて触れていきたいと思います。TerragruntはDevOps as a Serviceを提供しているGruntwork社が公開しているTerraformのラッパーツールです。「Terraformにこんな機能があったらいいのになー」というものが実装されている感じです。

共通化の全く無いTerraform

まずは共通化が全くされていないプレーンなTerraformを見てみます。ディレクトリ構成は↓のような状態です。

└── live
├── production
│   └── app
│       ├── main.tf
│       └── terraform.tfvars
└── staging
└── app
├── main.tf
└── terraform.tfvars

main.tf の中身は全く同じで↓のようになっています。

variable "instance_count" {
description = "How many servers to run"
}
variable "instance_type" {
description = "What kind of servers to run (e.g. t2.large)"
}
provider "aws" {
region = "ap-northeast-1"
}
resource "aws_instance" "web" {
ami           = "ami-afb09dc8"
instance_type = "${var.instance_type}"
count = "${var.instance_count}"
}

stagingでは環境のレベルを下げ気味で tfvars にて↓のように定義し

instance_count = 3
instance_type = "t2.micro"

production ではあるべき姿に設定しています↓

instance_count = 10
instance_type = "m2.large"

とりあえずはこの定義で staging と production のインフラを作ることができます。

moduleを使ったDRYなアプローチ

さて、上の例ははまったくもってDRY(Don’t Repeat Yourself)ではないので、ここでTerraformに標準機能として備わっている module を使って共通化を試みたいと思います。

まずは共通部分を切り出してGithubに置きます。こうすることで、この共通部分を呼び出したいTerraformのファイルは module として呼び出せるようになります。

module はhttps://github.com/capsulecloud/tf_ec2_sampleに置いてあるものとして、構成としては以下のとおり。

└── app
├── main.tf
└── variables.tf

それぞれのファイルの中身は以下のように分解されています。

# variables.tf
variable "instance_count" {
description = "How many servers to run"
}
variable "instance_type" {
description = "What kind of servers to run (e.g. t2.large)"
}
# main.tf
resource "aws_instance" "web" {
ami = "ami-afb09dc8"
instance_type = "${var.instance_type}"
count = "${var.instance_count}"
}

このような module が存在している場合に、まずディレクトリ構成は以下のようになります。

└── live
├── production
│   └── app
│       └── main.tf
└── staging
└── app
└── main.tf

そして呼び元からは以下のように呼び出すことができます。

# staging
provider "aws" {
region = "ap-northeast-1"
}
module "ec2" {
source = "git::https://github.com/capsulecloud/tf_ec2_sample//app"
instance_type = "t2.micro"
instance_count = "3"
}
# production
provider "aws" {
region = "ap-northeast-1"
}
module "ec2" {
source = "git::https://github.com/capsulecloud/tf_ec2_sample//app"
instance_type = "m2.large"
instance_count = "10"
}

単にこれは ec2 の module なのでほとんど恩恵がみられませんが、これが大きな module になった場合にこの共通化はなかなかな威力を発揮し始めます。

ただ、小さなモジュールだから余計に感じてしまうのですが、 provider も冗長ですし、 module のインプットをハードコードしてるのも微妙です(tfvars にしていいのですが、それも冗長な感じがしてしまう)。stagingとproductionでとにかく変えたいのは instance_type と instance_count だけなのです!それ以外には興味が無いのに、毎回書いてしまっています。

TerragruntによるDRYなアプローチ

上の課題をさらに解決してくれるのが Terragrunt です。Terragruntの場合、 module を作るというよりはまるっと今までの Terraform のファイルを再利用してしまうイメージです。なので↓のように provider ブロックもそのまま入れてしまいます。

# variables.tf
variable "instance_count" {
description = "How many servers to run"
}
variable "instance_type" {
description = "What kind of servers to run (e.g. t2.large)"
}
# main.tf
provider "aws" {
region = "ap-northeast-1"
}
resource "aws_instance" "web" {
ami           = "ami-afb09dc8"
instance_type = "${var.instance_type}"
count = "${var.instance_count}"
}

そして、このTerraformを参照する側のディレクトリ構成は↓のようになります。注目したいのは terraform.tfvars だけになっている点です。

└── live
├── production
│   └── app
│       └── terraform.tfvars
└── staging
└── app
└── terraform.tfvars

中身は↓のようになっています。Terragrunt独自の terragrunt ブロックがあることに注目してください。ここに共通利用したいTerraformファイルを参照させることができます。そして標準の terraform.tfvars 同様、変数の値も指定できるんです。これで極限までDRYにもっていけることがわかったと思います。少々シンプルすぎる例だったので恩恵が伝わりにくい部分もあるかと思いますが、これは確かに qa, staging, production と分けたい場合には便利だと思います。

# staging
terragrunt = {
terraform {
source = "git::https://github.com/capsulecloud/tf_ec2_sample//app"
}
}
instance_count = 3
instance_type = "t2.micro"
# production
terragrunt = {
terraform {
source = "git::https://github.com/capsulecloud/tf_ec2_sample//app"
}
}
instance_count = 10
instance_type = "m2.large"

 

おわりに

今回はTerraformのコードをDRYにする方法をTerragruntで紹介しました。Terragruntにはまだまだ強力な機能がありますが、他の機能に関してはまた違う機会にでも紹介できたらと思います。

APN Consulting Partner
スーパーソフトウエアはAWSパートナーネットワーク(APN)のコンサルティングパートナーです。