こちらの記事では、Terraformの繰り返し処理の基本であるcount、for_eachの構文の基礎と使い方について解説します。この構文を使うことによって、Terraformで複数のリソースを作成する際のソースコードの冗長化を防ぐことができます。
繰り返し処理の重要性
Terraformで複数のリソースを作成する際、繰り返しの構文を使用しないと以下のようになります。
前提条件として、VPCは作成済みです。
resource "aws_subnet" "subnet_1" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
tags = {
Name = "subnet-1"
}
}
resource "aws_subnet" "subnet_2" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
tags = {
Name = "subnet-2"
}
}
resource "aws_subnet" "subnet_3" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.3.0/24"
tags = {
Name = "subnet-3"
}
}
3つのサブネットを作成しようとした場合、リソースの数だけコードを記述することになります。これを10個、20個と記述していくことを想像すると、ものすごく冗長なコードになることが分かります。
Terraformの繰り返し処理の構文であるcountやfor_eachを使うことで、この状況を避けることができます。結果として、コードの冗長化を防ぐとともに、可読性の向上にもつながります。
Terraformとは
そもそもTerraformとは?という方へ概要を説明します。
Terraformは、HashiCorp社によって開発されたオープンソースのIaCツールです。Terraformを使うことで、従来の手動でのインフラ管理や設定作業を自動化し、コードベースでの管理が可能になります。Terraformを使用することで、インフラの設定変更やデプロイを迅速かつ確実に行うことができます。
クラウド環境でのリソース管理において、Terraformは多くのクラウドプロバイダとの互換性を持っています。具体的にはAWS、GCP、Azureなど主要なクラウドサービスに対応しており、インフラ構築を一元的にコードで管理することができます。
IaCのメリットなどについては以下のリンクで解説しています。
https://envader.plus/article/136
Terraformを実際に触ってみたい!という方へ向けて、環境構築、EC2インスタンスの作成方法について以下の記事で解説しています。
https://envader.plus/article/162
countによる繰り返し処理
countはモジュールや全てのリソースブロックで使用することができる、Meta-Argumentと呼ばれる特別な引数のうちの一つです。
countを使用することで、一つのリソースブロックで複数のリソースを管理することができます。
countの基本的な使い方
基本的な使い方は以下です。
resource "aws_instance" "server" {
count = 4 # 作成したいリソースの数
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
tags = {
Name = "Server ${count.index}" # 0から始まるindexを取得することができる
}
}
Terraform公式ドキュメントより引用: Basic Syntax
count
で作成したいリソースの数値を入力し、count.index
で作成したリソースのindex番号を取得できます。
countの注意点
countで指定できるのは、整数のみです。リソース作成後に生成されるIDなどを直接参照することはできません。
# これはエラーになります
resource "aws_instance" "server" {
count = aws_other_resource.example.id
...
}
IDを指定する場合には、length関数などを使って指定する必要がありました。
variable "subnet_ids" {
type = list(string)
}
resource "aws_instance" "server" {
# length関数を使用してサブネットのIDを参照している
count = length(var.subnet_ids)
ami = "ami-xxxxxxxxx"
instance_type = "t2.micro"
subnet_id = var.subnet_ids[count.index]
tags = {
Name = "Server ${count.index}"
}
}
Terraform公式ドキュメントより引用: When to Use for_each Instead of count
また、countではリソースの削除などによりindexが変わってしまうと、参照するIDなどが変わる可能性があります。
# subnet-002が削除された場合、subnet-003のインスタンスIDも変わってしまう。
variable "subnets" {
description = "List of subnets"
default = ["subnet-001", "subnet-002", "subnet-003"]
}
resource "aws_instance" "example" {
count = length(var.subnets)
subnet_id = var.subnets[count.index]
// ... other configuration ...
}
リソースの削除などによって参照先が変わってしまう可能性があるリソースに対しては、後述するfor_eachを使用することを検討しましょう。
countを使ったリソースの作成
countを使用して、3つのサブネットを作成する例を紹介します。
resource "aws_subnet" "subnet" {
count = 3 # 作成したいリソースの数
vpc_id = aws_vpc.main.id
cidr_block = "10.0.${count.index + 1}.0/24" # indexは0から始まるため、1を足す
tags = {
Name = "subnet-${count.index + 1}"
}
}
冒頭で紹介したソースコードを再度掲載します。
resource "aws_subnet" "subnet_1" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
tags = {
Name = "subnet-1"
}
}
resource "aws_subnet" "subnet_2" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
tags = {
Name = "subnet-2"
}
}
resource "aws_subnet" "subnet_3" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.3.0/24"
tags = {
Name = "subnet-3"
}
}
今回の例は簡単な例ですが、21行あったコードが8行まで削減できているのが分かります。countを使用することで、ソースコードの冗長化と可読性を向上させることができます。
for_eachによる繰り返し処理
for_eachはTerraformのバージョン0.13で追加された機能です。count同様、Meta-Argumentと呼ばれる特別な引数のうちの一つです。for_eachもモジュールや全てのリソースタイプで使用することができます。
for_eachの基本的な使い方
for_eachへ渡す値で使用できるのは、Terraform がサポートするコレクション型(mapまたはset)です。以下にmapを使用した簡単な例を示します。
# 3人のユーザーを変数で定義
variable "user_map" {
default = {
alice = "admin"
bob = "guest"
carol = "editor"
}
}
resource "aws_iam_user" "example" {
for_each = var.user_map
name = each.key
tags = {
Role = each.value
}
}
この例では、aws_iam_user
リソースを作成する際にfor_each
を使用しています。var.user_map
変数は3つのユーザー(alice、bob、carol)とそれぞれのロール(admin、guest、editor)を定義しています。
each.key
では、var.user_map
の各キー(alice、bob、carol)を指定しています。
each.value
では、var.user_map
の各値(admin、guest、editor)を指定しています。
このようにeach.key
とeach.value
を使用することで、動的に値を設定できます。
for_eachを使ったリソースの作成
countと同じように、サブネットを3つ作成する例です。
resource "aws_subnet" "subnet" {
for_each = {
# mapの値を渡している
# each.key = subnet_1、subnet_2、subnet_3
# each.valueでmapの値を取得している
subnet_1 = {
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-1a"
},
subnet_2 = {
cidr_block = "10.0.2.0/24"
availability_zone = "ap-northeast-1c"
},
subnet_3 = {
cidr_block = "10.0.3.0/24"
availability_zone = "ap-northeast-1a"
}
}
cidr_block = each.value.cidr_block
availability_zone = each.value.availability_zone
vpc_id = aws_vpc.main.id
tags = {
Name = each.key
}
}
mapの値を変数化すると、for_eachの部分をもう少しスッキリ書くこともできます。
variable "subnets" {
default = {
subnet_1 = {
cidr_block = "10.0.1.0/24"
availability_zone = "ap-northeast-1a"
},
subnet_2 = {
cidr_block = "10.0.2.0/24"
availability_zone = "ap-northeast-1c"
},
subnet_3 = {
cidr_block = "10.0.3.0/24"
availability_zone = "ap-northeast-1a"
}
}
}
resource "aws_subnet" "subnet" {
# for_eachで変数を参照し、一つのkeyごとにループしながら値を設定していくことができる。
for_each = var.subnets
cidr_block = each.value.cidr_block
availability_zone = each.value.availability_zone
vpc_id = aws_vpc.main.id
tags = {
Name = each.key
}
}
このコードでは、for_eachに変数subnetsを指定しています。each.value
で変数内で定義した内部マップを指定し、each.value.~
でその内部マップの値を参照しています
for_each = var.subnets
の部分で var.subnets
の各エントリ(subnet_1
, subnet_2subnet_3
など)が順番に処理されます。それぞれの繰り返し処理で、each.key
は外側のマップのキー( など)を、each.value
はそのキーに対応する値(各サブネットの設定マップ)を参照します。
そして、each.value.cidr_block
や each.value.availability_zone
のようにして、その内部マップの「値」を取得しています。
for_eachの注意点
for_eachを使う際に理解しておくべきポイントを公式ドキュメントを元にまとめました。
for_eachに渡す値はmapかsetである必要がある。
for_eachで使用する値はmapかsetでなければいけません。この値を使用することによって、for_eachでリソースを作成する際にそれぞれのリソースが重複して作成されることを防ぐことができます。
mapやsetなどTerraformの型については、以下の記事で解説しています。
https://envader.plus/article/190
setの値を渡す場合は、each.keyもeach.valueも同じ値になる。
setの値はmapのようにkey=value
形式ではないため、each.key
、each.value
どちらを指定しても同じ値を参照します。
locals {
subnet_ids = toset([
"subnet-abcdef",
"subnet-012345",
])
}
resource "aws_instance" "server" {
for_each = local.subnet_ids
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
subnet_id = each.key # each.valueを指定しても同じ値を参照する
tags = {
Name = "Server ${each.key}"
}
}
Terraform公式ドキュメントより引用: Using Sets
plan実行時に判断できない値は使用できない
random_petなど動的に生成されるリソースタイプをfor_eachで使用することはできません。このリソースはランダムな「ペットネーム」(通常は人間が読みやすい形式の文字列)を生成しますが、planを実行する時点では値が確定していません。
# 悪い例
locals {
users = tomap({
name = random_pet.name.id
role = "admin"
})
}
# randomな値を生成。
resource "random_pet" "name" {
length = 2
}
resource "aws_iam_user" "example" {
# local.usersにはランダムな値が含まれるため、エラーになる。
for_each = local.users
name = each.key
tags = {
Role = each.value
}
}
このように、plan実行時には値がわからないものを参照している場合、for_eachではエラーになります。
uuidやtimestampなど毎回異なる結果を返す関数は使用できない
uuid、timestamp、bcryptなど同じ引数に対して毎回異なる結果を返す関数を使用した場合もエラーになります。これは、関数を実行するタイミングで生成される値が毎回異なる可能性があるためです。
# 悪い例
locals {
resources = {
# timestamp関数を使ってローカル変数のmapにキーを生成している
"${timestamp()}" = "value1"
}
}
resource "aws_s3_bucket" "my_buckets" {
# timestamp関数はplanやapply実行時に結果が変わる可能性があるため、エラーになる
for_each = local.resources
bucket = "${each.key}-bucket"
}
パスワードなどの機密性の高い値は使用できない
for_eachではsensitive=true
としている値を使用することができません。
Terraformでは機密性の高い情報を使用する場合、sensitive属性をtrue
にする場面があります。こうすることにより、plan
やapply
を実行する際に値が表示されるのを防ぐことができます。
この値をfor_eachで指定するとエラーが発生します。
# 悪い例
variable "sensitive_values" {
type = map(string)
# sensitiveの値をtrueにしている
sensitive = true
default = {
password1 = "supersecret"
password2 = "evenmoresecret"
}
}
resource "aws_s3_bucket" "my_buckets" {
# sensitive=trueの値を指定しているためエラーになる
for_each = var.sensitive_values
bucket = "${each.key}-bucket"
tags = {
SensitiveValue = each.value
}
}
まとめ
こちらの記事では、Terraformのループ処理であるcount、for_eachについて解説しました。
count、for_eachを使用することでソースコードの冗長化を防ぐことができ、可読性も向上させることができます。
シンプルなリストや数値に基づいてリソースを生成する場合にはcountが適しています。注意点として、countでは作成したリソースのIDなどを参照するには向いていないため、使用する場面は検討する必要があります。
for_eachはmapやsetを使用し、繰り返しリソースを作成する場面に適しています。
countとfor_eachを同時には使用できないため、ループ処理を行いたい場合には両者の適切な使い分けが必要になってくるでしょう。
【番外編】USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話
プログラミング塾に半年通えば、一人前になれると思っているあなた。それ、勘違いですよ。「なぜ間違いなの?」「正しい勉強法とは何なの?」ITを学び始める全ての人に知って欲しい。そう思って書きました。是非読んでみてください。
「フリーランスエンジニア」
近年やっと世間に浸透した言葉だ。ひと昔まえ、終身雇用は当たり前で、大企業に就職することは一種のステータスだった。しかし、そんな時代も終わり「優秀な人材は転職する」ことが当たり前の時代となる。フリーランスエンジニアに高価値が付く現在、ネットを見ると「未経験でも年収400万以上」などと書いてある。これに釣られて、多くの人がフリーランスになろうとITの世界に入ってきている。私もその中の1人だ。数年前、USBも知らない状態からITの世界に没入し、そこから約2年間、毎日勉学を行なった。他人の何十倍も努力した。そして、企業研修やIT塾で数多くの受講生の指導経験も得た。そこで私は、伸びるエンジニアとそうでないエンジニアをたくさん見てきた。そして、稼げるエンジニア、稼げないエンジニアを見てきた。
「成功する人とそうでない人の違いは何か?」
私が出した答えは、「量産型エンジニアか否か」である。今のエンジニア市場には、量産型エンジニアが溢れている!!ここでの量産型エンジニアの定義は以下の通りである。
比較的簡単に学習可能なWebフレームワーク(WordPress, Rails)やPython等の知識はあるが、ITの基本概念を理解していないため、単調な作業しかこなすことができないエンジニアのこと。
多くの人がフリーランスエンジニアを目指す時代に中途半端な知識や技術力でこの世界に飛び込むと返って過酷な労働条件で働くことになる。そこで、エンジニアを目指すあなたがどう学習していくべきかを私の経験を交えて書こうと思った。続きはこちらから、、、、
エンベーダー編集部
エンベーダーは、ITスクールRareTECHのインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。
関連記事
2020.02.25
完全未経験からエンジニアを目指す爆速勉強法
USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話
- キャリア・学習法
- エンジニア
2023.03.01
インフラエンジニア将来のキャリアパス SREや上流工程
インフラエンジニアは、ネットワークやサーバーといったITインフラを支える大切な存在です。今やなくてはならない人材とも言えます。活躍の場が広がっているからこそ、キャリアパスは重要です。
- インフラエンジニア
- キャリア・学習法
2023.03.01
インフラエンジニアは高収入?年収についての分析
今回は、そんなインフラエンジニアの年収について詳しく解説していきます。年収をアップさせる方法も紹介しているので、ぜひ参考にしてください。
- インフラエンジニア
- キャリア・学習法
2023.03.01
インフラエンジニアはフリーランスで稼げる?需要は?どのような企業が求めているかを解説
フリーランスになりたいと考えているインフラエンジニアの方に向けて、需要や年収、案件について解説していきます。向いている人/向いていない人も紹介しているので、ぜひ参考にしてください。
- インフラエンジニア
- キャリア・学習法