はじめに
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にはまだまだ強力な機能がありますが、他の機能に関してはまた違う機会にでも紹介していきたいと思います。