zaki work log

作業ログやら生活ログやらなんやら

[Terraform] countまたはfor_eachを使った複数リソース作成

countを使うことで簡単に同じリソースを複数作成できる。

learn.hashicorp.com

元ネタは以下で作成した定義ファイル。

zaki-hmkc.hatenablog.com

countを使った個数指定

www.terraform.io

例えばn人分の環境を作りたい場合で、とにかく数を指定してその個数のリソースを作るというパターン。

countの指定

resource "aws_instance" "bastion" {
  count  = 3

  ami                         = data.aws_ssm_parameter.amzn2_ami.value
  instance_type               = "t3.nano"
  key_name                    = aws_key_pair.my_key.id
  subnet_id                   = aws_subnet.prac_public.id
  security_groups             = [aws_security_group.allow_ssh_icmp.id]
  associate_public_ip_address = true

  tags = {
    Name = "bastion"
  }
}

count = 3 を追加している。

[zaki@cloud-dev practice (main)]$ ssh ec2-user@**.**.**.** -i ~/.ssh/id_aws_terraform 
The authenticity of host '**.**.**.** (**.**.**.**)' can't be established.
ECDSA key fingerprint is SHA256:lWRWcx/vBbr3QHTyJf10zxImxSfxY0l1V41k/u/wrqM.
ECDSA key fingerprint is MD5:7f:bd:48:2f:44:3a:8c:c3:cf:e3:6b:88:63:da:0e:c6.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '**.**.**.**' (ECDSA) to the list of known hosts.

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
[ec2-user@ip-172-25-10-145 ~]$ 

ログインもちゃんとできる。

ただ、一覧で見てもわかるとおり、全て同じtag名になっている。

f:id:zaki-hmkc:20210505123301p:plain

インデックス値参照

count.indexでいわゆるループ内のインデックス値を参照できる。
これをtag名に付与してやればOK。

  tags = {
    Name = "bastion-${count.index}"
  }

f:id:zaki-hmkc:20210505123412p:plain

これで連番をsuffixに付けることができた。

for_eachを使ったリスト指定

www.terraform.io

作成するリソースは同じだけど、用途が異なるためそれぞれ別の名前を付けたい、などのパターン。
例として「app」と「db」というEC2を作る場合。

リスト定義

ホスト一覧として、appdbという文字列型のリスト(配列)を定義。

variable "host_list" {
    description = "host count"
    type        = list(string)
    default     = [
        "app",
        "db",
    ]
}

for_eachの指定

resource "aws_instance" "server" {
  for_each = toset(var.host_list)

  ami                         = data.aws_ssm_parameter.amzn2_ami.value
  instance_type               = "t3.nano"
  key_name                    = aws_key_pair.my_key.id
  subnet_id                   = aws_subnet.prac_priv2.id
  security_groups             = [aws_security_group.allow_ssh_icmp.id]
  associate_public_ip_address = false

  tags = {
    Name = each.value
  }
}

countと同じような要領で、for_eachを使ってホスト一覧を指定する。
for_eachを使うことで、リストの要素ごとにリソースが作成される。

また、それぞれホスト毎の名前を指定したいtag名については、each.valueを指定することで要素の内容に置き換わる。

f:id:zaki-hmkc:20210505123445p:plain

残課題

「count数の環境ごとにfor_eachの要素を作成 (bastion/app/dbのセットをcount数分作る)」をやりたくて、

resource "aws_instance" "server" {
  for_each = toset(var.host_list)
  count = var.host_count

  ami                         = data.aws_ssm_parameter.amzn2_ami.value
  instance_type               = "t3.nano"
  key_name                    = aws_key_pair.my_key.id
  subnet_id                   = aws_subnet.prac_priv2.id
  security_groups             = [aws_security_group.allow_ssh_icmp.id]
  associate_public_ip_address = false

  tags = {
    Name = "${each.value}-${count.index}"
  }
}

みたいに書いてみたけど、Terraformはcountfor_eachを併用できなかった。

[zaki@cloud-dev practice (main)]$ terraform plan
╷
│ Error: Invalid combination of "count" and "for_each"
│ 
│   on ec2.tf line 26, in resource "aws_instance" "server":
│   26:   for_each = toset(var.host_list)
│ 
│ The "count" and "for_each" meta-arguments are mutually-exclusive, only one should be used to be explicit about the number of resources to be
│ created.
╵

これはまた別のやり方で作る必要がある(未調査)

つかいわけ

同じサイズのコンピュートインスタンスを指定数分だけ並べたい程度であればcountでよさそうだが、たとえばサブネットの定義みたいに、CIDRとかのパラメタが異なるものを複数作る必要がある場合は、ListやMapで定義してfor_eachを使う方がよさそう。