Terraformを使うとプログラミングに不慣れな人でも比較的簡単にインフラのコード化(IaC)ができますが、その反面、ちょっとでも複雑なロジックを書こうとするとクセの強さを実感するときがあります。
そもそもTerraformのウリは「本質的なコードだけを宣言的に記述できて、誰でも簡単に読みやすいコードが書けること」なので、複雑なロジックを書こうとすること自体が若干ナンセンスなのです。が、汎用性や再利用性を考慮したコードを書くとなると避けては通れません。
今回は入門編として、Terraformで複雑なロジックを書く際の基本的な考え方やテクニックについて紹介してみたいと思います。(ちなみにTerraformのバージョンは0.12を前提とします)
単純なループは簡単に書ける
Terraformでも単純なループであれば簡単に書けます。
例えば、インスタンスを作るこんな定義について
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
}
ループ処理をしたいときは count
を使います。
resource "aws_instance" "web" {
count = 2
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
}
これで2つのインスタンスが作られることになります。簡単ですね。
count
以外にも繰り返し処理を実現する機能はありますが、まずは基本として count
をおさえましょう。
2重ループは無理、1重ループだけ。
例えば、あるリスト web_list = ["web1", "web2"]
の各要素に対して繰り返し処理をしたいとき
locals {
web_list = ["web1", "web2"]
}
resource "aws_instance" "web" {
count = length(local.web_list)
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
tags = {
Name = local.web_list[count.index]
}
}
このように書いてそれぞれのインスタンスに別々の名前を付けることができます。これは難しくありませんね。
では、例としては嘘くさくなりますが「 web_a と web_b と web_c、それぞれを t2.micro と t2.small の組で作りたい」ということで
web_list = ["web_a", "web_b", "web_c"]
type_list = ["t2.micro", "t2.medium"]
という2つのリストが与えられたとき、どのように書けばよいのでしょうか。
シェルスクリプト脳の僕らは「2つのリストの組み合わせだから、2重ループだな…」と考えてすぐにこんなコードを妄想するはずです。
locals {
web_list = ["web_a", "web_b", "web_c"]
type_list = ["t2.micro", "t2.medium"]
}
for name in local.web_list
do
for type in local.type_list
do
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = type
tags = {
Name = name
}
}
done
done
しかし、先ほど説明したようにTerraformのループ表現はあくまでも count
しかないので、こんなふうに2重ループを表現することはできません。
ではどうやるか。
2重ループを1重ループに変換することを考えます。
そのためには、処理するデータ自体を工夫する必要があります。2重ループは…言い換えれば「2つのデータを組み合わせて処理をする」ということになるので、食わせるデータを事前に組み合わせておけば1重ループで処理が可能ということになります。
言葉にすると分かりづらいので端的に表現してしまうと、
web_list = ["web_a", "web_b", "web_c"]
type_list = ["t2.micro", "t2.medium"]
という別々のデータを処理するのではなく
web_type = [
{ "name" = "web_a", "type" = "t2.micro" },
{ "name" = "web_a", "type" = "t2.medium" },
{ "name" = "web_b", "type" = "t2.micro" },
{ "name" = "web_b", "type" = "t2.medium" },
{ "name" = "web_c", "type" = "t2.micro" },
{ "name" = "web_c", "type" = "t2.medium" },
]
という感じであらかじめ組にしたデータを作っておけば、1重ループで処理できるということです。1列のデータになっているので1重ループで処理できるということですね。
じゃあこの組み合わせというのを、どのようにコードとして表現するか…
突然ですが、皆さんはPythonのリスト内包表記を知っていますか?
squares = [i**2 for i in range(5)]
こんなやつです。
これは言い換えるとつまりこういうこと
squares = [0, 1, 4, 9, 16]
リスト内でループを回して要素を1個ずつ作っていくイメージです。ループを回してデータを作っているんですね。
実はTerraformにもこれと似た機能があり、
squares = [for i in range(5): i*i]
というふうに同様の表現をすることができます。( range
関数もある)
これを利用して、2重ループを表現するデータを作成します。
locals {
web_list = ["web_a", "web_b", "web_c"]
type_list = ["t2.micro", "t2.medium"]
web_type = [
for name in web_list: [
for type in type_list: {
"name" = name
"type" = type
}
]
]
}
このとき、 web_type
変数の中身は以下のようになります。
[
[
{ "name" = "web_a", "type" = "t2.micro" },
{ "name" = "web_a", "type" = "t2.medium" },
],
[
{ "name" = "web_b", "type" = "t2.micro" },
{ "name" = "web_b", "type" = "t2.medium" },
],
[
{ "name" = "web_c", "type" = "t2.micro" },
{ "name" = "web_c", "type" = "t2.medium" },
]
]
…まあ、それっぽくはなっていますが、これではまだダメですね。階層が無駄に深くなって凸凹しちゃっています。凸凹していると1重ループで淡々と処理することはできませんね。
では、階層をフラットにする加工を施しましょう。こういったデータの加工を行うため、Terraformには多くの関数がバンドルされています。関数はかなり充実しているので、柔軟にデータ加工を行うことができます。
そのなかの一つに flatten
関数があります。これを使うと文字通り、階層をフラットにすることができるので、さきほどのデータを
[
{ "name" = "web_a", "type" = "t2.micro" },
{ "name" = "web_a", "type" = "t2.medium" },
{ "name" = "web_b", "type" = "t2.micro" },
{ "name" = "web_b", "type" = "t2.medium" },
{ "name" = "web_c", "type" = "t2.micro" },
{ "name" = "web_c", "type" = "t2.medium" }
]
このように変換することができます。
つまり変数定義を書き直して、
locals {
web_list = ["web_a", "web_b", "web_c"]
type_list = ["t2.micro", "t2.medium"]
web_type = flatten(
[
for name in web_list: [
for type in type_list: {
"name" = name
"type" = type
}
]
]
)
}
と定義すれば、
resource "aws_instance" "web" {
count = length(local.web_type)
ami = data.aws_ami.ubuntu.id
instance_type = local.web_type[count.index].type
tags = {
Name = local.web_type[count.index].name
}
}
こんな感じで目的の処理を実装することができます。
まとめ
- Terraformでループを表現する基本的な機能としては
count
があります。 - Terraformには入れ子のループを直接的に表現する仕組みがありません。
- 複数のリストを組み合わせたループを実装したいときには、まず1重ループで処理できるようにローカル変数を作成することから始めます。
- 組み込み関数が充実しているので、データの加工はかなり柔軟にできます。
今回の例に限らずなにか複雑な処理を実装するときは、まず必要な情報を整理したローカル変数を作るところから始め、あとは resource
内部でも適宜組み込み関数を駆使してロジックを組み立てていくという流れを覚えるとラクにコーディングできるようになります。
あと、HashiCorp社のツール全般に言えることですが、Terraformのドキュメントは平易な英語でコードサンプルも充実しているので、なにはともあれまずドキュメントを読んでみることを初心者にはオススメします!!!
以上