今回の記事について
仕事内容が今年度から変わったため、お久しぶりの投稿になってしまいました。。。
今回は、Terraform講座の第5回目として、繰り返し処理について解説しようと思います!
繰り返し処理とは?
東京リージョン内のVPCに、AZごとにプライベートサブネットを作りたいとき、皆さんならどうTerraformコードを書きますか?
パッと思いつくのは、1つ1つリソースを書く方法だと思います。
resource "aws_subnet" "private-1a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.0.0/24"
availability_zone = "ap-northeast-1a"
}
resource "aws_subnet" "private-1c" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-1c"
}
resource "aws_subnet" "private-1d" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
availability_zone = "ap-northeast-1d"
}
直感的で手を動かせばいいだけですが、かなり似たようなコードになっています。
さらに「パブリックサブネットとプライベートサブネット1つずつ作りましょう!」となると、合計で6個も似たようなコードを書くことになり、オプションが増えたりすると管理がどんどん煩雑になります。以上のコーディングの労力と管理を楽にするために、「1つのテンプレのようなコードを書いて欲しい数だけリソースを作成させる」 というのが繰り返し処理です。
Terraformでは、count
やfor_each
で繰り返し処理を実現することが出来ます!
countによる繰り返し処理
countを使用した例文
count
による繰り返し処理は、作成したい数を指定して繰り返し処理を行います。
作成したい数については基本的にあらかじめ配列を定義したうえで、その長さを指定することが多いです。
例えば、先ほどのサブネット作成を例にするとAZ名の配列とCIDRの配列を用意しておき、それらの長さをもとに繰り返し処理を定義します。
# AZ名の配列
variable "az_names" {
default = [
"ap-northeast-1a",
"ap-northeast-1c",
"ap-northeast-1d"
]
}
# CIDRの配列
variable "cidr_block" {
default = [
"10.0.0.0/24",
"10.0.1.0/24",
"10.0.2.0/24"
]
}
resource "aws_subnet" "private" {
# まず、countを記述
count = length(var.az_names)
vpc_id = aws_vpc.main.id
cidr_block = cidr_block[count.index]
availability_zone = az_names[count.index]
}
これにより、1つの aws_subnet
リソースのコードを記述するだけで配列の長さ=3つ分のサブネットを作成することが出来ます!とてもスマートになりました!!
countの難点
さて、このcount
ですが、コーディング負担は楽になる一方、Terraform内のリソース管理で少し難しくなってしまうケースがあります。
というのも、実際にPlan結果を見てみると、作成されるリソースは
# aws_subnet.private["0"] will be created
# aws_subnet.private["1"] will be created
# aws_subnet.private["2"] will be created
となります。これでは、先ほどのコードと比較して、どのサブネットがどのAZに構築したリソースなのか直感的に分かりづらくなっています。
では、もっといい方法が無いかというので、今度はmap
を使用して繰り返し処理を行うfor_each
について紹介します。
for_eachによる繰り返し処理
mapとは?
いわゆる辞書型のデータで、以下のようにKey-Value形式で値が対応しています。
variable "cidr_block" {
type = map(string)
default = {
"ap-notrheast-1a" = "10.0.0.0/24"
"ap-northeast-1c" = "10.0.1.0/24"
"ap-northeast-1d" = "10.0.2.0/24"
}
}
例えば、cidr_block["ap-northeast-1a"] = "10.0.0.0/24"
となります。
そして、for_each
文では、Keyによるリソース名を作成することが出来ます。
for_eachを使用した例文
for_each
によるサブネット構築のコードは以下のようになります。
variable "cidr_block" {
type = map(string)
default = {
"1a" = "10.0.0.0/24"
"1c" = "10.0.1.0/24"
"1d" = "10.0.2.0/24"
}
}
resource "aws_subnet" "private" {
# まず、for_each文を記述
for_each = var.cidr_block
vpc_id = aws_vpc.main.id
cidr_block = each.value
availability_zone = "ap-northeast-${each.key}"
}
これにより、count
と同様に、3つのサブネットを作成することが出来ます。そして、Planを行うと作成されるリソースは、
# aws_subnet.private["1a"] will be created
# aws_subnet.private["1c"] will be created
# aws_subnet.private["1d"] will be created
となり、どのサブネットがどのAZに構築されているのか一目で分かる管理となっています!
forを使用したfor_each
場合によっては、変数cidr_block
は、object
型の配列として用意されている可能性もあります。
object
型は、map
型に様々な型の値を入れることが出来るデータ型になります。今回は文字列型だけになりますが笑。そして、この例だとあまり恩恵がない感じになっていますがそこもご容赦ください・・・。
variable "subnet_variables" {
type = list(object({
az_name = string
cidr_block = string
}))
default = [
{
az_name = "1a"
cidr_block = "10.0.0.0/24"
},
{
az_name = "1c"
cidr_block = "10.0.1.0/24"
},
{
az_name = "1d"
cidr_block = "10.0.2.0/24"
}
]
}
このような変数の場合、for
文を使用して、map
に変更することが出来ます。
記述方法は以下の通りです。
subnet_info_map = { for l in subnet_variables : l.az_name => l }
これの見方についてです。
まず、:
で区切ってください。:
の左側がfor
文で配列の各要素を取り出しており、右側でsubnet_info_map
のKeyとValueを定義しています。つまり、以下の対応になっています!
subnet_info_map["1a"] = {
az_name = "1a"
cidr_block = "10.0.0.0/24"
}
これを使用したfor_each
は次の通りです。
variable "subnet_variables" {
type = list(object({
az_name = string
cidr_block = string
}))
default = [
{
az_name = "1a"
cidr_block = "10.0.0.0/24"
},
{
az_name = "1c"
cidr_block = "10.0.1.0/24"
},
{
az_name = "1d"
cidr_block = "10.0.2.0/24"
}
]
}
resource "aws_subnet" "private" {
for_each = { for l in subnet_variables : l.az_name => l }
vpc_id = aws_vpc.main.id
cidr_block = each.value.cidr_block
availability_zone = "ap-northeast-${each.value.az_name}"
}
まとめ
count
、for_each
どちらを使うべきかは、筆者もまだ勉強中です。。。まずは壁にぶち当たってみて()、どんどんスマートに使い分けて、コード文の記述量・そして管理の負担を少なくしていきましょう!